From aaf8a50511d77dbc209ca93bbba308c21179a8bc Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 31 三月 2026 15:38:47 +0800
Subject: [PATCH] #前端

---
 rsf-design/src/api/system-manage.js                                                                         |  443 
 rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-detail-drawer.vue |   83 
 rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemTable.columns.js                                      |  150 
 rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js                                               |  260 
 rsf-design/src/views/manager/task-item-log/taskItemLogTable.columns.js                                      |  164 
 rsf-design/src/views/manager/qly-ispt-item/index.vue                                                        |  245 
 rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js                                       |  313 
 rsf-design/src/views/orders/wave-item/modules/wave-item-detail-drawer.vue                                   |   55 
 rsf-design/src/views/stock/stock-transfer/stockTransferPage.helpers.js                                      |  136 
 rsf-design/src/views/basic-info/loc-area/modules/loc-area-dialog.vue                                        |  162 
 rsf-design/src/views/orders/wait-pakin-item/index.vue                                                       |  333 
 rsf-design/src/views/basic-info/loc-area-mat-rela/index.vue                                                 |  471 
 rsf-design/src/views/system/task-instance-node/modules/task-instance-node-detail-drawer.vue                 |   45 
 rsf-design/src/views/manager/task-item/taskItemPage.helpers.js                                              |  203 
 rsf-design/src/views/manager/task-log/taskLogPage.helpers.js                                                |  162 
 rsf-design/src/views/basic-info/loc-type/modules/loc-type-detail-drawer.vue                                 |   57 
 rsf-design/src/views/basic-info/bas-container/basContainerPage.helpers.js                                   |  342 
 rsf-design/src/views/orders/delivery/deliveryTable.columns.js                                               |  122 
 rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-detail-drawer.vue                         |   60 
 rsf-design/src/views/basic-info/contract/modules/contract-dialog.vue                                        |  156 
 rsf-design/src/views/orders/wait-pakin-log/waitPakinLogTable.columns.js                                     |  137 
 rsf-design/src/views/manager/menu-pda/menuPdaTable.columns.js                                               |   97 
 rsf-design/src/views/manager/stock/index.vue                                                                |  278 
 rsf-design/src/views/system/tenant/index.vue                                                                |  283 
 rsf-design/src/api/loc-area.js                                                                              |  142 
 rsf-design/src/views/orders/check-item/index.vue                                                            |   92 
 rsf-design/src/api/device-bind.js                                                                           |  167 
 rsf-design/src/views/system/flow-step-template/flowStepTemplatePage.helpers.js                              |  165 
 rsf-design/src/views/orders/transfer-item/transferItemTable.columns.js                                      |  164 
 rsf-design/src/views/orders/asn-order/index.vue                                                             |  399 
 rsf-design/src/views/system/config/configPage.helpers.js                                                    |  119 
 rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js                                      |  131 
 rsf-design/src/views/statistics/in-statistic/inStatisticPage.helpers.js                                     |  107 
 rsf-design/src/views/manager/revise-log-item/reviseLogItemTable.columns.js                                  |  260 
 rsf-design/src/api/ai-config.js                                                                             |  225 
 rsf-design/src/views/basic-info/device-site/deviceSiteTable.columns.js                                      |  150 
 rsf-design/src/views/system/serial-rule-item/index.vue                                                      |  430 
 rsf-design/src/views/system/flow-step-instance/flowStepInstanceTable.columns.js                             |   35 
 rsf-design/src/views/reports/statistic-count/statisticCountTable.columns.js                                 |   44 
 rsf-design/src/views/orders/wave/modules/wave-public-task-dialog.vue                                        |   68 
 rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js                             |  176 
 rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaPage.helpers.js                             |  389 
 rsf-design/src/views/basic-info/task-path-template-node/index.vue                                           |  362 
 rsf-design/src/views/manager/task-item/index.vue                                                            |  265 
 rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue                                     |   93 
 rsf-design/src/views/system/fields-item/modules/fields-item-detail-drawer.vue                               |   52 
 rsf-design/src/views/orders/wave-item/index.vue                                                             |  202 
 rsf-design/src/views/orders/check-item/checkOrderItemPage.helpers.js                                        |   55 
 rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js                                      |  245 
 rsf-design/src/views/system/dept/deptTable.columns.js                                                       |   69 
 rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js                                       |  146 
 rsf-design/src/views/basic-info/loc-area-mat/index.vue                                                      |  365 
 rsf-design/src/views/orders/purchase/modules/purchase-detail-drawer.vue                                     |   91 
 rsf-design/src/views/manager/stock/modules/stock-detail-drawer.vue                                          |   36 
 rsf-design/src/views/system/dict-type/index.vue                                                             |  225 
 rsf-design/src/views/system/user/index.vue                                                                  |   15 
 rsf-design/src/views/system/serial-rule/serialRulePage.helpers.js                                           |  124 
 rsf-design/src/views/system/task-instance-node/index.vue                                                    |  233 
 rsf-design/src/views/system/operation-record/operationRecordTable.columns.js                                |   76 
 rsf-design/src/api/preparation-item.js                                                                      |   78 
 rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-dialog.vue                                  |  202 
 rsf-design/src/views/orders/wait-pakin-item/waitPakinItemPage.helpers.js                                    |  220 
 rsf-design/src/api/wave.js                                                                                  |  145 
 rsf-design/src/router/index.js                                                                              |    2 
 rsf-design/src/views/manager/wave-rule/waveRuleTable.columns.js                                             |   73 
 rsf-design/src/views/manager/task-item/modules/task-item-detail-drawer.vue                                  |   56 
 rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-detail-drawer.vue               |   60 
 rsf-design/src/views/manager/revise-log/reviseLogTable.columns.js                                           |  127 
 rsf-design/src/views/system/fields-item/modules/fields-item-dialog.vue                                      |  174 
 rsf-design/src/views/orders/delivery/deliveryPage.helpers.js                                                |  264 
 rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js                                              |  296 
 rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue                                   |   74 
 rsf-design/src/views/orders/preparation/index.vue                                                           |  385 
 rsf-design/src/views/manager/freeze/freezeTable.columns.js                                                  |  101 
 rsf-design/src/views/orders/asn-order-item/modules/asn-order-item-detail-drawer.vue                         |  107 
 rsf-design/src/api/bas-station.js                                                                           |  190 
 rsf-design/src/views/basic-info/companys/modules/companys-dialog.vue                                        |  247 
 rsf-design/src/views/statistics/out-statistic-item/modules/out-statistic-item-detail-drawer.vue             |   57 
 rsf-design/src/views/system/dict-type/dictTypePage.helpers.js                                               |   92 
 rsf-design/src/api/purchase.js                                                                              |  118 
 rsf-design/src/views/manager/wave-rule/modules/wave-rule-detail-drawer.vue                                  |   41 
 rsf-design/src/locales/langs/en.json                                                                        |   13 
 rsf-design/src/views/basic-info/device-site/modules/device-site-dialog.vue                                  |  137 
 rsf-design/src/views/system/ai-param/index.vue                                                              |  392 
 rsf-design/src/views/basic-info/warehouse/warehouseTable.columns.js                                         |  118 
 rsf-design/src/views/work/out-bound/outBoundPage.helpers.js                                                 |  239 
 rsf-design/src/api/bas-container.js                                                                         |  199 
 rsf-design/src/views/manager/task/taskPage.helpers.js                                                       |  146 
 rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-detail-drawer.vue                        |   47 
 rsf-design/src/api/loc-preview.js                                                                           |   51 
 rsf-design/src/views/manager/qly-ispt-item-result/index.vue                                                 |   74 
 rsf-design/src/views/manager/loc-dead-report/modules/loc-dead-report-detail-drawer.vue                      |   82 
 rsf-design/src/utils/backend-menu-title.js                                                                  |    2 
 rsf-design/src/api/wave-item.js                                                                             |   71 
 rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-detail-drawer.vue   |   88 
 rsf-design/src/router/guards/beforeEach.js                                                                  |   27 
 rsf-design/src/api/loc-area-mat-rela.js                                                                     |  228 
 rsf-design/src/views/statistics/out-statistic/index.vue                                                     |  140 
 rsf-design/src/views/stock/warehouse-stock/warehouseStockTable.columns.js                                   |   93 
 rsf-design/src/api/asn-order-log.js                                                                         |  108 
 rsf-design/src/views/system/tenant/modules/tenant-init-dialog.vue                                           |  219 
 rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-detail-drawer.vue                     |   44 
 rsf-design/src/api/warehouse-areas-item.js                                                                  |   80 
 rsf-design/src/api/loc-area-mat.js                                                                          |  240 
 rsf-design/src/views/orders/check/checkOrderTable.columns.js                                                |  134 
 rsf-design/src/views/manager/loc-revise/modules/loc-revise-detail-drawer.vue                                |   92 
 rsf-design/src/views/system/flow-instance/modules/flow-instance-detail-drawer.vue                           |   35 
 rsf-design/src/views/basic-info/bas-container/modules/bas-container-detail-drawer.vue                       |   83 
 rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue                           |   78 
 rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js                                      |  141 
 rsf-design/src/views/basic-info/matnr-group/matnrGroupPage.helpers.js                                       |  311 
 rsf-design/src/views/system/serial-rule/index.vue                                                           |  223 
 rsf-design/src/views/system/dept/modules/dept-dialog.vue                                                    |  183 
 rsf-design/src/api/stock-transfer.js                                                                        |   87 
 rsf-design/src/api/loc-area-rela.js                                                                         |  157 
 rsf-design/src/views/system/flow-step-instance/flowStepInstancePage.helpers.js                              |  162 
 rsf-design/src/store/modules/worktab.js                                                                     |   39 
 rsf-design/src/utils/sys/requestGuard.js                                                                    |   66 
 rsf-design/src/views/system/ai-observe/aiObservePage.helpers.js                                             |  165 
 rsf-design/src/views/dashboard/console/index.vue                                                            |  542 
 rsf-design/src/views/system/host/hostPage.helpers.js                                                        |   89 
 rsf-design/src/views/manager/loc-preview/modules/loc-preview-detail-drawer.vue                              |   52 
 rsf-design/src/views/basic-info/companys/companysPage.helpers.js                                            |  252 
 rsf-design/src/views/basic-info/loc-area/index.vue                                                          |  345 
 rsf-design/src/views/orders/asn-order-item/index.vue                                                        |  386 
 rsf-design/src/views/system/operation-record/modules/operation-record-detail-drawer.vue                     |   53 
 rsf-design/src/views/basic-info/warehouse/index.vue                                                         |  325 
 rsf-design/src/views/basic-info/device-site/modules/device-site-init-dialog.vue                             |  212 
 rsf-design/src/views/basic-info/companys/index.vue                                                          |  355 
 rsf-design/src/views/orders/asn-order-item/asnOrderItemTable.columns.js                                     |   66 
 rsf-design/src/views/orders/check/checkOrderPage.helpers.js                                                 |  135 
 rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue                                             |   75 
 rsf-design/src/views/system/tenant/modules/tenant-detail-drawer.vue                                         |   39 
 rsf-design/src/views/orders/purchase/purchaseTable.columns.js                                               |  179 
 rsf-design/src/views/system/task-instance/taskInstancePage.helpers.js                                       |  204 
 rsf-design/src/views/orders/out-stock/index.vue                                                             |  318 
 rsf-design/src/views/basic-info/bas-station/modules/bas-station-detail-drawer.vue                           |  130 
 rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-dialog.vue                                |  198 
 rsf-design/src/views/orders/preparation/preparationPage.helpers.js                                          |  250 
 rsf-design/src/api/out-statistic-item.js                                                                    |   43 
 rsf-design/src/views/system/flow-step-instance/index.vue                                                    |  117 
 rsf-design/src/views/basic-info/loc-type/modules/loc-type-dialog.vue                                        |  161 
 rsf-design/src/views/orders/check-diff-item/checkDiffItemPage.helpers.js                                    |   91 
 rsf-design/src/views/system/flow-instance/index.vue                                                         |  117 
 rsf-design/src/views/work/check-out-bound/checkOutBoundPage.helpers.js                                      |  178 
 rsf-design/src/views/manager/task-item-log/taskItemLogPage.helpers.js                                       |  249 
 rsf-design/src/views/basic-info/contract/contractPage.helpers.js                                            |  162 
 rsf-design/src/views/orders/wait-pakin-item/waitPakinItemTable.columns.js                                   |  145 
 rsf-design/src/views/manager/in-statistic-item/inStatisticItemPage.helpers.js                               |   86 
 rsf-design/src/views/system/flow-step-log/flowStepLogPage.helpers.js                                        |  121 
 rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaPage.helpers.js                                    |  193 
 rsf-design/src/views/basic-info/task-path-template/taskPathTemplatePage.helpers.js                          |  282 
 rsf-design/src/views/orders/check/index.vue                                                                 |  249 
 rsf-design/src/views/system/flow-instance/flowInstanceTable.columns.js                                      |   35 
 rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js                                 |  111 
 rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplateTable.columns.js                   |   48 
 rsf-design/src/views/system/task-instance-node/taskInstanceNodePage.helpers.js                              |  176 
 rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue                        |  307 
 rsf-design/src/views/manager/loc-item/modules/loc-item-detail-drawer.vue                                    |   71 
 rsf-design/src/views/work/check-out-bound/index.vue                                                         |  310 
 rsf-design/src/views/basic-info/matnr-group/index.vue                                                       |  501 
 rsf-design/src/views/system/dict-type/dictTypeTable.columns.js                                              |   74 
 rsf-design/src/api/task.js                                                                                  |   95 
 rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaTable.columns.js                                   |  115 
 rsf-design/src/api/check-order.js                                                                           |  102 
 rsf-design/src/views/abnormal/abnormalPage.helpers.js                                                       |  174 
 rsf-design/src/views/manager/loc-preview/index.vue                                                          |  333 
 rsf-design/src/views/manager/menu-pda/modules/menu-pda-dialog.vue                                           |  273 
 rsf-design/src/api/asn-order.js                                                                             |  129 
 rsf-design/src/api/device-site.js                                                                           |  213 
 rsf-design/src/views/manager/wave-rule/modules/wave-rule-dialog.vue                                         |  153 
 rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergeTable.columns.js              |  200 
 rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-dialog.vue                      |  261 
 rsf-design/src/views/system/dict-type/modules/dict-type-detail-drawer.vue                                   |   41 
 rsf-design/src/api/out-bound.js                                                                             |  103 
 rsf-design/src/api/flow-step-template.js                                                                    |   69 
 rsf-design/src/api/task-path-template-node.js                                                               |  175 
 rsf-design/src/views/orders/purchase/modules/purchase-dialog.vue                                            |  261 
 rsf-design/src/views/system/ai-param/modules/ai-param-dialog.vue                                            |  325 
 rsf-design/src/views/orders/wait-pakin-log/waitPakinLogPage.helpers.js                                      |  165 
 rsf-design/src/views/manager/qly-inspect/qlyInspectTable.columns.js                                         |   77 
 rsf-design/src/views/system/flow-instance/flowInstancePage.helpers.js                                       |  175 
 rsf-design/src/views/statistics/in-statistic/modules/in-statistic-detail-drawer.vue                         |   48 
 rsf-design/src/components/core/layouts/art-breadcrumb/index.vue                                             |    4 
 rsf-design/src/views/manager/loc-dead-report/locDeadReportPage.helpers.js                                   |  198 
 rsf-design/src/views/statistics/out-statistic/outStatisticTable.columns.js                                  |   91 
 rsf-design/src/views/manager/qly-inspect/modules/qly-inspect-items-drawer.vue                               |   44 
 rsf-design/src/views/statistics/out-statistic/modules/out-statistic-detail-drawer.vue                       |   57 
 rsf-design/src/views/basic-info/warehouse/modules/warehouse-detail-drawer.vue                               |   57 
 rsf-design/src/api/task-instance.js                                                                         |   69 
 rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-detail-drawer.vue                       |   63 
 rsf-design/src/views/statistics/in-statistic-item/inStatisticItemPage.helpers.js                            |   86 
 rsf-design/src/views/stock/stock-transfer/modules/stock-transfer-detail-drawer.vue                          |   78 
 rsf-design/src/views/system/ai-prompt/modules/ai-prompt-dialog.vue                                          |  303 
 rsf-design/src/views/system/task-instance/index.vue                                                         |  242 
 rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemTable.columns.js                          |  113 
 rsf-design/src/views/statistics/out-statistic-item/index.vue                                                |  148 
 rsf-design/src/api/loc-type.js                                                                              |  139 
 rsf-design/src/views/manager/freeze/index.vue                                                               |  227 
 rsf-design/src/api/loc-revise.js                                                                            |  139 
 rsf-design/src/views/manager/revise-log/reviseLogPage.helpers.js                                            |  151 
 rsf-design/src/views/system/serial-rule/modules/serial-rule-detail-drawer.vue                               |   45 
 rsf-design/src/views/basic-info/device-site/index.vue                                                       |  439 
 rsf-design/src/views/system/serial-rule-item/serialRuleItemPage.helpers.js                                  |  206 
 rsf-design/src/views/dashboard/console/consolePage.helpers.js                                               |  164 
 rsf-design/src/views/system/flow-step-template/index.vue                                                    |  185 
 rsf-design/src/views/orders/wait-pakin/waitPakinTable.columns.js                                            |  122 
 rsf-design/src/views/orders/wave/wavePage.helpers.js                                                        |  312 
 rsf-design/src/views/system/task-instance-node/taskInstanceNodeTable.columns.js                             |   97 
 rsf-design/src/api/stock-item.js                                                                            |   82 
 rsf-design/src/views/system/subsystem-flow-template/modules/subsystem-flow-template-detail-drawer.vue       |   33 
 rsf-design/src/views/basic-info/bas-container/modules/bas-container-dialog.vue                              |  171 
 rsf-design/src/views/basic-info/device-bind/deviceBindTable.columns.js                                      |  141 
 rsf-design/src/views/system/flow-step-log/modules/flow-step-log-detail-drawer.vue                           |   39 
 rsf-design/src/views/system/host/index.vue                                                                  |  210 
 rsf-design/src/views/system/ai-mcp-mount/aiMcpMountPage.helpers.js                                          |  122 
 rsf-design/src/views/orders/purchase/index.vue                                                              |  483 
 rsf-design/src/views/basic-info/loc-area-rela/index.vue                                                     |  361 
 rsf-design/src/views/stock/warehouse-areas-item/index.vue                                                   |  482 
 rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-dialog.vue                            |  194 
 rsf-design/src/views/manager/in-statistic-item/inStatisticItemTable.columns.js                              |  117 
 rsf-design/src/views/system/fields/index.vue                                                                |  233 
 rsf-design/src/views/orders/wave/index.vue                                                                  |  416 
 rsf-design/src/views/system/serial-rule-item/serialRuleItemTable.columns.js                                 |  103 
 rsf-design/src/views/manager/freeze/freezePage.helpers.js                                                   |   98 
 rsf-design/src/views/system/serial-rule/modules/serial-rule-dialog.vue                                      |  195 
 rsf-design/src/api/purchase-item.js                                                                         |   95 
 rsf-design/src/api/subsystem-flow-template.js                                                               |   69 
 rsf-design/src/views/stock/warehouse-stock/warehouseStockPage.helpers.js                                    |  186 
 rsf-design/src/views/system/dict-type/modules/dict-type-dialog.vue                                          |  154 
 rsf-design/src/views/system/menu/menuTable.columns.js                                                       |  134 
 rsf-design/src/api/asn-order-item.js                                                                        |   78 
 rsf-design/src/views/orders/transfer/transferTable.columns.js                                               |  199 
 rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogTable.columns.js                            |  131 
 rsf-design/src/views/manager/task-item-log/modules/task-item-log-detail-drawer.vue                          |   72 
 rsf-design/src/views/orders/check-diff-item/checkDiffItemTable.columns.js                                   |  108 
 rsf-design/src/views/orders/check-diff/checkDiffPage.helpers.js                                             |  124 
 rsf-design/src/views/orders/check-diff-item/index.vue                                                       |  224 
 rsf-design/src/store/modules/menu.js                                                                        |    3 
 rsf-design/src/views/manager/loc-dead-report/index.vue                                                      |  402 
 rsf-design/src/locales/langs/zh.json                                                                        |   15 
 rsf-design/src/views/basic-info/companys/modules/companys-detail-drawer.vue                                 |   65 
 rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-site-dialog.vue                                   |  189 
 rsf-design/src/views/orders/out-stock/outStockTable.columns.js                                              |  113 
 rsf-design/src/views/orders/wait-pakin/waitPakinPage.helpers.js                                             |  253 
 rsf-design/src/views/statistics/in-statistic/inStatisticTable.columns.js                                    |   86 
 rsf-design/src/views/manager/stock/stockPage.helpers.js                                                     |  103 
 rsf-design/src/views/reports/statistic-count/statisticCountPage.helpers.js                                  |   55 
 rsf-design/src/views/orders/wait-pakin-item-log/modules/wait-pakin-item-log-detail-drawer.vue               |   70 
 rsf-design/src/api/task-log.js                                                                              |   69 
 rsf-design/src/views/statistics/in-statistic-item/modules/in-statistic-item-detail-drawer.vue               |   53 
 rsf-design/src/views/basic-info/loc-area/locAreaTable.columns.js                                            |  108 
 rsf-design/src/views/basic-info/bas-station/index.vue                                                       |  472 
 rsf-design/src/views/orders/transfer/index.vue                                                              |  474 
 rsf-design/src/api/flow-step-instance.js                                                                    |   69 
 rsf-design/src/api/transfer.js                                                                              |  137 
 rsf-design/src/views/system/task-instance/taskInstanceTable.columns.js                                      |   97 
 rsf-design/src/views/orders/check-diff/index.vue                                                            |  281 
 rsf-design/src/api/delivery.js                                                                              |  200 
 rsf-design/src/views/system/menu/menuPage.helpers.js                                                        |  164 
 rsf-design/src/views/basic-info/loc-area-mat/locAreaMatPage.helpers.js                                      |  449 
 rsf-design/src/views/manager/qly-inspect/qlyInspectPage.helpers.js                                          |  125 
 rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplatePage.helpers.js                    |  163 
 rsf-design/src/views/reports/statistic-count/index.vue                                                      |  167 
 rsf-design/src/api/task-path-template-merge.js                                                              |  298 
 rsf-design/src/api/freeze.js                                                                                |   45 
 rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-dialog.vue                    |  294 
 rsf-design/src/views/manager/task/index.vue                                                                 |  352 
 rsf-design/src/api/statistic-count.js                                                                       |   33 
 rsf-design/src/views/manager/task-log/index.vue                                                             |  250 
 rsf-design/src/views/manager/task/modules/task-detail-drawer.vue                                            |   59 
 rsf-design/src/views/system/config/index.vue                                                                |  226 
 rsf-design/src/views/manager/task-log/modules/task-log-detail-drawer.vue                                    |   54 
 rsf-design/src/api/task-item-log.js                                                                         |   69 
 rsf-design/src/views/basic-info/bas-container/basContainerTable.columns.js                                  |  136 
 rsf-design/src/views/statistics/in-statistic-item/index.vue                                                 |   86 
 rsf-design/src/views/system/tenant/tenantPage.helpers.js                                                    |  148 
 rsf-design/src/views/work/check-out-bound/modules/check-out-bound-detail-drawer.vue                         |   65 
 rsf-design/src/views/manager/loc-item/locItemPage.helpers.js                                                |  169 
 rsf-design/src/api/check-diff.js                                                                            |  109 
 rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-matnr-dialog.vue                     |  212 
 rsf-design/src/views/statistics/out-statistic/outStatisticPage.helpers.js                                   |  116 
 rsf-design/src/views/system/flow-step-instance/modules/flow-step-instance-detail-drawer.vue                 |   40 
 rsf-design/src/views/system/serial-rule/serialRuleTable.columns.js                                          |   92 
 rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-dialog.vue          |  283 
 rsf-design/src/views/manager/loc-revise/locReviseTable.columns.js                                           |  114 
 rsf-design/src/views/basic-info/contract/index.vue                                                          |  316 
 rsf-design/src/views/manager/task-item-log/index.vue                                                        |  389 
 rsf-design/src/router/core/RouteRegistry.js                                                                 |   34 
 rsf-design/src/views/orders/wave-item/waveItemTable.columns.js                                              |  180 
 rsf-design/src/views/system/config/modules/config-dialog.vue                                                |  181 
 rsf-design/src/api/out-statistic.js                                                                         |   43 
 rsf-design/src/views/orders/wait-pakin/index.vue                                                            |  381 
 rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-dialog.vue                              |  152 
 rsf-design/src/views/manager/stock-item/modules/stock-item-detail-drawer.vue                                |   82 
 rsf-design/src/api/serial-rule-item.js                                                                      |  157 
 rsf-design/src/views/system/ai-observe/aiObserveTable.columns.js                                            |   78 
 rsf-design/src/views/system/ai-observe/modules/ai-observe-detail-drawer.vue                                 |  110 
 rsf-design/src/views/system/task-instance/modules/task-instance-detail-drawer.vue                           |   45 
 rsf-design/src/views/manager/revise-log-item/modules/revise-log-item-detail-drawer.vue                      |   48 
 rsf-design/src/views/orders/out-stock-item/modules/out-stock-item-detail-drawer.vue                         |   87 
 rsf-design/src/api/qly-ispt-item.js                                                                         |  110 
 rsf-design/src/views/orders/asn-order-item-log/asnOrderItemLogPage.helpers.js                               |  169 
 rsf-design/src/views/system/flow-step-template/modules/flow-step-template-detail-drawer.vue                 |   33 
 rsf-design/src/views/orders/transfer-item/transferItemPage.helpers.js                                       |  205 
 rsf-design/src/views/orders/preparation/preparationTable.columns.js                                         |   55 
 rsf-design/src/views/orders/asn-order-log/index.vue                                                         |  468 
 .tmp-menu.json                                                                                              |  931 
 rsf-design/src/views/orders/transfer-item/index.vue                                                         |  429 
 rsf-design/src/views/basic-info/loc-type/locTypePage.helpers.js                                             |  175 
 rsf-design/src/views/manager/freeze/modules/freeze-detail-drawer.vue                                        |   39 
 rsf-design/src/views/system/host/modules/host-detail-drawer.vue                                             |   37 
 rsf-design/src/views/system/ai-param/aiParamPage.helpers.js                                                 |  225 
 rsf-design/src/components/biz/list-export-print/index.vue                                                   |   11 
 rsf-design/src/api/in-statistic-item.js                                                                     |   49 
 rsf-design/src/api/task-item.js                                                                             |   69 
 rsf-design/src/views/manager/stock-item/stockItemTable.columns.js                                           |  177 
 rsf-design/src/api/flow-instance.js                                                                         |   69 
 rsf-design/src/views/orders/delivery-item/modules/delivery-item-detail-drawer.vue                           |   74 
 rsf-design/src/api/wait-pakin-log.js                                                                        |   85 
 rsf-design/src/views/manager/loc-preview/locPreviewTable.columns.js                                         |   77 
 rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue                                     |  178 
 rsf-design/src/views/work/out-bound/outBoundTable.columns.js                                                |  203 
 rsf-design/src/views/system/ai-prompt/index.vue                                                             |  316 
 rsf-design/src/views/system/common/usePrintExportPage.js                                                    |   20 
 rsf-design/src/views/system/config/modules/config-detail-drawer.vue                                         |   42 
 rsf-design/src/views/orders/check-item/modules/check-order-item-detail-drawer.vue                           |   43 
 rsf-design/src/views/system/operation-record/index.vue                                                      |  281 
 rsf-design/src/views/basic-info/companys/companysTable.columns.js                                           |  102 
 rsf-design/src/views/basic-info/loc-type/index.vue                                                          |  342 
 rsf-design/src/views/manager/stock/stockTable.columns.js                                                    |   86 
 rsf-design/src/views/statistics/out-statistic-item/outStatisticItemTable.columns.js                         |  133 
 rsf-design/src/api/out-stock-item.js                                                                        |   97 
 rsf-design/src/views/orders/check-diff/checkDiffTable.columns.js                                            |   69 
 rsf-design/src/views/orders/check/modules/check-order-detail-drawer.vue                                     |   71 
 rsf-design/src/views/manager/loc-preview/locPreviewPage.helpers.js                                          |  127 
 rsf-design/src/views/basic-info/device-bind/deviceBindPage.helpers.js                                       |  314 
 rsf-design/src/views/orders/purchase/purchasePage.helpers.js                                                |  314 
 rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemPage.helpers.js                           |  201 
 rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue                                            |  184 
 rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodeTable.columns.js                |  145 
 rsf-design/src/views/work/check-out-bound/checkOutBoundTable.columns.js                                     |   94 
 rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue                 |   69 
 rsf-design/src/views/orders/check-diff-item/modules/check-diff-item-detail-drawer.vue                       |   42 
 rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-loc-dialog.vue                       |  199 
 rsf-design/src/api/wait-pakin-item-log.js                                                                   |  104 
 rsf-design/src/api/contract.js                                                                              |  112 
 rsf-design/src/views/statistics/in-statistic/index.vue                                                      |   91 
 rsf-design/src/views/stock/stock-transfer/stockTransferTable.columns.js                                     |   83 
 rsf-design/src/views/manager/loc-revise/locRevisePage.helpers.js                                            |  233 
 rsf-design/src/views/manager/stock-item/index.vue                                                           |  492 
 rsf-design/src/views/system/fields-item/index.vue                                                           |  232 
 rsf-design/src/views/orders/preparation-item/index.vue                                                      |  300 
 rsf-design/src/views/basic-info/device-bind/index.vue                                                       |  434 
 rsf-design/src/views/basic-info/bas-station/modules/bas-station-dialog.vue                                  |  292 
 rsf-design/src/views/system/dept/index.vue                                                                  |  211 
 rsf-design/src/api/loc-dead-report.js                                                                       |   69 
 rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-detail-drawer.vue                                 |   78 
 rsf-design/src/views/system/tenant/tenantTable.columns.js                                                   |   80 
 rsf-design/src/views/manager/revise-log-item/index.vue                                                      |  174 
 rsf-design/src/views/basic-info/task-path-template/index.vue                                                |  428 
 rsf-design/src/views/system/dept/deptPage.helpers.js                                                        |  137 
 rsf-design/src/api/task-path-template.js                                                                    |  197 
 rsf-design/src/views/basic-info/bas-station-area/index.vue                                                  |  459 
 rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-histories-drawer.vue                     |   48 
 rsf-design/src/views/manager/in-statistic-item/index.vue                                                    |  157 
 rsf-design/src/views/stock/warehouse-areas-item/modules/warehouse-ispt-result-drawer.vue                    |   44 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java              |  134 
 rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js                                        |  306 
 rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js                                       |  531 
 rsf-design/src/views/system/ai-mcp-mount/index.vue                                                          |  347 
 rsf-design/src/api/loc-item.js                                                                              |   81 
 rsf-design/src/views/orders/transfer-item/modules/transfer-item-detail-drawer.vue                           |   78 
 rsf-design/src/views/manager/task-item/taskItemTable.columns.js                                             |  109 
 rsf-design/src/views/orders/wait-pakin-item-log/index.vue                                                   |  329 
 rsf-design/src/views/orders/check-diff/modules/check-diff-detail-drawer.vue                                 |   61 
 rsf-design/src/api/task-instance-node.js                                                                    |   69 
 rsf-design/src/views/manager/wave-rule/index.vue                                                            |  248 
 rsf-design/src/api/bas-station-area.js                                                                      |  218 
 rsf-design/src/api/check-out-bound.js                                                                       |   52 
 rsf-design/src/views/system/operation-record/operationRecordPage.helpers.js                                 |  123 
 rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue                        |  177 
 rsf-design/src/views/orders/out-stock/outStockPage.helpers.js                                               |  211 
 rsf-design/src/api/transfer-item.js                                                                         |  125 
 rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogPage.helpers.js                             |  167 
 rsf-design/src/views/orders/purchase-item/purchaseItemTable.columns.js                                      |  161 
 rsf-design/src/views/basic-info/device-bind/modules/device-bind-dialog.vue                                  |  235 
 rsf-design/src/views/orders/wave/waveTable.columns.js                                                       |  179 
 rsf-design/src/views/abnormal/index.vue                                                                     |  153 
 rsf-design/src/views/orders/purchase-item/modules/purchase-item-detail-drawer.vue                           |   72 
 rsf-design/src/views/system/ai-observe/index.vue                                                            |  279 
 rsf-design/src/views/manager/task-log/taskLogTable.columns.js                                               |  109 
 rsf-design/src/views/manager/loc-revise/modules/loc-revise-dialog.vue                                       |  168 
 rsf-design/src/views/manager/revise-log/index.vue                                                           |  266 
 rsf-design/src/api/revise-log-item.js                                                                       |   61 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/StockStatisticController.java            |   26 
 rsf-design/src/views/statistics/out-statistic-item/outStatisticItemPage.helpers.js                          |   89 
 rsf-design/src/views/manager/qly-inspect/index.vue                                                          |  416 
 rsf-design/src/views/system/ai-param/modules/ai-param-runtime-summary.vue                                   |  142 
 rsf-design/src/views/basic-info/warehouse/warehousePage.helpers.js                                          |  182 
 rsf-design/src/router/adapters/backendMenuAdapter.js                                                        |   63 
 rsf-design/src/views/system/fields/modules/fields-detail-drawer.vue                                         |   42 
 rsf-design/src/router/routes/staticRoutes.js                                                                |   89 
 rsf-design/src/views/work/out-bound/index.vue                                                               |  368 
 rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-relation-panel.vue                        |  435 
 rsf-design/src/api/warehouse-stock.js                                                                       |   60 
 rsf-design/src/views/orders/wait-pakin-item/modules/wait-pakin-item-detail-drawer.vue                       |  102 
 rsf-design/src/views/orders/wave-item/waveItemPage.helpers.js                                               |  165 
 rsf-design/src/views/system/host/hostTable.columns.js                                                       |   68 
 rsf-design/src/views/orders/check-item/checkOrderItemTable.columns.js                                       |   80 
 rsf-design/src/views/system/flow-step-template/flowStepTemplateTable.columns.js                             |   48 
 rsf-design/src/views/basic-info/bas-container/index.vue                                                     |  400 
 rsf-design/src/views/basic-info/device-site/modules/device-site-detail-drawer.vue                           |   62 
 rsf-design/src/views/manager/revise-log/modules/revise-log-detail-drawer.vue                                |   74 
 rsf-design/src/views/system/fields/fieldsPage.helpers.js                                                    |  134 
 rsf-design/src/views/system/flow-step-log/index.vue                                                         |  244 
 rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-mount-dialog.vue                                    |  292 
 rsf-design/src/views/system/fields/modules/fields-dialog.vue                                                |  166 
 rsf-design/src/views/manager/loc-dead-report/locDeadReportTable.columns.js                                  |  150 
 rsf-design/src/views/system/flow-step-log/flowStepLogTable.columns.js                                       |   75 
 rsf-design/src/views/basic-info/device-site/deviceSitePage.helpers.js                                       |  442 
 rsf-design/src/views/stock/stock-transfer/index.vue                                                         |  312 
 rsf-design/src/views/system/ai-prompt/aiPromptPage.helpers.js                                               |  182 
 rsf-design/src/views/orders/purchase-item/index.vue                                                         |  370 
 rsf-design/package.json                                                                                     |    2 
 rsf-design/src/api/out-stock.js                                                                             |   75 
 rsf-design/src/views/basic-info/task-path-template-merge/index.vue                                          |  323 
 rsf-design/src/router/core/RouteTransformer.js                                                              |    2 
 rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue                               |   67 
 rsf-design/src/api/companys.js                                                                              |  105 
 rsf-design/src/api/wait-pakin.js                                                                            |  111 
 rsf-design/src/plugins/iconify.collections.js                                                               |   66 
 rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue                                   |   73 
 rsf-design/src/api/preparation.js                                                                           |   90 
 rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js                              |  474 
 rsf-design/src/api/in-statistic.js                                                                          |   43 
 rsf-design/src/views/basic-info/loc-type/locTypeTable.columns.js                                            |  115 
 rsf-design/src/views/basic-info/task-path-template/taskPathTemplateTable.columns.js                         |  207 
 rsf-design/src/views/statistics/in-statistic-item/inStatisticItemTable.columns.js                           |  128 
 rsf-design/src/views/basic-info/device-bind/modules/device-bind-detail-drawer.vue                           |   79 
 rsf-design/src/views/system/fields-item/fieldsItemPage.helpers.js                                           |  136 
 rsf-design/src/views/manager/qly-ispt-item/modules/qly-ispt-item-detail-drawer.vue                          |   49 
 rsf-design/src/views/orders/wait-pakin-log/modules/wait-pakin-log-detail-drawer.vue                         |   59 
 rsf-design/src/views/manager/stock-item/modules/stock-item-dialog.vue                                       |  450 
 rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaTable.columns.js                            |  138 
 rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-dialog.vue        |  320 
 rsf-design/src/views/manager/menu-pda/index.vue                                                             |  222 
 rsf-design/src/router/core/RouteValidator.js                                                                |   10 
 rsf-design/src/views/manager/loc-revise/index.vue                                                           |  542 
 rsf-design/src/views/manager/menu-pda/menuPdaPage.helpers.js                                                |  124 
 rsf-design/src/api/revise-log.js                                                                            |   61 
 rsf-design/src/api/qly-inspect.js                                                                           |   76 
 rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodePage.helpers.js                 |  311 
 rsf-design/src/views/orders/out-stock-item/outStockItemTable.columns.js                                     |  119 
 rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-detail-drawer.vue             |   76 
 rsf-design/src/views/orders/wait-pakin-log/index.vue                                                        |  339 
 rsf-design/src/views/stock/warehouse-stock/index.vue                                                        |  503 
 rsf-design/src/views/manager/revise-log-item/reviseLogItemPage.helpers.js                                   |  128 
 rsf-design/src/views/manager/in-statistic-item/modules/in-statistic-item-detail-drawer.vue                  |   58 
 rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-tools-drawer.vue                                    |  190 
 rsf-design/src/api/flow-step-log.js                                                                         |   69 
 rsf-design/src/views/manager/loc-item/index.vue                                                             |  234 
 rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue                             |  343 
 rsf-design/src/views/basic-info/loc-area/locAreaPage.helpers.js                                             |  202 
 rsf-design/src/views/orders/purchase-item/purchaseItemPage.helpers.js                                       |  183 
 rsf-design/src/views/manager/wave-rule/waveRulePage.helpers.js                                              |  114 
 rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue               |   78 
 rsf-design/src/views/manager/loc-item/locItemTable.columns.js                                               |  143 
 rsf-design/src/views/system/fields-item/fieldsItemTable.columns.js                                          |   80 
 rsf-design/src/views/manager/task/taskTable.columns.js                                                      |   93 
 rsf-design/src/api/wait-pakin-item.js                                                                       |  105 
 rsf-design/src/views/basic-info/loc-area/modules/loc-area-detail-drawer.vue                                 |   56 
 rsf-design/src/views/manager/stock-item/stockItemPage.helpers.js                                            |  342 
 rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js                                      |  136 
 rsf-design/src/views/work/out-bound/modules/out-bound-detail-drawer.vue                                     |   41 
 rsf-design/src/views/basic-info/contract/contractTable.columns.js                                           |   99 
 rsf-design/src/views/orders/transfer/transferPage.helpers.js                                                |  347 
 rsf-design/src/views/orders/asn-order-item-log/index.vue                                                    |  256 
 rsf-design/src/views/orders/delivery/index.vue                                                              |  387 
 rsf-design/src/views/system/role/modules/role-permission-dialog.vue                                         |   17 
 rsf-design/src/views/basic-info/warehouse/modules/warehouse-dialog.vue                                      |  167 
 rsf-design/src/views/system/host/modules/host-dialog.vue                                                    |  130 
 rsf-design/src/views/system/menu/index.vue                                                                  |  323 
 rsf-design/src/views/system/subsystem-flow-template/index.vue                                               |  185 
 rsf-design/src/views/basic-info/contract/modules/contract-detail-drawer.vue                                 |   55 
 rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergePage.helpers.js               |  484 
 rsf-design/src/api/stock.js                                                                                 |   66 
 rsf-design/src/views/system/config/configTable.columns.js                                                   |   80 
 rsf-design/src/views/system/tenant/modules/tenant-edit-dialog.vue                                           |  149 
 rsf-design/src/api/matnr-group.js                                                                           |  154 
 rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemPage.helpers.js                                       |  167 
 rsf-design/src/views/orders/out-stock-item/index.vue                                                        |  217 
 /dev/null                                                                                                   |   19 
 rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js                                      |  174 
 rsf-design/src/views/system/fields/fieldsTable.columns.js                                                   |   73 
 rsf-design/src/views/orders/delivery-item/index.vue                                                         |  178 
 rsf-design/src/api/dashboard.js                                                                             |   44 
 rsf-design/src/api/warehouse.js                                                                             |  140 
 rsf-design/src/views/basic-info/loc-area-mat/locAreaMatTable.columns.js                                     |  125 
 rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-detail-drawer.vue                           |   58 
 500 files changed, 82,337 insertions(+), 632 deletions(-)

diff --git a/.tmp-menu.json b/.tmp-menu.json
new file mode 100644
index 0000000..9ee9b13
--- /dev/null
+++ b/.tmp-menu.json
@@ -0,0 +1,931 @@
+{
+  "msg": "Success",
+  "code": 200,
+  "data": [
+    {
+      "id": 5318,
+      "name": "AI绠$悊涓績",
+      "parentId": 0,
+      "path": "",
+      "route": "/AI",
+      "component": null,
+      "type": 0,
+      "icon": "ri:command-fill",
+      "sort": 0,
+      "children": [
+        {
+          "id": 422,
+          "name": "menu.aiParam",
+          "parentId": 5318,
+          "path": "5318",
+          "route": "/system/aiParam",
+          "component": "aiParam",
+          "type": 0,
+          "icon": "ri:command-fill",
+          "sort": 9,
+          "children": null
+        },
+        {
+          "id": 428,
+          "name": "menu.aiPrompt",
+          "parentId": 5318,
+          "path": "5318",
+          "route": "/system/aiPrompt",
+          "component": "aiPrompt",
+          "type": 0,
+          "icon": "ri:function-line",
+          "sort": 10,
+          "children": null
+        },
+        {
+          "id": 430,
+          "name": "menu.aiCallLog",
+          "parentId": 5318,
+          "path": "5318",
+          "route": "/system/aiCallLog",
+          "component": "aiCallLog",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 12,
+          "children": null
+        },
+        {
+          "id": 5311,
+          "name": "menu.aiMcpMount",
+          "parentId": 5318,
+          "path": "5318",
+          "route": "/system/aiMcpMount",
+          "component": "aiMcpMount",
+          "type": 0,
+          "icon": "ri:command-fill",
+          "sort": 13,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 67,
+      "name": "menu.basicInfo",
+      "parentId": 0,
+      "path": "",
+      "route": "/basic",
+      "component": null,
+      "type": 0,
+      "icon": "ri:home-smile-2-line",
+      "sort": 1,
+      "children": [
+        {
+          "id": 417,
+          "name": "menu.basStationArea",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/basStationArea",
+          "component": "basStationArea",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 68,
+          "name": "menu.warehouse",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/warehouse",
+          "component": "warehouse",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 73,
+          "name": "menu.warehouseAreas",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/warehouseAreas",
+          "component": "warehouseAreas",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 78,
+          "name": "menu.loc",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/loc",
+          "component": "loc",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 62,
+          "name": "menu.matnrGroup",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/matnrGroup",
+          "component": "matnrGroup",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 4,
+          "children": null
+        },
+        {
+          "id": 57,
+          "name": "menu.matnr",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/matnr",
+          "component": "matnr",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 5,
+          "children": null
+        },
+        {
+          "id": 331,
+          "name": "menu.basContainer",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/basContainer",
+          "component": "basContainer",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 6,
+          "children": null
+        },
+        {
+          "id": 325,
+          "name": "menu.basStation",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/basStation",
+          "component": "basStation",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 7,
+          "children": null
+        },
+        {
+          "id": 302,
+          "name": "menu.deviceBind",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/deviceBind",
+          "component": "deviceBind",
+          "type": 0,
+          "icon": "ri:command-fill",
+          "sort": 8,
+          "children": null
+        },
+        {
+          "id": 200,
+          "name": "menu.deviceSite",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/deviceSite",
+          "component": "deviceSite",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 9,
+          "children": null
+        },
+        {
+          "id": 118,
+          "name": "menu.companys",
+          "parentId": 67,
+          "path": "67",
+          "route": "/manager/companys",
+          "component": "companys",
+          "type": 0,
+          "icon": "ri:group-line",
+          "sort": 10,
+          "children": null
+        },
+        {
+          "id": 405,
+          "name": "menu.taskPathTemplate",
+          "parentId": 67,
+          "path": "67",
+          "route": "/taskPathTemplate",
+          "component": "taskPathTemplate",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 11,
+          "children": null
+        },
+        {
+          "id": 410,
+          "name": "menu.taskPathTemplateMerge",
+          "parentId": 67,
+          "path": "67",
+          "route": "/taskPathTemplateMerge",
+          "component": "taskPathTemplateMerge",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 12,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 300,
+      "name": "menu.inStockPoces",
+      "parentId": 0,
+      "path": "",
+      "route": "/stock/in",
+      "component": null,
+      "type": 0,
+      "icon": "ri:bill-line",
+      "sort": 2,
+      "children": [
+        {
+          "id": 134,
+          "name": "menu.asnOrder",
+          "parentId": 300,
+          "path": "300",
+          "route": "/manager/asnOrder",
+          "component": "asnOrder",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 400,
+          "name": "鍏ュ簱閫氱煡鍗曟槑缁�",
+          "parentId": 300,
+          "path": "300",
+          "route": "/manager/asnOrderItem",
+          "component": "asnOrderItem",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 216,
+          "name": "menu.asnOrderLog",
+          "parentId": 300,
+          "path": "300",
+          "route": "/manager/asnOrderLog",
+          "component": "asnOrderLog",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 4,
+          "children": null
+        },
+        {
+          "id": 205,
+          "name": "menu.waitPakin",
+          "parentId": 300,
+          "path": "300",
+          "route": "/manager/waitPakin",
+          "component": "waitPakin",
+          "type": 0,
+          "icon": "ri:function-line",
+          "sort": 5,
+          "children": null
+        },
+        {
+          "id": 246,
+          "name": "menu.waitPakinLog",
+          "parentId": 300,
+          "path": "300",
+          "route": "/manager/waitPakinLog",
+          "component": "waitPakinLog",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 6,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 301,
+      "name": "menu.outStockPoces",
+      "parentId": 0,
+      "path": "",
+      "route": "/stock/out",
+      "component": null,
+      "type": 0,
+      "icon": "ri:bill-line",
+      "sort": 3,
+      "children": [
+        {
+          "id": 290,
+          "name": "menu.outStock",
+          "parentId": 301,
+          "path": "301",
+          "route": "manager/outStock",
+          "component": "outStock",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 402,
+          "name": "鍑哄簱璁㈠崟鏄庣粏",
+          "parentId": 301,
+          "path": "301",
+          "route": "/manager/outStockItem",
+          "component": "outStockItem",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 387,
+          "name": "menu.preparation",
+          "parentId": 301,
+          "path": "301",
+          "route": "/manager/matPreparation",
+          "component": "preparation",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 4,
+          "children": null
+        },
+        {
+          "id": 307,
+          "name": "menu.wave",
+          "parentId": 301,
+          "path": "301",
+          "route": "/manager/wave",
+          "component": "wave",
+          "type": 0,
+          "icon": "ri:progress-2-line",
+          "sort": 6,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 389,
+      "name": "menu.transferPoces",
+      "parentId": 0,
+      "path": "",
+      "route": "/transfer",
+      "component": "",
+      "type": 0,
+      "icon": "ri:bill-line",
+      "sort": 4,
+      "children": [
+        {
+          "id": 356,
+          "name": "menu.transfer",
+          "parentId": 389,
+          "path": "389",
+          "route": "/manager/transfer",
+          "component": "transfer",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 0,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 322,
+      "name": "menu.tasks",
+      "parentId": 0,
+      "path": "",
+      "route": "/tasks",
+      "component": null,
+      "type": 0,
+      "icon": "ri:progress-2-line",
+      "sort": 5,
+      "children": [
+        {
+          "id": 336,
+          "name": "menu.outBound",
+          "parentId": 322,
+          "path": "322",
+          "route": "/manager/outBound",
+          "component": "outBound",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 338,
+          "name": "menu.stockTransfer",
+          "parentId": 322,
+          "path": "322",
+          "route": "/manager/stockTransfer",
+          "component": "stockTransfer",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 226,
+          "name": "menu.task",
+          "parentId": 322,
+          "path": "322",
+          "route": "/manager/task",
+          "component": "task",
+          "type": 0,
+          "icon": "ri:progress-2-line",
+          "sort": 5,
+          "children": null
+        },
+        {
+          "id": 236,
+          "name": "menu.taskLog",
+          "parentId": 322,
+          "path": "322",
+          "route": "/manager/taskLog",
+          "component": "taskLog",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 7,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 323,
+      "name": "menu.stockManage",
+      "parentId": 0,
+      "path": "",
+      "route": "/stock/manage",
+      "component": "manage",
+      "type": 0,
+      "icon": "ri:function-line",
+      "sort": 6,
+      "children": [
+        {
+          "id": 330,
+          "name": "menu.warehouseStock",
+          "parentId": 323,
+          "path": "323",
+          "route": "/warehouse/stock",
+          "component": "warehouseStock",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 361,
+          "name": "menu.locPreview",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/locPreview",
+          "component": "locPreview",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 172,
+          "name": "menu.warehouseAreasItem",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/warehouseAreasItem",
+          "component": "warehouseAreasItem",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 103,
+          "name": "menu.qlyInspect",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/qlyInspect",
+          "component": "qlyInspect",
+          "type": 0,
+          "icon": "ri:function-line",
+          "sort": 4,
+          "children": null
+        },
+        {
+          "id": 366,
+          "name": "menu.locRevise",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/locRevise",
+          "component": "locRevise",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 10,
+          "children": null
+        },
+        {
+          "id": 394,
+          "name": "menu.freeze",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/freeze",
+          "component": "freeze",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 11,
+          "children": null
+        },
+        {
+          "id": 265,
+          "name": "menu.stock",
+          "parentId": 323,
+          "path": "323",
+          "route": "/manager/stock",
+          "component": "stock",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 12,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 391,
+      "name": "menu.check",
+      "parentId": 0,
+      "path": "",
+      "route": "/check",
+      "component": "check",
+      "type": 0,
+      "icon": "ri:pie-chart-line",
+      "sort": 7,
+      "children": [
+        {
+          "id": 345,
+          "name": "menu.checkOrder",
+          "parentId": 391,
+          "path": "391",
+          "route": "/orders/check",
+          "component": "check",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 351,
+          "name": "menu.checkDiff",
+          "parentId": 391,
+          "path": "391",
+          "route": "/manager/checkDiff",
+          "component": "checkDiff",
+          "type": 0,
+          "icon": "ri:error-warning-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 337,
+          "name": "menu.checkOutBound",
+          "parentId": 391,
+          "path": "391",
+          "route": "/manager/checkOutBound",
+          "component": "checkOutBound",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 3,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 392,
+      "name": "menu.abnormal",
+      "parentId": 0,
+      "path": "",
+      "route": "/abnormal",
+      "component": "abnormal",
+      "type": 0,
+      "icon": "ri:error-warning-line",
+      "sort": 8,
+      "children": null
+    },
+    {
+      "id": 371,
+      "name": "menu.statisticReport",
+      "parentId": 0,
+      "path": "",
+      "route": "/manager/statisticReport",
+      "component": "",
+      "type": 0,
+      "icon": "ri:pie-chart-line",
+      "sort": 9,
+      "children": [
+        {
+          "id": 385,
+          "name": "menu.outStatisticItem",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/outStatisticItem",
+          "component": "outStatisticItem",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 386,
+          "name": "menu.statisticCount",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/statisticCount",
+          "component": "statisticCount",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 382,
+          "name": "menu.outStatistic",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/outStatistic",
+          "component": "outStatistic",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 384,
+          "name": "menu.inStatisticItem",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/inStatisticItem",
+          "component": "inStatisticItem",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 383,
+          "name": "menu.inStatistic",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/inStatistic",
+          "component": "inStatistic",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 0,
+          "children": null
+        },
+        {
+          "id": 376,
+          "name": "menu.locDeadReport",
+          "parentId": 371,
+          "path": "371",
+          "route": "/manager/locDeadReport",
+          "component": "locDeadReport",
+          "type": 0,
+          "icon": "ri:pie-chart-line",
+          "sort": 1,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 160,
+      "name": "menu.logs",
+      "parentId": 0,
+      "path": "",
+      "route": "/logs",
+      "component": null,
+      "type": 0,
+      "icon": "ri:bill-line",
+      "sort": 10,
+      "children": [
+        {
+          "id": 32,
+          "name": "menu.operation",
+          "parentId": 160,
+          "path": "160",
+          "route": "/system/operationRecord",
+          "component": "operationRecord",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 5,
+          "children": null
+        },
+        {
+          "id": 27,
+          "name": "menu.token",
+          "parentId": 160,
+          "path": "160",
+          "route": "/system/userLogin",
+          "component": "userLogin",
+          "type": 0,
+          "icon": "ri:bill-line",
+          "sort": 6,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 179,
+      "name": "menu.permissions",
+      "parentId": 0,
+      "path": "",
+      "route": "/permissions",
+      "component": null,
+      "type": 0,
+      "icon": "ri:user-settings-line",
+      "sort": 11,
+      "children": [
+        {
+          "id": 2,
+          "name": "menu.user",
+          "parentId": 179,
+          "path": "179",
+          "route": "/system/user",
+          "component": "user",
+          "type": 0,
+          "icon": "ri:user-3-line",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 7,
+          "name": "menu.role",
+          "parentId": 179,
+          "path": "179",
+          "route": "/system/role",
+          "component": "role",
+          "type": 0,
+          "icon": "ri:palette-line",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 22,
+          "name": "menu.department",
+          "parentId": 179,
+          "path": "179",
+          "route": "/system/dept",
+          "component": "dept",
+          "type": 0,
+          "icon": "ri:group-line",
+          "sort": 4,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 1,
+      "name": "menu.system",
+      "parentId": 0,
+      "path": "",
+      "route": "/system",
+      "component": null,
+      "type": 0,
+      "icon": "ri:settings-line",
+      "sort": 12,
+      "children": [
+        {
+          "id": 12,
+          "name": "menu.menu",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/menu",
+          "component": "menu",
+          "type": 0,
+          "icon": "ri:menu-2-fill",
+          "sort": 1,
+          "children": null
+        },
+        {
+          "id": 395,
+          "name": "menu.menuPda",
+          "parentId": 1,
+          "path": "1",
+          "route": "/manager/menuPda",
+          "component": "menuPda",
+          "type": 0,
+          "icon": "ri:menu-2-fill",
+          "sort": 2,
+          "children": null
+        },
+        {
+          "id": 123,
+          "name": "menu.serialRule",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/serialRule",
+          "component": "serialRule",
+          "type": 0,
+          "icon": "ri:function-line",
+          "sort": 3,
+          "children": null
+        },
+        {
+          "id": 340,
+          "name": "menu.waveRule",
+          "parentId": 1,
+          "path": "1",
+          "route": "/manager/waveRule",
+          "component": "waveRule",
+          "type": 0,
+          "icon": "ri:progress-2-line",
+          "sort": 4,
+          "children": null
+        },
+        {
+          "id": 108,
+          "name": "menu.dictType",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/dictType",
+          "component": "dictType",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 5,
+          "children": null
+        },
+        {
+          "id": 37,
+          "name": "menu.config",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/config",
+          "component": "config",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 7,
+          "children": null
+        },
+        {
+          "id": 162,
+          "name": "menu.fields",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/fields",
+          "component": "fields",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 10,
+          "children": null
+        },
+        {
+          "id": 167,
+          "name": "menu.fieldsItem",
+          "parentId": 1,
+          "path": "1",
+          "route": "/system/fieldsItem",
+          "component": "fieldsItem",
+          "type": 0,
+          "icon": "ri:book-2-line",
+          "sort": 11,
+          "children": null
+        }
+      ]
+    },
+    {
+      "id": 390,
+      "name": "menu.platform",
+      "parentId": 0,
+      "path": "",
+      "route": "/platform",
+      "component": "platform",
+      "type": 0,
+      "icon": "ri:command-fill",
+      "sort": 13,
+      "children": [
+        {
+          "id": 42,
+          "name": "menu.tenant",
+          "parentId": 390,
+          "path": "390",
+          "route": "/system/tenant",
+          "component": "tenant",
+          "type": 0,
+          "icon": "ri:command-fill",
+          "sort": 8,
+          "children": null
+        },
+        {
+          "id": 17,
+          "name": "menu.host",
+          "parentId": 390,
+          "path": "390",
+          "route": "/system/host",
+          "component": "host",
+          "type": 0,
+          "icon": "ri:map-pin-line",
+          "sort": 9,
+          "children": null
+        }
+      ]
+    }
+  ]
+}
diff --git a/rsf-design/package.json b/rsf-design/package.json
index f0db9dd..82a0385 100644
--- a/rsf-design/package.json
+++ b/rsf-design/package.json
@@ -7,7 +7,7 @@
     "pnpm": ">=8.8.0"
   },
   "scripts": {
-    "test": "node --test tests/auth-contract.test.mjs tests/backend-menu-adapter.test.mjs tests/clean-dev-helpers.test.mjs tests/iconify-local-minimal.test.mjs tests/iconify-local-prefixes.test.mjs tests/manual-chunks.test.mjs tests/repo-hygiene.test.mjs tests/system-manage-contract.test.mjs tests/system-role-scope-contract.test.mjs tests/system-user-page-contract.test.mjs tests/work-tab-icon-contract.test.mjs tests/worktab-icon-normalization-contract.test.mjs",
+    "test": "node --test tests/auth-contract.test.mjs tests/backend-menu-adapter.test.mjs tests/clean-dev-helpers.test.mjs tests/iconify-local-minimal.test.mjs tests/iconify-local-prefixes.test.mjs tests/manual-chunks.test.mjs tests/orders-out-stock-item-page-contract.test.mjs tests/repo-hygiene.test.mjs tests/system-manage-contract.test.mjs tests/system-role-scope-contract.test.mjs tests/system-user-page-contract.test.mjs tests/work-tab-icon-contract.test.mjs tests/worktab-icon-normalization-contract.test.mjs",
     "dev": "vite --open",
     "build": "vite build",
     "serve": "vite preview",
diff --git a/rsf-design/src/api/ai-config.js b/rsf-design/src/api/ai-config.js
new file mode 100644
index 0000000..909bb24
--- /dev/null
+++ b/rsf-design/src/api/ai-config.js
@@ -0,0 +1,225 @@
+import request from '@/utils/http'
+
+export function buildAiParamPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.providerType !== undefined ? { providerType: params.providerType } : {}),
+    ...(params.model !== undefined ? { model: params.model } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {})
+  }
+}
+
+function normalizeManyIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id ?? '').trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  return String(ids ?? '').trim()
+}
+
+function fetchAiParamPage(params = {}) {
+  return request.post({ url: '/aiParam/page', params: buildAiParamPageParams(params) })
+}
+
+function fetchGetAiParamDetail(id) {
+  return request.get({ url: `/aiParam/${id}` })
+}
+
+function fetchGetAiParamMany(ids) {
+  return request.post({ url: `/aiParam/many/${normalizeManyIds(ids)}` })
+}
+
+function fetchSaveAiParam(params) {
+  return request.post({ url: '/aiParam/save', params })
+}
+
+function fetchUpdateAiParam(params) {
+  return request.post({ url: '/aiParam/update', params })
+}
+
+function fetchDeleteAiParam(id) {
+  return request.post({ url: `/aiParam/remove/${id}` })
+}
+
+function fetchValidateAiParamDraft(params) {
+  return request.post({ url: '/aiParam/validate-draft', params })
+}
+
+function fetchSetAiParamDefault(id) {
+  return request.post({ url: `/aiParam/set-default/${id}` })
+}
+
+function fetchGetAiConfigSummary(promptCode) {
+  return request.get({
+    url: '/ai/config/summary',
+    params: promptCode ? { promptCode } : {}
+  })
+}
+
+async function fetchExportAiParamReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/aiParam/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function buildAiPromptPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.code !== undefined ? { code: params.code } : {}),
+    ...(params.scene !== undefined ? { scene: params.scene } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {})
+  }
+}
+
+function fetchAiPromptPage(params = {}) {
+  return request.post({ url: '/aiPrompt/page', params: buildAiPromptPageParams(params) })
+}
+
+function fetchGetAiPromptDetail(id) {
+  return request.get({ url: `/aiPrompt/${id}` })
+}
+
+function fetchSaveAiPrompt(params) {
+  return request.post({ url: '/aiPrompt/save', params })
+}
+
+function fetchUpdateAiPrompt(params) {
+  return request.post({ url: '/aiPrompt/update', params })
+}
+
+function fetchDeleteAiPrompt(id) {
+  return request.post({ url: `/aiPrompt/remove/${id}` })
+}
+
+function fetchRenderAiPromptPreview(params) {
+  return request.post({ url: '/aiPrompt/render-preview', params })
+}
+
+async function fetchExportAiPromptReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/aiPrompt/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function buildAiObservePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.requestId !== undefined ? { requestId: params.requestId } : {}),
+    ...(params.promptCode !== undefined ? { promptCode: params.promptCode } : {}),
+    ...(params.userId !== undefined ? { userId: params.userId } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {})
+  }
+}
+
+function fetchAiCallLogPage(params = {}) {
+  return request.post({ url: '/aiCallLog/page', params: buildAiObservePageParams(params) })
+}
+
+function fetchGetAiCallLogDetail(id) {
+  return request.get({ url: `/aiCallLog/${id}` })
+}
+
+function fetchGetAiObserveStats() {
+  return request.get({ url: '/aiCallLog/stats' })
+}
+
+function fetchGetAiCallLogMcpLogs(id) {
+  return request.get({ url: `/aiCallLog/${id}/mcpLogs` })
+}
+
+export function buildAiMcpMountPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.transportType !== undefined ? { transportType: params.transportType } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {})
+  }
+}
+
+function fetchAiMcpMountPage(params = {}) {
+  return request.post({ url: '/aiMcpMount/page', params: buildAiMcpMountPageParams(params) })
+}
+
+function fetchGetAiMcpMountDetail(id) {
+  return request.get({ url: `/aiMcpMount/${id}` })
+}
+
+function fetchSaveAiMcpMount(params) {
+  return request.post({ url: '/aiMcpMount/save', params })
+}
+
+function fetchUpdateAiMcpMount(params) {
+  return request.post({ url: '/aiMcpMount/update', params })
+}
+
+function fetchDeleteAiMcpMount(id) {
+  return request.post({ url: `/aiMcpMount/remove/${id}` })
+}
+
+function fetchPreviewAiMcpTools(id) {
+  return request.get({ url: `/aiMcpMount/${id}/tools` })
+}
+
+function fetchTestAiMcpConnectivity(id) {
+  return request.post({ url: `/aiMcpMount/${id}/connectivity/test` })
+}
+
+function fetchValidateAiMcpDraftConnectivity(params) {
+  return request.post({ url: '/aiMcpMount/connectivity/validate-draft', params })
+}
+
+function fetchTestAiMcpTool(id, params) {
+  return request.post({ url: `/aiMcpMount/${id}/tool/test`, params })
+}
+
+export {
+  fetchAiCallLogPage,
+  fetchAiMcpMountPage,
+  fetchAiParamPage,
+  fetchAiPromptPage,
+  fetchDeleteAiMcpMount,
+  fetchDeleteAiParam,
+  fetchDeleteAiPrompt,
+  fetchExportAiParamReport,
+  fetchExportAiPromptReport,
+  fetchGetAiCallLogDetail,
+  fetchGetAiCallLogMcpLogs,
+  fetchGetAiConfigSummary,
+  fetchGetAiMcpMountDetail,
+  fetchGetAiObserveStats,
+  fetchGetAiParamDetail,
+  fetchGetAiParamMany,
+  fetchGetAiPromptDetail,
+  fetchPreviewAiMcpTools,
+  fetchSaveAiMcpMount,
+  fetchSaveAiParam,
+  fetchRenderAiPromptPreview,
+  fetchSaveAiPrompt,
+  fetchSetAiParamDefault,
+  fetchTestAiMcpConnectivity,
+  fetchTestAiMcpTool,
+  fetchUpdateAiMcpMount,
+  fetchUpdateAiParam,
+  fetchUpdateAiPrompt,
+  fetchValidateAiMcpDraftConnectivity,
+  fetchValidateAiParamDraft
+}
diff --git a/rsf-design/src/api/asn-order-item.js b/rsf-design/src/api/asn-order-item.js
new file mode 100644
index 0000000..bfe089b
--- /dev/null
+++ b/rsf-design/src/api/asn-order-item.js
@@ -0,0 +1,78 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        if (Array.isArray(value) && value.length === 0) return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildAsnOrderItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderBy ? { orderBy: normalizeText(params.orderBy) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'orderBy'])
+  }
+}
+
+export function fetchAsnOrderItemPage(params = {}) {
+  return request.post({
+    url: '/asnOrderItem/page',
+    params: buildAsnOrderItemPageParams(params)
+  })
+}
+
+export function fetchAsnOrderItemFullPage(params = {}) {
+  return request.post({
+    url: '/asnOrderItemFull/in/page',
+    params: buildAsnOrderItemPageParams(params)
+  })
+}
+
+export function fetchGetAsnOrderItemDetail(id) {
+  return request.get({
+    url: `/asnOrderItem/${id}`
+  })
+}
+
+export function fetchGetAsnOrderItemMany(ids) {
+  return request.post({
+    url: `/asnOrderItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportAsnOrderItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/asnOrderItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/asn-order-log.js b/rsf-design/src/api/asn-order-log.js
new file mode 100644
index 0000000..e0d5bff
--- /dev/null
+++ b/rsf-design/src/api/asn-order-log.js
@@ -0,0 +1,108 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildAsnOrderLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildAsnOrderItemLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.logId !== undefined ? { logId: Number(params.logId) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'logId'])
+  }
+}
+
+export function fetchAsnOrderLogPage(params = {}) {
+  return request.post({
+    url: '/asnOrderLog/page',
+    params: buildAsnOrderLogPageParams(params)
+  })
+}
+
+export function fetchGetAsnOrderLogDetail(id) {
+  return request.get({
+    url: `/asnOrderLog/${id}`
+  })
+}
+
+export function fetchGetAsnOrderLogMany(ids) {
+  return request.post({
+    url: `/asnOrderLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchAsnOrderItemLogPage(params = {}) {
+  return request.post({
+    url: '/asnOrderItemLog/page',
+    params: buildAsnOrderItemLogPageParams(params)
+  })
+}
+
+export function fetchGetAsnOrderItemLogDetail(id) {
+  return request.get({
+    url: `/asnOrderItemLog/${id}`
+  })
+}
+
+export function fetchGetAsnOrderItemLogMany(ids) {
+  return request.post({
+    url: `/asnOrderItemLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportAsnOrderItemLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/asnOrderItemLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export async function fetchExportAsnOrderLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/asnOrderLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/asn-order.js b/rsf-design/src/api/asn-order.js
new file mode 100644
index 0000000..dc822da
--- /dev/null
+++ b/rsf-design/src/api/asn-order.js
@@ -0,0 +1,129 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildAsnOrderPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildAsnOrderItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderId !== undefined ? { orderId: Number(params.orderId) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'orderId'])
+  }
+}
+
+export function buildPurchaseFilterPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildPurchaseItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.purchaseId !== undefined ? { purchaseId: Number(params.purchaseId) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'purchaseId'])
+  }
+}
+
+export function fetchAsnOrderPage(params = {}) {
+  return request.post({
+    url: '/asnOrder/page',
+    params: buildAsnOrderPageParams(params)
+  })
+}
+
+export function fetchGetAsnOrderDetail(id) {
+  return request.get({
+    url: `/asnOrder/${id}`
+  })
+}
+
+export function fetchGetAsnOrderMany(ids) {
+  return request.post({
+    url: `/asnOrder/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchAsnOrderItemPage(params = {}) {
+  return request.post({
+    url: '/asnOrderItem/page',
+    params: buildAsnOrderItemPageParams(params)
+  })
+}
+
+export function fetchCompleteAsnOrder(id) {
+  return request.post({
+    url: `/asnOrder/complete/${id}`
+  })
+}
+
+export function fetchPurchaseFilterPage(params = {}) {
+  return request.post({
+    url: '/purchase/filters/page',
+    params: buildPurchaseFilterPageParams(params)
+  })
+}
+
+export function fetchPurchaseItemPage(params = {}) {
+  return request.post({
+    url: '/purchaseItem/page',
+    params: buildPurchaseItemPageParams(params)
+  })
+}
+
+export function fetchCreateAsnOrderByPurchase(payload = {}) {
+  return request.post({
+    url: '/asnOrder/purchases/save',
+    params: payload
+  })
+}
+
+export async function fetchExportAsnOrderReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/asnOrder/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/bas-container.js b/rsf-design/src/api/bas-container.js
new file mode 100644
index 0000000..f673660
--- /dev/null
+++ b/rsf-design/src/api/bas-container.js
@@ -0,0 +1,199 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildBasContainerPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildBasContainerSavePayload(formData = {}) {
+  const areas = normalizeBasContainerAreas(formData.areas)
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    containerType:
+      formData.containerType !== undefined && formData.containerType !== null && formData.containerType !== ''
+        ? Number(formData.containerType)
+        : void 0,
+    codeType: normalizeText(formData.codeType) || '',
+    ...(areas.length ? { areas } : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function normalizeBasContainerAreas(areas = []) {
+  if (!Array.isArray(areas)) {
+    return []
+  }
+
+  return areas
+    .map((item, index) => {
+      if (item === null || item === undefined) {
+        return null
+      }
+
+      if (typeof item === 'object') {
+        const id = normalizeNumberValue(item.id ?? item.areaId ?? item.value)
+        if (id === void 0) {
+          return null
+        }
+        const sort = normalizeSortValue(item.sort, index + 1)
+        return { id, sort }
+      }
+
+      const id = normalizeNumberValue(item)
+      if (id === void 0) {
+        return null
+      }
+      return { id, sort: index + 1 }
+    })
+    .filter(Boolean)
+    .sort((left, right) => {
+      const leftSort = normalizeSortValue(left.sort, Number.MAX_SAFE_INTEGER)
+      const rightSort = normalizeSortValue(right.sort, Number.MAX_SAFE_INTEGER)
+      return leftSort - rightSort
+    })
+    .map((item, index) => ({
+      id: item.id,
+      sort: index + 1
+    }))
+}
+
+function normalizeNumberValue(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? void 0 : numberValue
+}
+
+function normalizeSortValue(value, fallback = 1) {
+  const numberValue = normalizeNumberValue(value)
+  return numberValue === void 0 ? fallback : numberValue
+}
+
+export function buildBasContainerSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    containerType:
+      params.containerType !== undefined && params.containerType !== null && params.containerType !== ''
+        ? Number(params.containerType)
+        : void 0,
+    codeType: normalizeText(params.codeType),
+    areas: normalizeText(params.areas),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function fetchBasContainerPage(params = {}) {
+  return request.post({
+    url: '/basContainer/page',
+    params: buildBasContainerPageParams(params)
+  })
+}
+
+export function fetchGetBasContainerDetail(id) {
+  return request.get({
+    url: `/basContainer/${id}`
+  })
+}
+
+export function fetchGetBasContainerMany(ids) {
+  return request.post({
+    url: `/basContainer/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveBasContainer(params = {}) {
+  return request.post({
+    url: '/basContainer/save',
+    params: buildBasContainerSavePayload(params)
+  })
+}
+
+export function fetchUpdateBasContainer(params = {}) {
+  return request.post({
+    url: '/basContainer/update',
+    params: buildBasContainerSavePayload(params)
+  })
+}
+
+export function fetchDeleteBasContainer(ids) {
+  return request.post({
+    url: `/basContainer/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchBasContainerQuery(condition = '') {
+  return request.post({
+    url: '/basContainer/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportBasContainerReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/basContainer/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function fetchWarehouseAreasList() {
+  return request.post({
+    url: '/warehouseAreas/list',
+    data: {}
+  })
+}
diff --git a/rsf-design/src/api/bas-station-area.js b/rsf-design/src/api/bas-station-area.js
new file mode 100644
index 0000000..7919a1a
--- /dev/null
+++ b/rsf-design/src/api/bas-station-area.js
@@ -0,0 +1,218 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildBasStationAreaPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildBasStationAreaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    stationAreaName: normalizeText(params.stationAreaName),
+    stationAreaId: normalizeText(params.stationAreaId),
+    type:
+      params.type !== undefined && params.type !== null && params.type !== ''
+        ? Number(params.type)
+        : void 0,
+    inAble:
+      params.inAble !== undefined && params.inAble !== null && params.inAble !== ''
+        ? Number(params.inAble)
+        : void 0,
+    outAble:
+      params.outAble !== undefined && params.outAble !== null && params.outAble !== ''
+        ? Number(params.outAble)
+        : void 0,
+    useStatus: normalizeText(params.useStatus),
+    area:
+      params.area !== undefined && params.area !== null && params.area !== ''
+        ? Number(params.area)
+        : void 0,
+    isCrossZone:
+      params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
+        ? Number(params.isCrossZone)
+        : void 0,
+    crossZoneArea: normalizeText(params.crossZoneArea),
+    isWcs:
+      params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
+        ? Number(params.isWcs)
+        : void 0,
+    wcsData: normalizeText(params.wcsData),
+    containerType: normalizeText(params.containerType),
+    barcode: normalizeText(params.barcode),
+    autoTransfer:
+      params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+        ? Number(params.autoTransfer)
+        : void 0,
+    stationAlias: normalizeText(params.stationAlias),
+    memo: normalizeText(params.memo),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+function normalizeIdArray(ids = []) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => {
+        if (item === null || item === undefined || item === '') {
+          return void 0
+        }
+        const parsed = Number(item)
+        return Number.isNaN(parsed) ? void 0 : parsed
+      })
+      .filter((item) => item !== void 0)
+  }
+
+  if (typeof ids === 'string' && ids.trim()) {
+    return ids
+      .split(',')
+      .map((item) => {
+        const parsed = Number(item.trim())
+        return Number.isNaN(parsed) ? void 0 : parsed
+      })
+      .filter((item) => item !== void 0)
+  }
+
+  return []
+}
+
+export function buildBasStationAreaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    stationAreaName: normalizeText(formData.stationAreaName) || '',
+    stationAreaId: normalizeText(formData.stationAreaId) || '',
+    ...(formData.type !== void 0 && formData.type !== null && formData.type !== ''
+      ? { type: Number(formData.type) }
+      : {}),
+    ...(formData.inAble !== void 0 && formData.inAble !== null && formData.inAble !== ''
+      ? { inAble: Number(formData.inAble) }
+      : {}),
+    ...(formData.outAble !== void 0 && formData.outAble !== null && formData.outAble !== ''
+      ? { outAble: Number(formData.outAble) }
+      : {}),
+    useStatus: normalizeText(formData.useStatus) || '',
+    ...(formData.area !== void 0 && formData.area !== null && formData.area !== ''
+      ? { area: Number(formData.area) }
+      : {}),
+    ...(formData.isCrossZone !== void 0 && formData.isCrossZone !== null && formData.isCrossZone !== ''
+      ? { isCrossZone: Number(formData.isCrossZone) }
+      : {}),
+    ...(Array.isArray(formData.crossZoneArea) && formData.crossZoneArea.length
+      ? { crossZoneArea: normalizeIdArray(formData.crossZoneArea) }
+      : {}),
+    ...(formData.isWcs !== void 0 && formData.isWcs !== null && formData.isWcs !== ''
+      ? { isWcs: Number(formData.isWcs) }
+      : {}),
+    wcsData: normalizeText(formData.wcsData) || '',
+    ...(Array.isArray(formData.containerType) && formData.containerType.length
+      ? { containerType: normalizeIdArray(formData.containerType) }
+      : {}),
+    barcode: normalizeText(formData.barcode) || '',
+    ...(formData.autoTransfer !== void 0 && formData.autoTransfer !== null && formData.autoTransfer !== ''
+      ? { autoTransfer: Number(formData.autoTransfer) }
+      : {}),
+    ...(Array.isArray(formData.stationAlias) && formData.stationAlias.length
+      ? { stationAlias: formData.stationAlias.map((item) => String(item).trim()).filter(Boolean) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchBasStationAreaPage(params = {}) {
+  return request.post({
+    url: '/basStationArea/page',
+    params: buildBasStationAreaPageParams(params)
+  })
+}
+
+export function fetchBasStationAreaList() {
+  return request.post({
+    url: '/basStationArea/list',
+    data: {}
+  })
+}
+
+export function fetchBasStationAreaDetail(id) {
+  return request.get({
+    url: `/basStationArea/${id}`
+  })
+}
+
+export function fetchBasStationAreaMany(ids) {
+  return request.post({
+    url: `/basStationArea/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveBasStationArea(params = {}) {
+  return request.post({
+    url: '/basStationArea/save',
+    params: buildBasStationAreaSavePayload(params)
+  })
+}
+
+export function fetchUpdateBasStationArea(params = {}) {
+  return request.post({
+    url: '/basStationArea/update',
+    params: buildBasStationAreaSavePayload(params)
+  })
+}
+
+export function fetchDeleteBasStationArea(ids) {
+  return request.post({
+    url: `/basStationArea/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchBasStationAreaQuery(condition = '') {
+  return request.post({
+    url: '/basStationArea/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
diff --git a/rsf-design/src/api/bas-station.js b/rsf-design/src/api/bas-station.js
new file mode 100644
index 0000000..bf9bd42
--- /dev/null
+++ b/rsf-design/src/api/bas-station.js
@@ -0,0 +1,190 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildBasStationPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildBasStationSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    stationName: normalizeText(params.stationName),
+    stationId: normalizeText(params.stationId),
+    type:
+      params.type !== undefined && params.type !== null && params.type !== ''
+        ? Number(params.type)
+        : void 0,
+    useStatus: normalizeText(params.useStatus),
+    area:
+      params.area !== undefined && params.area !== null && params.area !== ''
+        ? Number(params.area)
+        : void 0,
+    isCrossZone:
+      params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
+        ? Number(params.isCrossZone)
+        : void 0,
+    isWcs:
+      params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
+        ? Number(params.isWcs)
+        : void 0,
+    barcode: normalizeText(params.barcode),
+    autoTransfer:
+      params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+        ? Number(params.autoTransfer)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildBasStationSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    stationName: normalizeText(formData.stationName) || '',
+    stationId: normalizeText(formData.stationId) || '',
+    ...(formData.type !== undefined && formData.type !== null && formData.type !== ''
+      ? { type: Number(formData.type) }
+      : {}),
+    ...(formData.area !== undefined && formData.area !== null && formData.area !== ''
+      ? { area: Number(formData.area) }
+      : {}),
+    useStatus: normalizeText(formData.useStatus) || '',
+    ...(formData.isCrossZone !== undefined && formData.isCrossZone !== null && formData.isCrossZone !== ''
+      ? { isCrossZone: Number(formData.isCrossZone) }
+      : {}),
+    ...(Array.isArray(formData.areaIds) && formData.areaIds.length
+      ? { areaIds: formData.areaIds.map((id) => Number(id)).filter((id) => !Number.isNaN(id)) }
+      : {}),
+    ...(formData.isWcs !== undefined && formData.isWcs !== null && formData.isWcs !== ''
+      ? { isWcs: Number(formData.isWcs) }
+      : {}),
+    wcsData: normalizeText(formData.wcsData) || '',
+    ...(Array.isArray(formData.containerTypes) && formData.containerTypes.length
+      ? { containerTypes: formData.containerTypes.map((id) => Number(id)).filter((id) => !Number.isNaN(id)) }
+      : {}),
+    barcode: normalizeText(formData.barcode) || '',
+    ...(formData.autoTransfer !== undefined && formData.autoTransfer !== null && formData.autoTransfer !== ''
+      ? { autoTransfer: Number(formData.autoTransfer) }
+      : {}),
+    ...(formData.inAble !== undefined && formData.inAble !== null && formData.inAble !== ''
+      ? { inAble: Number(formData.inAble) }
+      : {}),
+    ...(formData.outAble !== undefined && formData.outAble !== null && formData.outAble !== ''
+      ? { outAble: Number(formData.outAble) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchBasStationPage(params = {}, requestOptions = {}) {
+  return request.post({
+    url: '/basStation/page',
+    params: buildBasStationPageParams(params),
+    ...requestOptions
+  })
+}
+
+export function fetchBasStationList() {
+  return request.post({
+    url: '/basStation/list',
+    data: {}
+  })
+}
+
+export function fetchGetBasStationDetail(id) {
+  return request.get({
+    url: `/basStation/${id}`
+  })
+}
+
+export function fetchGetBasStationMany(ids) {
+  return request.post({
+    url: `/basStation/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveBasStation(params = {}) {
+  return request.post({
+    url: '/basStation/save',
+    params: buildBasStationSavePayload(params)
+  })
+}
+
+export function fetchUpdateBasStation(params = {}) {
+  return request.post({
+    url: '/basStation/update',
+    params: buildBasStationSavePayload(params)
+  })
+}
+
+export function fetchDeleteBasStation(ids) {
+  return request.post({
+    url: `/basStation/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchBasStationQuery(condition = '') {
+  return request.post({
+    url: '/basStation/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportBasStationReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/basStation/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/check-diff.js b/rsf-design/src/api/check-diff.js
new file mode 100644
index 0000000..9b76461
--- /dev/null
+++ b/rsf-design/src/api/check-diff.js
@@ -0,0 +1,109 @@
+import request from '@/utils/http'
+import {
+  buildCheckDiffItemPageRequestParams,
+  buildCheckDiffPageRequestParams
+} from '../views/orders/check-diff/checkDiffPage.helpers'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => String(item).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildCheckDiffPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildCheckDiffItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchCheckDiffPage(params = {}) {
+  return request.post({ url: '/checkDiff/page', params: buildCheckDiffPageRequestParams(params) })
+}
+
+export function fetchGetCheckDiffDetail(id) {
+  return request.get({ url: `/checkDiff/${id}` })
+}
+
+export function fetchGetCheckDiffMany(ids) {
+  return request.post({ url: `/checkDiff/many/${normalizeIds(ids)}` })
+}
+
+export function fetchCheckDiffItemPage(params = {}) {
+  return request.post({ url: '/checkDiffItem/page', params: buildCheckDiffItemPageRequestParams(params) })
+}
+
+export function fetchGetCheckDiffItemDetail(id) {
+  return request.get({ url: `/checkDiffItem/${id}` })
+}
+
+export function fetchGetCheckDiffItemMany(ids) {
+  return request.post({ url: `/checkDiffItem/many/${normalizeIds(ids)}` })
+}
+
+export function fetchUpdateCheckDiffItem(params = {}) {
+  return request.post({ url: '/checkDiffItem/update', params })
+}
+
+export function fetchDeleteCheckDiff(ids) {
+  return request.post({ url: `/checkDiff/remove/${normalizeIds(ids)}` })
+}
+
+export function fetchDeleteCheckDiffItem(ids) {
+  return request.post({ url: `/checkDiffItem/remove/${normalizeIds(ids)}` })
+}
+
+export async function fetchExportCheckDiffReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/checkDiff/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export async function fetchExportCheckDiffItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/checkDiffItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/check-order.js b/rsf-design/src/api/check-order.js
new file mode 100644
index 0000000..7a82059
--- /dev/null
+++ b/rsf-design/src/api/check-order.js
@@ -0,0 +1,102 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildCheckOrderPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildCheckOrderItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchCheckOrderPage(params = {}) {
+  return request.post({
+    url: '/check/page',
+    params: buildCheckOrderPageParams(params)
+  })
+}
+
+export function fetchGetCheckOrderDetail(id) {
+  return request.get({
+    url: `/check/${id}`
+  })
+}
+
+export function fetchGetCheckOrderMany(ids) {
+  return request.post({
+    url: `/check/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchCheckOrderItemPage(params = {}) {
+  return request.post({
+    url: '/checkItem/page',
+    params: buildCheckOrderItemPageParams(params)
+  })
+}
+
+export function fetchGetCheckOrderItemDetail(id) {
+  return request.get({
+    url: `/checkItem/${id}`
+  })
+}
+
+export function fetchGetCheckOrderItemMany(ids) {
+  return request.post({
+    url: `/checkItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchCancelCheckOrder(id) {
+  return request.get({
+    url: `/check/cancel/${id}`
+  })
+}
+
+export async function fetchExportCheckOrderReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/check/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/check-out-bound.js b/rsf-design/src/api/check-out-bound.js
new file mode 100644
index 0000000..a1c5733
--- /dev/null
+++ b/rsf-design/src/api/check-out-bound.js
@@ -0,0 +1,52 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildCheckOutBoundPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchCheckOutBoundPage(params = {}) {
+  return request.post({
+    url: '/locItem/useO/page',
+    params: buildCheckOutBoundPageParams(params)
+  })
+}
+
+export function fetchGetCheckOutBoundDetail(id) {
+  return request.get({
+    url: `/locItem/${id}`
+  })
+}
+
+export function fetchCheckOutBoundSites() {
+  return request.get({
+    url: '/check/tasks/sites'
+  })
+}
+
+export function fetchGenerateCheckOutBoundTask(payload = {}) {
+  return request.post({
+    url: '/locItem/check/task',
+    params: payload
+  })
+}
diff --git a/rsf-design/src/api/companys.js b/rsf-design/src/api/companys.js
new file mode 100644
index 0000000..4546a24
--- /dev/null
+++ b/rsf-design/src/api/companys.js
@@ -0,0 +1,105 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildCompanysPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchCompanysPage(params = {}) {
+  return request.post({
+    url: '/companys/page',
+    params: buildCompanysPageParams(params)
+  })
+}
+
+export function fetchCompanysList() {
+  return request.post({
+    url: '/companys/list',
+    data: {}
+  })
+}
+
+export function fetchGetCompanysDetail(id) {
+  return request.get({
+    url: `/companys/${id}`
+  })
+}
+
+export function fetchGetCompanysMany(ids) {
+  return request.post({
+    url: `/companys/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveCompanys(params = {}) {
+  return request.post({
+    url: '/companys/save',
+    params
+  })
+}
+
+export function fetchUpdateCompanys(params = {}) {
+  return request.post({
+    url: '/companys/update',
+    params
+  })
+}
+
+export function fetchDeleteCompanys(ids) {
+  return request.post({
+    url: `/companys/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchCompanysQuery(condition = '') {
+  return request.post({
+    url: '/companys/query',
+    params: {
+      condition: normalizeText(condition)
+    }
+  })
+}
+
+export async function fetchExportCompanysReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/companys/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/contract.js b/rsf-design/src/api/contract.js
new file mode 100644
index 0000000..0bc196e
--- /dev/null
+++ b/rsf-design/src/api/contract.js
@@ -0,0 +1,112 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildContractPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildContractSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    projectName: normalizeText(formData.projectName) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchContractPage(params = {}) {
+  return request.post({
+    url: '/contract/page',
+    params: buildContractPageParams(params)
+  })
+}
+
+export function fetchGetContractDetail(id) {
+  return request.get({
+    url: `/contract/${id}`
+  })
+}
+
+export function fetchGetContractMany(ids) {
+  return request.post({
+    url: `/contract/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveContract(params = {}) {
+  return request.post({
+    url: '/contract/save',
+    params: buildContractSavePayload(params)
+  })
+}
+
+export function fetchUpdateContract(params = {}) {
+  return request.post({
+    url: '/contract/update',
+    params: buildContractSavePayload(params)
+  })
+}
+
+export function fetchDeleteContract(ids) {
+  return request.post({
+    url: `/contract/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchContractQuery(condition = '') {
+  return request.post({
+    url: '/contract/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportContractReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/contract/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/dashboard.js b/rsf-design/src/api/dashboard.js
new file mode 100644
index 0000000..86c5d52
--- /dev/null
+++ b/rsf-design/src/api/dashboard.js
@@ -0,0 +1,44 @@
+import request from '@/utils/http'
+
+function fetchDashboardHeader() {
+  return request.post({
+    url: '/asnOrder/dashbord/header',
+    params: {}
+  })
+}
+
+function fetchDashboardTrend() {
+  return request.post({
+    url: '/asnOrder/stock/trand',
+    params: {}
+  })
+}
+
+function fetchDashboardDeadStock(params) {
+  return request.post({
+    url: '/locItem/page',
+    params
+  })
+}
+
+function fetchDashboardLocUsage() {
+  return request.post({
+    url: '/loc/pie/list',
+    params: {}
+  })
+}
+
+function fetchDashboardTasks(params) {
+  return request.post({
+    url: '/task/page',
+    params
+  })
+}
+
+export {
+  fetchDashboardDeadStock,
+  fetchDashboardHeader,
+  fetchDashboardLocUsage,
+  fetchDashboardTasks,
+  fetchDashboardTrend
+}
diff --git a/rsf-design/src/api/delivery.js b/rsf-design/src/api/delivery.js
new file mode 100644
index 0000000..0b33f8a
--- /dev/null
+++ b/rsf-design/src/api/delivery.js
@@ -0,0 +1,200 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildDeliverySearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'platId', 'type', 'wkType', 'source', 'timeStart', 'timeEnd', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) result[key] = value
+  })
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+  return result
+}
+
+export function buildDeliveryPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeliverySearchParams(params)
+  }
+}
+
+export function buildDeliveryDetailQueryParams(params = {}) {
+  return {
+    deliveryId: params.deliveryId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function buildDeliveryItemSearchParams(params = {}) {
+  const result = {}
+  ;[
+    'condition',
+    'deliveryCode',
+    'platItemId',
+    'matnrCode',
+    'maktx',
+    'splrName',
+    'splrCode',
+    'splrBatch',
+    'timeStart',
+    'timeEnd',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) result[key] = value
+  })
+  if (params.deliveryId !== '' && params.deliveryId !== undefined && params.deliveryId !== null) {
+    result.deliveryId = Number(params.deliveryId)
+  }
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+  return result
+}
+
+export function buildDeliveryItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeliveryItemSearchParams(params)
+  }
+}
+
+export function buildDeliveryItemDetailQueryParams(params = {}) {
+  return {
+    deliveryItemId: params.deliveryItemId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function buildDeliveryExportParams(payload = {}) {
+  const params = buildDeliverySearchParams(payload)
+  return filterParams(params, ['ids'])
+}
+
+export function fetchDeliveryPage(params = {}) {
+  return request.post({
+    url: '/delivery/page',
+    params: buildDeliveryPageParams(params)
+  })
+}
+
+export function fetchGetDeliveryDetail(id) {
+  return request.get({
+    url: `/delivery/${id}`
+  })
+}
+
+export function fetchGetDeliveryMany(ids) {
+  return request.post({
+    url: `/delivery/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchDeleteDelivery(id) {
+  return request.post({
+    url: `/delivery/remove/${id}`
+  })
+}
+
+export function fetchSaveDelivery(payload = {}) {
+  return request.post({
+    url: '/delivery/save',
+    params: payload
+  })
+}
+
+export function fetchUpdateDelivery(payload = {}) {
+  return request.post({
+    url: '/delivery/update',
+    params: payload
+  })
+}
+
+export function fetchDeliveryItemPage(params = {}) {
+  return request.post({
+    url: '/deliveryItem/page',
+    params: buildDeliveryItemPageParams(params)
+  })
+}
+
+export function fetchGetDeliveryItemDetail(id) {
+  return request.get({
+    url: `/deliveryItem/${id}`
+  })
+}
+
+export function fetchGetDeliveryItemMany(ids) {
+  return request.post({
+    url: `/deliveryItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchDeleteDeliveryItem(id) {
+  return request.post({
+    url: `/deliveryItem/remove/${id}`
+  })
+}
+
+export function fetchSaveDeliveryItem(payload = {}) {
+  return request.post({
+    url: '/deliveryItem/save',
+    params: payload
+  })
+}
+
+export function fetchUpdateDeliveryItem(payload = {}) {
+  return request.post({
+    url: '/deliveryItem/update',
+    params: payload
+  })
+}
+
+export async function fetchExportDeliveryReport(payload = {}, options = {}) {
+  const exportRequest = { url: `/delivery/export` }
+  return fetch(`${import.meta.env.VITE_API_URL}${exportRequest.url}`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(buildDeliveryExportParams(payload))
+  })
+}
diff --git a/rsf-design/src/api/device-bind.js b/rsf-design/src/api/device-bind.js
new file mode 100644
index 0000000..81cd505
--- /dev/null
+++ b/rsf-design/src/api/device-bind.js
@@ -0,0 +1,167 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? void 0 : numberValue
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildDeviceBindPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildDeviceBindSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    currentRow: normalizeNumber(params.currentRow),
+    startRow: normalizeNumber(params.startRow),
+    endRow: normalizeNumber(params.endRow),
+    deviceQty: normalizeNumber(params.deviceQty),
+    startDeviceNo: normalizeNumber(params.startDeviceNo),
+    endDeviceNo: normalizeNumber(params.endDeviceNo),
+    typeId: normalizeNumber(params.typeId),
+    staList: normalizeText(params.staList),
+    beSimilar: normalizeText(params.beSimilar),
+    emptySimilar: normalizeText(params.emptySimilar),
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildDeviceBindSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.currentRow !== undefined && formData.currentRow !== null && formData.currentRow !== ''
+      ? { currentRow: Number(formData.currentRow) }
+      : {}),
+    ...(formData.startRow !== undefined && formData.startRow !== null && formData.startRow !== ''
+      ? { startRow: Number(formData.startRow) }
+      : {}),
+    ...(formData.endRow !== undefined && formData.endRow !== null && formData.endRow !== ''
+      ? { endRow: Number(formData.endRow) }
+      : {}),
+    ...(formData.deviceQty !== undefined && formData.deviceQty !== null && formData.deviceQty !== ''
+      ? { deviceQty: Number(formData.deviceQty) }
+      : {}),
+    ...(formData.startDeviceNo !== undefined && formData.startDeviceNo !== null && formData.startDeviceNo !== ''
+      ? { startDeviceNo: Number(formData.startDeviceNo) }
+      : {}),
+    ...(formData.endDeviceNo !== undefined && formData.endDeviceNo !== null && formData.endDeviceNo !== ''
+      ? { endDeviceNo: Number(formData.endDeviceNo) }
+      : {}),
+    staList: normalizeText(formData.staList) || '',
+    ...(formData.typeId !== undefined && formData.typeId !== null && formData.typeId !== ''
+      ? { typeId: Number(formData.typeId) }
+      : {}),
+    beSimilar: normalizeText(formData.beSimilar) || '',
+    emptySimilar: normalizeText(formData.emptySimilar) || '',
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchDeviceBindPage(params = {}) {
+  return request.post({
+    url: '/deviceBind/page',
+    params: buildDeviceBindPageParams(params)
+  })
+}
+
+export function fetchDeviceBindList() {
+  return request.post({
+    url: '/deviceBind/list',
+    data: {}
+  })
+}
+
+export function fetchGetDeviceBindDetail(id) {
+  return request.get({
+    url: `/deviceBind/${id}`
+  })
+}
+
+export function fetchGetDeviceBindMany(ids) {
+  return request.post({
+    url: `/deviceBind/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveDeviceBind(params = {}) {
+  return request.post({
+    url: '/deviceBind/save',
+    params: buildDeviceBindSavePayload(params)
+  })
+}
+
+export function fetchUpdateDeviceBind(params = {}) {
+  return request.post({
+    url: '/deviceBind/update',
+    params: buildDeviceBindSavePayload(params)
+  })
+}
+
+export function fetchDeleteDeviceBind(ids) {
+  return request.post({
+    url: `/deviceBind/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchDeviceBindQuery(condition = '') {
+  return request.post({
+    url: '/deviceBind/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportDeviceBindReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/deviceBind/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/device-site.js b/rsf-design/src/api/device-site.js
new file mode 100644
index 0000000..40d823a
--- /dev/null
+++ b/rsf-design/src/api/device-site.js
@@ -0,0 +1,213 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeMultiValue(value) {
+  if (Array.isArray(value)) {
+    return value.map((item) => normalizeText(item)).filter(Boolean).join(',')
+  }
+  return normalizeText(value)
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildDeviceSitePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildDeviceSiteSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    type: normalizeMultiValue(params.type),
+    site: normalizeText(params.site),
+    name: normalizeText(params.name),
+    target: normalizeText(params.target),
+    label: normalizeText(params.label),
+    device: normalizeText(params.device),
+    deviceCode: normalizeText(params.deviceCode),
+    deviceSite: normalizeText(params.deviceSite),
+    channel: normalizeNumber(params.channel),
+    areaIdStart: normalizeNumber(params.areaIdStart),
+    areaIdEnd: normalizeNumber(params.areaIdEnd),
+    status: normalizeNumber(params.status),
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildDeviceSiteSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    type: normalizeMultiValue(formData.type),
+    site: normalizeText(formData.site) || '',
+    name: normalizeText(formData.name) || '',
+    target: normalizeText(formData.target) || '',
+    label: normalizeText(formData.label) || '',
+    device: normalizeText(formData.device) || '',
+    deviceCode: normalizeText(formData.deviceCode) || '',
+    deviceSite: normalizeText(formData.deviceSite) || '',
+    ...(formData.channel !== void 0 && formData.channel !== null && formData.channel !== ''
+      ? { channel: Number(formData.channel) }
+      : {}),
+    ...(formData.areaIdStart !== void 0 && formData.areaIdStart !== null && formData.areaIdStart !== ''
+      ? { areaIdStart: Number(formData.areaIdStart) }
+      : {}),
+    ...(formData.areaIdEnd !== void 0 && formData.areaIdEnd !== null && formData.areaIdEnd !== ''
+      ? { areaIdEnd: Number(formData.areaIdEnd) }
+      : {}),
+    ...(formData.status !== void 0 && formData.status !== null && formData.status !== ''
+      ? { status: Number(formData.status) }
+      : {}),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildDeviceSiteInitPayload(formData = {}) {
+  return {
+    flagInit:
+      formData.flagInit !== void 0 && formData.flagInit !== null && formData.flagInit !== ''
+        ? Number(formData.flagInit)
+        : 0,
+    deviceType: normalizeText(formData.deviceType) || '',
+    typeIds: Array.isArray(formData.typeIds) ? formData.typeIds.map((item) => normalizeText(item)).filter(Boolean) : [],
+    site: normalizeText(formData.site) || '',
+    deviceCode: normalizeText(formData.deviceCode) || '',
+    deviceSites: normalizeText(formData.deviceSites) || '',
+    target: normalizeText(formData.target) || '',
+    rows: Array.isArray(formData.rows)
+      ? formData.rows
+          .map((row) => ({
+            deviceSite: normalizeText(row?.deviceSite) || '',
+            site: normalizeText(row?.site) || '',
+            target: normalizeText(row?.target) || ''
+          }))
+          .filter((row) => row.deviceSite && row.site && row.target)
+      : [],
+    channel: normalizeText(formData.channel) || '',
+    ...(formData.areaIdStart !== void 0 && formData.areaIdStart !== null && formData.areaIdStart !== ''
+      ? { areaIdStart: Number(formData.areaIdStart) }
+      : {}),
+    ...(formData.areaIdEnd !== void 0 && formData.areaIdEnd !== null && formData.areaIdEnd !== ''
+      ? { areaIdEnd: Number(formData.areaIdEnd) }
+      : {}),
+    name: normalizeText(formData.name) || '',
+    wcsCode: normalizeText(formData.wcsCode) || '',
+    label: normalizeText(formData.label) || ''
+  }
+}
+
+export function fetchDeviceSitePage(params = {}) {
+  return request.post({
+    url: '/deviceSite/page',
+    params: buildDeviceSitePageParams(params)
+  })
+}
+
+export function fetchDeviceSiteList() {
+  return request.post({
+    url: '/deviceSite/list',
+    data: {}
+  })
+}
+
+export function fetchGetDeviceSiteDetail(id) {
+  return request.get({
+    url: `/deviceSite/${id}`
+  })
+}
+
+export function fetchGetDeviceSiteMany(ids) {
+  return request.post({
+    url: `/deviceSite/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveDeviceSite(params = {}) {
+  return request.post({
+    url: '/deviceSite/save',
+    params: buildDeviceSiteSavePayload(params)
+  })
+}
+
+export function fetchUpdateDeviceSite(params = {}) {
+  return request.post({
+    url: '/deviceSite/update',
+    params: buildDeviceSiteSavePayload(params)
+  })
+}
+
+export function fetchDeleteDeviceSite(ids) {
+  return request.post({
+    url: `/deviceSite/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchDeviceSiteQuery(condition = '') {
+  return request.post({
+    url: '/deviceSite/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchInitDeviceSite(params = {}) {
+  return request.post({
+    url: '/deviceSite/init',
+    params: buildDeviceSiteInitPayload(params)
+  })
+}
+
+export async function fetchExportDeviceSiteReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/deviceSite/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/flow-instance.js b/rsf-design/src/api/flow-instance.js
new file mode 100644
index 0000000..438040b
--- /dev/null
+++ b/rsf-design/src/api/flow-instance.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildFlowInstancePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchFlowInstancePage(params = {}) {
+  return request.post({
+    url: '/flowInstance/page',
+    params: buildFlowInstancePageParams(params)
+  })
+}
+
+export function fetchGetFlowInstanceDetail(id) {
+  return request.get({
+    url: `/flowInstance/${id}`
+  })
+}
+
+export function fetchGetFlowInstanceMany(ids) {
+  return request.post({
+    url: `/flowInstance/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportFlowInstanceReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/flowInstance/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/flow-step-instance.js b/rsf-design/src/api/flow-step-instance.js
new file mode 100644
index 0000000..bbb0888
--- /dev/null
+++ b/rsf-design/src/api/flow-step-instance.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildFlowStepInstancePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchFlowStepInstancePage(params = {}) {
+  return request.post({
+    url: '/flowStepInstance/page',
+    params: buildFlowStepInstancePageParams(params)
+  })
+}
+
+export function fetchGetFlowStepInstanceDetail(id) {
+  return request.get({
+    url: `/flowStepInstance/${id}`
+  })
+}
+
+export function fetchGetFlowStepInstanceMany(ids) {
+  return request.post({
+    url: `/flowStepInstance/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportFlowStepInstanceReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/flowStepInstance/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/flow-step-log.js b/rsf-design/src/api/flow-step-log.js
new file mode 100644
index 0000000..050b9c4
--- /dev/null
+++ b/rsf-design/src/api/flow-step-log.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildFlowStepLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchFlowStepLogPage(params = {}) {
+  return request.post({
+    url: '/flowStepLog/page',
+    params: buildFlowStepLogPageParams(params)
+  })
+}
+
+export function fetchGetFlowStepLogDetail(id) {
+  return request.get({
+    url: `/flowStepLog/${id}`
+  })
+}
+
+export function fetchGetFlowStepLogMany(ids) {
+  return request.post({
+    url: `/flowStepLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportFlowStepLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/flowStepLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/flow-step-template.js b/rsf-design/src/api/flow-step-template.js
new file mode 100644
index 0000000..8021896
--- /dev/null
+++ b/rsf-design/src/api/flow-step-template.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildFlowStepTemplatePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchFlowStepTemplatePage(params = {}) {
+  return request.post({
+    url: '/flowStepTemplate/page',
+    params: buildFlowStepTemplatePageParams(params)
+  })
+}
+
+export function fetchGetFlowStepTemplateDetail(id) {
+  return request.get({
+    url: `/flowStepTemplate/${id}`
+  })
+}
+
+export function fetchGetFlowStepTemplateMany(ids) {
+  return request.post({
+    url: `/flowStepTemplate/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportFlowStepTemplateReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/flowStepTemplate/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/freeze.js b/rsf-design/src/api/freeze.js
new file mode 100644
index 0000000..b93f140
--- /dev/null
+++ b/rsf-design/src/api/freeze.js
@@ -0,0 +1,45 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+export function buildFreezePageParams(params = {}) {
+  const entries = Object.entries(params).filter(([key, value]) => {
+    if (['current', 'pageSize', 'size'].includes(key)) {
+      return false
+    }
+    if (value === undefined || value === null) {
+      return false
+    }
+    if (typeof value === 'string' && value.trim() === '') {
+      return false
+    }
+    return true
+  })
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...Object.fromEntries(entries.map(([key, value]) => [key, normalizeText(value)]))
+  }
+}
+
+export function fetchFreezePage(params = {}) {
+  return request.post({
+    url: '/locItem/useO/page',
+    params: buildFreezePageParams(params)
+  })
+}
+
+export function fetchFreezeDetail(id) {
+  return request.get({
+    url: `/locItem/${id}`
+  })
+}
+
+export function fetchEnabledFields() {
+  return request.get({
+    url: '/fields/enable/list'
+  })
+}
diff --git a/rsf-design/src/api/in-statistic-item.js b/rsf-design/src/api/in-statistic-item.js
new file mode 100644
index 0000000..2c15b2b
--- /dev/null
+++ b/rsf-design/src/api/in-statistic-item.js
@@ -0,0 +1,49 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildInStatisticItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    taskType: normalizeNumber(params.taskType, 1),
+    taskStatus: normalizeNumber(params.taskStatus, 100),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'taskType', 'taskStatus'])
+  }
+}
+
+export function fetchInStatisticItemPage(params = {}) {
+  return request.post({
+    url: '/inStatisticItem/page',
+    params: buildInStatisticItemPageParams(params)
+  })
+}
+
+export function fetchGetInStatisticItemDetail(id) {
+  return request.get({
+    url: `/stockStatistic/${id}`
+  })
+}
diff --git a/rsf-design/src/api/in-statistic.js b/rsf-design/src/api/in-statistic.js
new file mode 100644
index 0000000..c9c0da8
--- /dev/null
+++ b/rsf-design/src/api/in-statistic.js
@@ -0,0 +1,43 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildInStatisticPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    taskType: normalizeNumber(params.taskType, 1),
+    taskStatus: normalizeNumber(params.taskStatus, 100),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'taskType', 'taskStatus'])
+  }
+}
+
+export function fetchInStatisticPage(params = {}) {
+  return request.post({
+    url: '/inStatistic/page',
+    params: buildInStatisticPageParams(params)
+  })
+}
diff --git a/rsf-design/src/api/loc-area-mat-rela.js b/rsf-design/src/api/loc-area-mat-rela.js
new file mode 100644
index 0000000..1cb9289
--- /dev/null
+++ b/rsf-design/src/api/loc-area-mat-rela.js
@@ -0,0 +1,228 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeMultiIds(value) {
+  if (Array.isArray(value)) {
+    return value
+      .map((item) => normalizeNumber(item))
+      .filter((item) => item !== void 0 && item !== null)
+  }
+  const normalized = normalizeNumber(value)
+  return normalized === void 0 || normalized === null ? [] : [normalized]
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocAreaMatRelaPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildLocAreaMatRelaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    areaMatId:
+      params.areaMatId !== undefined && params.areaMatId !== null && params.areaMatId !== ''
+        ? normalizeNumber(params.areaMatId)
+        : void 0,
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? normalizeNumber(params.areaId)
+        : void 0,
+    code: normalizeText(params.code),
+    matnrId:
+      params.matnrId !== undefined && params.matnrId !== null && params.matnrId !== ''
+        ? normalizeNumber(params.matnrId)
+        : void 0,
+    groupId:
+      params.groupId !== undefined && params.groupId !== null && params.groupId !== ''
+        ? normalizeNumber(params.groupId)
+        : void 0,
+    locTypeId:
+      params.locTypeId !== undefined && params.locTypeId !== null && params.locTypeId !== ''
+        ? normalizeNumber(params.locTypeId)
+        : void 0,
+    locId:
+      params.locId !== undefined && params.locId !== null && params.locId !== ''
+        ? normalizeNumber(params.locId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaMatRelaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(formData.areaMatId !== undefined && formData.areaMatId !== null && formData.areaMatId !== ''
+      ? { areaMatId: normalizeNumber(formData.areaMatId) }
+      : {}),
+    ...(formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+      ? { areaId: normalizeNumber(formData.areaId) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    ...(formData.matnrId !== undefined && formData.matnrId !== null && formData.matnrId !== ''
+      ? { matnrId: normalizeNumber(formData.matnrId) }
+      : {}),
+    ...(formData.groupId !== undefined && formData.groupId !== null && formData.groupId !== ''
+      ? { groupId: normalizeNumber(formData.groupId) }
+      : {}),
+    ...(formData.locTypeId !== undefined && formData.locTypeId !== null && formData.locTypeId !== ''
+      ? { locTypeId: normalizeNumber(formData.locTypeId) }
+      : {}),
+    ...(formData.locId !== undefined && formData.locId !== null && formData.locId !== ''
+      ? { locId: normalizeNumber(formData.locId) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchLocAreaMatRelaPage(params = {}) {
+  return request.post({
+    url: '/locAreaMatRela/page',
+    params: buildLocAreaMatRelaPageParams(params)
+  })
+}
+
+export function fetchLocAreaMatRelaList() {
+  return request.post({
+    url: '/locAreaMatRela/list',
+    data: {}
+  })
+}
+
+export function fetchGetLocAreaMatRelaMany(ids) {
+  return request.post({
+    url: `/locAreaMatRela/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaMatRelaDetail(id) {
+  return request.get({
+    url: `/locAreaMatRela/${id}`
+  })
+}
+
+export function fetchSaveLocAreaMatRela(params = {}) {
+  return request.post({
+    url: '/locAreaMatRela/save',
+    params: buildLocAreaMatRelaSavePayload(params)
+  })
+}
+
+export function fetchUpdateLocAreaMatRela(params = {}) {
+  return request.post({
+    url: '/locAreaMatRela/update',
+    params: buildLocAreaMatRelaSavePayload(params)
+  })
+}
+
+export function fetchDeleteLocAreaMatRela(ids) {
+  return request.post({
+    url: `/locAreaMatRela/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchLocAreaMatRelaQuery(condition = '') {
+  return request.post({
+    url: '/locAreaMatRela/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchGetLocAreaMatRelaGroups(id) {
+  return request.get({
+    url: `/locAreaMatRela/groups/${id}`
+  })
+}
+
+export function fetchGetLocAreaMatRelaLocType(id) {
+  return request.get({
+    url: `/locAreaMatRela/locType/${id}`
+  })
+}
+
+export function fetchBindLocAreaMatRelaByMatnr(payload = {}) {
+  return request.post({
+    url: '/locAreaMatRela/matnr/bind',
+    params: {
+      ...(payload.areaMatId !== undefined && payload.areaMatId !== null && payload.areaMatId !== ''
+        ? { areaMatId: normalizeNumber(payload.areaMatId) }
+        : {}),
+      ...(payload.warehouseId !== undefined && payload.warehouseId !== null && payload.warehouseId !== ''
+        ? { warehouseId: normalizeNumber(payload.warehouseId) }
+        : {}),
+      ...(payload.areaId !== undefined && payload.areaId !== null && payload.areaId !== ''
+        ? { areaId: normalizeNumber(payload.areaId) }
+        : {}),
+      ...(payload.groupId !== undefined && payload.groupId !== null && payload.groupId !== ''
+        ? { groupId: normalizeNumber(payload.groupId) }
+        : {}),
+      matnrId: normalizeMultiIds(payload.matnrId),
+      typeId: normalizeMultiIds(payload.typeId),
+      locId: normalizeMultiIds(payload.locId)
+    }
+  })
+}
+
+export async function fetchExportLocAreaMatRelaReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locAreaMatRela/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/loc-area-mat.js b/rsf-design/src/api/loc-area-mat.js
new file mode 100644
index 0000000..814be69
--- /dev/null
+++ b/rsf-design/src/api/loc-area-mat.js
@@ -0,0 +1,240 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeMultiIds(value) {
+  if (Array.isArray(value)) {
+    return value
+      .map((item) => normalizeNumber(item))
+      .filter((item) => item !== void 0 && item !== null)
+  }
+  const normalized = normalizeNumber(value)
+  return normalized === void 0 || normalized === null ? [] : [normalized]
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocAreaMatPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildLocAreaMatSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    warehouseId:
+      params.warehouseId !== undefined && params.warehouseId !== null && params.warehouseId !== ''
+        ? normalizeNumber(params.warehouseId)
+        : void 0,
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? normalizeNumber(params.areaId)
+        : void 0,
+    depict: normalizeText(params.depict),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaMatSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    warehouseId:
+      formData.warehouseId !== undefined && formData.warehouseId !== null && formData.warehouseId !== ''
+        ? normalizeNumber(formData.warehouseId)
+        : void 0,
+    areaId:
+      formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+        ? normalizeNumber(formData.areaId)
+        : void 0,
+    depict: normalizeText(formData.depict) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocAreaMatRelationBindPayload(formData = {}) {
+  return {
+    areaMatId:
+      formData.areaMatId !== undefined && formData.areaMatId !== null && formData.areaMatId !== ''
+        ? normalizeNumber(formData.areaMatId)
+        : void 0,
+    warehouseId:
+      formData.warehouseId !== undefined && formData.warehouseId !== null && formData.warehouseId !== ''
+        ? normalizeNumber(formData.warehouseId)
+        : void 0,
+    areaId:
+      formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+        ? normalizeNumber(formData.areaId)
+        : void 0,
+    groupId:
+      formData.groupId !== undefined && formData.groupId !== null && formData.groupId !== ''
+        ? normalizeNumber(formData.groupId)
+        : void 0,
+    matnrId: normalizeMultiIds(formData.matnrId),
+    typeId: normalizeMultiIds(formData.typeId),
+    locId: normalizeMultiIds(formData.locId)
+  }
+}
+
+export function fetchLocAreaMatPage(params = {}) {
+  return request.post({
+    url: '/locAreaMat/page',
+    params: buildLocAreaMatPageParams(params)
+  })
+}
+
+export function fetchLocAreaMatList() {
+  return request.post({
+    url: '/locAreaMat/list',
+    data: {}
+  })
+}
+
+export function fetchGetLocAreaMatMany(ids) {
+  return request.post({
+    url: `/locAreaMat/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaMatDetail(id) {
+  return request.get({
+    url: `/locAreaMat/${id}`
+  })
+}
+
+export function fetchSaveLocAreaMat(params = {}) {
+  return request.post({
+    url: '/locAreaMat/save',
+    params: buildLocAreaMatSavePayload(params)
+  })
+}
+
+export function fetchUpdateLocAreaMat(params = {}) {
+  return request.post({
+    url: '/locAreaMat/update',
+    params: buildLocAreaMatSavePayload(params)
+  })
+}
+
+export function fetchDeleteLocAreaMat(ids) {
+  return request.post({
+    url: `/locAreaMat/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchLocAreaMatQuery(condition = '') {
+  return request.post({
+    url: '/locAreaMat/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportLocAreaMatReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locAreaMat/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function fetchLocAreaMatRelaPage(params = {}) {
+  return request.post({
+    url: '/locAreaMatRela/page',
+    params: buildLocAreaMatPageParams(params)
+  })
+}
+
+export function fetchGetLocAreaMatRelaMany(ids) {
+  return request.post({
+    url: `/locAreaMatRela/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaMatRelaDetail(id) {
+  return request.get({
+    url: `/locAreaMatRela/${id}`
+  })
+}
+
+export function fetchDeleteLocAreaMatRela(ids) {
+  return request.post({
+    url: `/locAreaMatRela/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaMatRelaGroups(id) {
+  return request.get({
+    url: `/locAreaMatRela/groups/${id}`
+  })
+}
+
+export function fetchGetLocAreaMatRelaLocType(id) {
+  return request.get({
+    url: `/locAreaMatRela/locType/${id}`
+  })
+}
+
+export function fetchBindLocAreaMatByMatnr(payload = {}) {
+  return request.post({
+    url: '/locAreaMatRela/matnr/bind',
+    params: buildLocAreaMatRelationBindPayload(payload)
+  })
+}
+
diff --git a/rsf-design/src/api/loc-area-rela.js b/rsf-design/src/api/loc-area-rela.js
new file mode 100644
index 0000000..f804761
--- /dev/null
+++ b/rsf-design/src/api/loc-area-rela.js
@@ -0,0 +1,157 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocAreaRelaPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildLocAreaRelaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    locAreaId:
+      params.locAreaId !== undefined && params.locAreaId !== null && params.locAreaId !== ''
+        ? normalizeNumber(params.locAreaId)
+        : void 0,
+    locId:
+      params.locId !== undefined && params.locId !== null && params.locId !== ''
+        ? normalizeNumber(params.locId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
+  )
+}
+
+export function buildLocAreaRelaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(formData.locAreaId !== undefined && formData.locAreaId !== null && formData.locAreaId !== ''
+      ? { locAreaId: normalizeNumber(formData.locAreaId) }
+      : {}),
+    ...(formData.locId !== undefined && formData.locId !== null && formData.locId !== ''
+      ? { locId: normalizeNumber(formData.locId) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchLocAreaRelaPage(params = {}) {
+  return request.post({
+    url: '/locAreaRela/page',
+    params: buildLocAreaRelaPageParams(params)
+  })
+}
+
+export function fetchLocAreaRelaList() {
+  return request.post({
+    url: '/locAreaRela/list',
+    data: {}
+  })
+}
+
+export function fetchLocAreaRelaMany(ids) {
+  return request.post({
+    url: `/locAreaRela/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaRelaDetail(id) {
+  return request.get({
+    url: `/locAreaRela/${id}`
+  })
+}
+
+export function fetchSaveLocAreaRela(params = {}) {
+  return request.post({
+    url: '/locAreaRela/save',
+    params: buildLocAreaRelaSavePayload(params)
+  })
+}
+
+export function fetchUpdateLocAreaRela(params = {}) {
+  return request.post({
+    url: '/locAreaRela/update',
+    params: buildLocAreaRelaSavePayload(params)
+  })
+}
+
+export function fetchDeleteLocAreaRela(ids) {
+  return request.post({
+    url: `/locAreaRela/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchLocAreaRelaQuery(condition = '') {
+  return request.post({
+    url: '/locAreaRela/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportLocAreaRelaReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locAreaRela/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/loc-area.js b/rsf-design/src/api/loc-area.js
new file mode 100644
index 0000000..bf345ae
--- /dev/null
+++ b/rsf-design/src/api/loc-area.js
@@ -0,0 +1,142 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocAreaPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildLocAreaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? Number(params.areaId)
+        : void 0,
+    name: normalizeText(params.name),
+    code: normalizeText(params.code),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+      ? { areaId: Number(formData.areaId) }
+      : {}),
+    name: normalizeText(formData.name) || '',
+    code: normalizeText(formData.code) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchLocAreaPage(params = {}) {
+  return request.post({
+    url: '/locArea/page',
+    params: buildLocAreaPageParams(params)
+  })
+}
+
+export function fetchLocAreaList() {
+  return request.post({
+    url: '/locArea/list',
+    data: {}
+  })
+}
+
+export function fetchLocAreaMany(ids) {
+  return request.post({
+    url: `/locArea/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocAreaDetail(id) {
+  return request.get({
+    url: `/locArea/${id}`
+  })
+}
+
+export function fetchSaveLocArea(params = {}) {
+  return request.post({
+    url: '/locArea/save',
+    params: buildLocAreaSavePayload(params)
+  })
+}
+
+export function fetchUpdateLocArea(params = {}) {
+  return request.post({
+    url: '/locArea/update',
+    params: buildLocAreaSavePayload(params)
+  })
+}
+
+export function fetchDeleteLocArea(ids) {
+  return request.post({
+    url: `/locArea/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchLocAreaQuery(condition = '') {
+  return request.post({
+    url: '/locArea/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportLocAreaReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locArea/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/loc-dead-report.js b/rsf-design/src/api/loc-dead-report.js
new file mode 100644
index 0000000..bff09c0
--- /dev/null
+++ b/rsf-design/src/api/loc-dead-report.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocDeadReportPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchLocDeadReportPage(params = {}) {
+  return request.post({
+    url: '/locDeadReport/page',
+    params: buildLocDeadReportPageParams(params)
+  })
+}
+
+export function fetchGetLocDeadReportDetail(id) {
+  return request.get({
+    url: `/locDeadReport/${id}`
+  })
+}
+
+export function fetchGetLocDeadReportMany(ids) {
+  return request.post({
+    url: `/locDeadReport/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportLocDeadReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locDeadReport/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/loc-item.js b/rsf-design/src/api/loc-item.js
new file mode 100644
index 0000000..f981f08
--- /dev/null
+++ b/rsf-design/src/api/loc-item.js
@@ -0,0 +1,81 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function withTextParam(target, key, value) {
+  const normalized = normalizeText(value)
+  if (normalized) {
+    target[key] = normalized
+  }
+}
+
+function withNumberParam(target, key, value) {
+  const normalized = normalizeNumber(value)
+  if (normalized !== null) {
+    target[key] = normalized
+  }
+}
+
+export function buildLocItemPageParams(params = {}) {
+  const payload = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  ;[
+    'condition',
+    'type',
+    'maktx',
+    'matnrCode',
+    'trackCode',
+    'unit',
+    'batch',
+    'splrBatch',
+    'spec',
+    'model',
+    'fieldsIndex',
+    'memo'
+  ].forEach((key) => withTextParam(payload, key, params[key]))
+
+  ;['locId', 'orderId', 'orderItemId', 'wkType', 'matnrId', 'anfme', 'status'].forEach((key) =>
+    withNumberParam(payload, key, params[key])
+  )
+
+  if (params.timeStart) {
+    payload.timeStart = params.timeStart
+  }
+  if (params.timeEnd) {
+    payload.timeEnd = params.timeEnd
+  }
+
+  return payload
+}
+
+export function fetchLocItemPage(params = {}) {
+  return request.post({
+    url: '/locItem/page',
+    params: buildLocItemPageParams(params)
+  })
+}
+
+export function fetchLocItemDetail(id) {
+  return request.get({
+    url: `/locItem/${id}`
+  })
+}
+
+export function fetchEnabledFields() {
+  return request.get({
+    url: '/fields/enable/list'
+  })
+}
diff --git a/rsf-design/src/api/loc-preview.js b/rsf-design/src/api/loc-preview.js
new file mode 100644
index 0000000..e016e31
--- /dev/null
+++ b/rsf-design/src/api/loc-preview.js
@@ -0,0 +1,51 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+export function buildLocPreviewPageParams(params = {}) {
+  const condition = normalizeText(params.condition)
+  const code = normalizeText(params.code)
+  const barcode = normalizeText(params.barcode)
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(condition ? { condition } : {}),
+    ...(code ? { code } : {}),
+    ...(barcode ? { barcode } : {})
+  }
+}
+
+export function buildLocPreviewItemsPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.locId !== undefined ? { locId: params.locId } : {})
+  }
+}
+
+export function fetchLocPreviewPage(params = {}) {
+  return request.post({
+    url: '/locPreview/page',
+    params: buildLocPreviewPageParams(params)
+  })
+}
+
+export function fetchLocPreviewDetail(id) {
+  return request.get({
+    url: `/locPreview/${id}`
+  })
+}
+
+export function fetchLocPreviewItemsPage(params = {}) {
+  return request.post({
+    url: '/locItem/page',
+    params: buildLocPreviewItemsPageParams(params)
+  })
+}
+
+export function fetchEnabledFields() {
+  return request.get({ url: '/fields/enable/list' })
+}
diff --git a/rsf-design/src/api/loc-revise.js b/rsf-design/src/api/loc-revise.js
new file mode 100644
index 0000000..a04360b
--- /dev/null
+++ b/rsf-design/src/api/loc-revise.js
@@ -0,0 +1,139 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) {
+          return false
+        }
+        if (value === undefined || value === null) {
+          return false
+        }
+        if (typeof value === 'string' && value.trim() === '') {
+          return false
+        }
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocRevisePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildReviseLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildReviseLogItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchLocRevisePage(params = {}) {
+  return request.post({
+    url: '/locRevise/page',
+    params: buildLocRevisePageParams(params)
+  })
+}
+
+export function fetchGetLocReviseDetail(id) {
+  return request.get({
+    url: `/locRevise/${id}`
+  })
+}
+
+export function fetchSaveLocRevise(payload = {}) {
+  return request.post({
+    url: '/locRevise/save',
+    data: payload,
+    showSuccessMessage: true
+  })
+}
+
+export function fetchUpdateLocRevise(payload = {}) {
+  return request.post({
+    url: '/locRevise/update',
+    data: payload,
+    showSuccessMessage: true
+  })
+}
+
+export function fetchDeleteLocRevise(ids) {
+  return request.post({
+    url: `/locRevise/remove/${normalizeIds(ids)}`,
+    showSuccessMessage: true
+  })
+}
+
+export function fetchGetLocReviseMany(ids) {
+  return request.post({
+    url: `/locRevise/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchReviseLogPage(params = {}) {
+  return request.post({
+    url: '/reviseLog/page',
+    params: buildReviseLogPageParams(params)
+  })
+}
+
+export function fetchReviseLogItemPage(params = {}) {
+  return request.post({
+    url: '/reviseLogItem/page',
+    params: buildReviseLogItemPageParams(params)
+  })
+}
+
+export function fetchCompleteLocRevise(id) {
+  return request.post({
+    url: `/reviseLog/complete/${id}`,
+    showSuccessMessage: true
+  })
+}
+
+export function fetchWarehouseAreasList() {
+  return request.post({
+    url: '/warehouseAreas/list',
+    data: {}
+  })
+}
+
+export async function fetchExportLocReviseReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locRevise/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/loc-type.js b/rsf-design/src/api/loc-type.js
new file mode 100644
index 0000000..3726026
--- /dev/null
+++ b/rsf-design/src/api/loc-type.js
@@ -0,0 +1,139 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildLocTypePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildLocTypeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    uuid: normalizeText(params.uuid),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    regex: normalizeText(params.regex),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocTypeSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    regex: normalizeText(formData.regex) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchLocTypePage(params = {}) {
+  return request.post({
+    url: '/locType/page',
+    params: buildLocTypePageParams(params)
+  })
+}
+
+export function fetchLocTypeList() {
+  return request.post({
+    url: '/locType/list',
+    data: {}
+  })
+}
+
+export function fetchLocTypeMany(ids) {
+  return request.post({
+    url: `/locType/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchGetLocTypeDetail(id) {
+  return request.get({
+    url: `/locType/${id}`
+  })
+}
+
+export function fetchSaveLocType(params = {}) {
+  return request.post({
+    url: '/locType/save',
+    params: buildLocTypeSavePayload(params)
+  })
+}
+
+export function fetchUpdateLocType(params = {}) {
+  return request.post({
+    url: '/locType/update',
+    params: buildLocTypeSavePayload(params)
+  })
+}
+
+export function fetchDeleteLocType(ids) {
+  return request.post({
+    url: `/locType/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchLocTypeQuery(condition = '') {
+  return request.post({
+    url: '/locType/query',
+    data: {},
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportLocTypeReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/locType/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/matnr-group.js b/rsf-design/src/api/matnr-group.js
new file mode 100644
index 0000000..f65af69
--- /dev/null
+++ b/rsf-design/src/api/matnr-group.js
@@ -0,0 +1,154 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const normalized = Number(value)
+  return Number.isNaN(normalized) ? void 0 : normalized
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
+  )
+}
+
+export function buildMatnrGroupPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildMatnrGroupSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    parCode: normalizeText(params.parCode),
+    memo: normalizeText(params.memo),
+    sort: normalizeNumber(params.sort),
+    status: normalizeNumber(params.status),
+    parentId: normalizeNumber(params.parentId)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildMatnrGroupSavePayload(params = {}) {
+  return {
+    ...(params.id !== undefined && params.id !== null && params.id !== ''
+      ? { id: Number(params.id) }
+      : {}),
+    parentId:
+      params.parentId !== undefined && params.parentId !== null && params.parentId !== ''
+        ? Number(params.parentId)
+        : 0,
+    parCode: normalizeText(params.parCode) || '',
+    code: normalizeText(params.code) || '',
+    name: normalizeText(params.name) || '',
+    sort:
+      params.sort !== undefined && params.sort !== null && params.sort !== ''
+        ? Number(params.sort)
+        : 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : 1,
+    memo: normalizeText(params.memo) || ''
+  }
+}
+
+export function fetchMatnrGroupPage(params = {}) {
+  return request.post({
+    url: '/matnrGroup/page',
+    params: buildMatnrGroupPageParams(params)
+  })
+}
+
+export function fetchGetMatnrGroupDetail(id) {
+  return request.get({
+    url: `/matnrGroup/${id}`
+  })
+}
+
+export function fetchGetMatnrGroupMany(ids) {
+  return request.post({
+    url: `/matnrGroup/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveMatnrGroup(params = {}) {
+  return request.post({
+    url: '/matnrGroup/save',
+    params: buildMatnrGroupSavePayload(params)
+  })
+}
+
+export function fetchUpdateMatnrGroup(params = {}) {
+  return request.post({
+    url: '/matnrGroup/update',
+    params: buildMatnrGroupSavePayload(params)
+  })
+}
+
+export function fetchDeleteMatnrGroup(ids) {
+  return request.post({
+    url: `/matnrGroup/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchMatnrGroupQuery(condition = '') {
+  return request.post({
+    url: '/matnrGroup/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchMatnrGroupTree(params = {}) {
+  return request.post({
+    url: '/matnrGroup/tree',
+    params: {
+      condition: normalizeText(params.condition)
+    }
+  })
+}
+
+export async function fetchExportMatnrGroupReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/matnrGroup/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/out-bound.js b/rsf-design/src/api/out-bound.js
new file mode 100644
index 0000000..0eded96
--- /dev/null
+++ b/rsf-design/src/api/out-bound.js
@@ -0,0 +1,103 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeOptionalText(value) {
+  const text = normalizeText(value)
+  return text === '-' ? '' : text
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return value
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : value
+}
+
+function compactDefinedFields(record = {}) {
+  return Object.fromEntries(
+    Object.entries(record).filter(([, value]) => value !== undefined && value !== null && value !== '')
+  )
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? normalizeText(value) : value])
+  )
+}
+
+export function buildOutBoundInventoryPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchOutboundInventoryPage(params = {}) {
+  return request.post({
+    url: '/locItem/useO/page',
+    params: buildOutBoundInventoryPageParams(params)
+  })
+}
+
+export function fetchGetOutboundInventoryDetail(id) {
+  return request.get({
+    url: `/locItem/${id}`
+  })
+}
+
+export function fetchGenerateOutboundTask(payload = {}) {
+  return request.post({
+    url: '/locItem/generate/task',
+    data: payload
+  })
+}
+
+export function fetchOutboundSiteList(params = {}) {
+  return request.post({
+    url: '/selectStaList/list',
+    params: {
+      type: params.type || [101, 103]
+    }
+  })
+}
+
+export function normalizeOutboundTaskPayload(payload = {}) {
+  return {
+    ...payload,
+    siteNo: normalizeOptionalText(payload.siteNo),
+    memo: normalizeText(payload.memo),
+    items: Array.isArray(payload.items)
+      ? payload.items.map((item) =>
+          compactDefinedFields({
+            ...item,
+            id: normalizeNumber(item.id),
+            locId: normalizeNumber(item.locId),
+            orderId: normalizeNumber(item.orderId),
+            orderItemId: normalizeNumber(item.orderItemId),
+            wkType: normalizeNumber(item.wkType),
+            matnrId: normalizeNumber(item.matnrId),
+            outQty: Number(normalizeNumber(item.outQty) || 0),
+            anfme: Number(normalizeNumber(item.anfme) || 0),
+            workQty: Number(normalizeNumber(item.workQty) || 0),
+            qty: Number(normalizeNumber(item.qty) || 0),
+            channel: normalizeNumber(item.channel),
+            status: normalizeNumber(item.status),
+            siteNo: normalizeOptionalText(item.siteNo),
+            memo: normalizeOptionalText(item.memo)
+          })
+        )
+      : []
+  }
+}
diff --git a/rsf-design/src/api/out-statistic-item.js b/rsf-design/src/api/out-statistic-item.js
new file mode 100644
index 0000000..e6bd055
--- /dev/null
+++ b/rsf-design/src/api/out-statistic-item.js
@@ -0,0 +1,43 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildOutStatisticItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    taskType: normalizeNumber(params.taskType, 101),
+    taskStatus: normalizeNumber(params.taskStatus, 200),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'taskType', 'taskStatus'])
+  }
+}
+
+export function fetchOutStatisticItemPage(params = {}) {
+  return request.post({
+    url: '/outStatisticItem/page',
+    params: buildOutStatisticItemPageParams(params)
+  })
+}
diff --git a/rsf-design/src/api/out-statistic.js b/rsf-design/src/api/out-statistic.js
new file mode 100644
index 0000000..f644967
--- /dev/null
+++ b/rsf-design/src/api/out-statistic.js
@@ -0,0 +1,43 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildOutStatisticPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    taskType: normalizeNumber(params.taskType, 101),
+    taskStatus: normalizeNumber(params.taskStatus, 200),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'taskType', 'taskStatus'])
+  }
+}
+
+export function fetchOutStatisticPage(params = {}) {
+  return request.post({
+    url: '/outStatistic/page',
+    params: buildOutStatisticPageParams(params)
+  })
+}
diff --git a/rsf-design/src/api/out-stock-item.js b/rsf-design/src/api/out-stock-item.js
new file mode 100644
index 0000000..591471d
--- /dev/null
+++ b/rsf-design/src/api/out-stock-item.js
@@ -0,0 +1,97 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+
+  return String(ids).trim()
+}
+
+export function buildOutStockItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'orderCode',
+    'poCode',
+    'platItemId',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'splrBatch',
+    'trackCode',
+    'barcode',
+    'fieldsIndex'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    const numericStatus = Number(params.status)
+    if (!Number.isNaN(numericStatus)) {
+      result.status = numericStatus
+    }
+  }
+
+  return result
+}
+
+export function buildOutStockItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutStockItemSearchParams(params)
+  }
+}
+
+export function fetchOutStockItemPage(params = {}) {
+  return request.post({
+    url: '/outStockItem/page',
+    params: buildOutStockItemPageParams(params)
+  })
+}
+
+export function fetchOutStockItemList(params = {}) {
+  return request.post({
+    url: '/outStockItem/list',
+    params: buildOutStockItemSearchParams(params)
+  })
+}
+
+export function fetchGetOutStockItemDetail(id) {
+  return request.get({
+    url: `/outStockItem/${id}`
+  })
+}
+
+export function fetchGetOutStockItemMany(ids) {
+  return request.post({
+    url: `/outStockItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportOutStockItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/outStockItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/out-stock.js b/rsf-design/src/api/out-stock.js
new file mode 100644
index 0000000..9e2d9e8
--- /dev/null
+++ b/rsf-design/src/api/out-stock.js
@@ -0,0 +1,75 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => String(item).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildOutStockPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    type: 'out',
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchOutStockPage(params = {}) {
+  return request.post({ url: '/outStock/page', params: buildOutStockPageParams(params) })
+}
+
+export function fetchGetOutStockDetail(id) {
+  return request.get({ url: `/outStock/${id}` })
+}
+
+export function fetchGetOutStockMany(ids) {
+  return request.post({ url: `/outStock/many/${normalizeIds(ids)}` })
+}
+
+export function fetchDeleteOutStock(ids) {
+  return request.post({ url: `/outStock/remove/${normalizeIds(ids)}` })
+}
+
+export function fetchCompleteOutStock(id) {
+  return request.get({ url: `/outStock/complete/${id}` })
+}
+
+export function fetchCancelOutStock(id) {
+  return request.get({ url: `/outStock/cancel/${id}` })
+}
+
+export async function fetchExportOutStockReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/outStock/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/preparation-item.js b/rsf-design/src/api/preparation-item.js
new file mode 100644
index 0000000..4a0b7e2
--- /dev/null
+++ b/rsf-design/src/api/preparation-item.js
@@ -0,0 +1,78 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) {
+          return false
+        }
+        if (value === null || value === undefined) {
+          return false
+        }
+        if (typeof value === 'string' && value.trim() === '') {
+          return false
+        }
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildPreparationItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderId !== '' && params.orderId !== undefined && params.orderId !== null
+      ? { orderId: Number(params.orderId) }
+      : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'orderId'])
+  }
+}
+
+export function fetchPreparationItemPage(params = {}) {
+  return request.post({
+    url: '/outStockItem/page',
+    params: buildPreparationItemPageParams(params)
+  })
+}
+
+export function fetchGetPreparationItemDetail(id) {
+  return request.get({
+    url: `/outStockItem/${id}`
+  })
+}
+
+export function fetchGetPreparationItemMany(ids) {
+  return request.post({
+    url: `/outStockItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportPreparationItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/outStockItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/preparation.js b/rsf-design/src/api/preparation.js
new file mode 100644
index 0000000..0049199
--- /dev/null
+++ b/rsf-design/src/api/preparation.js
@@ -0,0 +1,90 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => String(item).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildPreparationPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildPreparationItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderId !== undefined ? { orderId: Number(params.orderId) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'orderId'])
+  }
+}
+
+export function fetchPreparationPage(params = {}) {
+  return request.post({ url: '/preparation/page', params: buildPreparationPageParams(params) })
+}
+
+export function fetchPreparationItemPage(params = {}) {
+  return request.post({
+    url: '/outStockItem/page',
+    params: buildPreparationItemPageParams(params)
+  })
+}
+
+export function fetchGetPreparationDetail(id) {
+  return request.get({ url: `/preparation/${id}` })
+}
+
+export function fetchGetPreparationMany(ids) {
+  return request.post({ url: `/preparation/many/${normalizeIds(ids)}` })
+}
+
+export function fetchDeletePreparation(ids) {
+  return request.post({ url: `/preparation/remove/${normalizeIds(ids)}` })
+}
+
+export function fetchCompletePreparation(id) {
+  return request.get({ url: `/preparation/complete/${id}` })
+}
+
+export function fetchCancelPreparation(id) {
+  return request.get({ url: `/preparation/cancel/${id}` })
+}
+
+export async function fetchExportPreparationReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/preparation/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/purchase-item.js b/rsf-design/src/api/purchase-item.js
new file mode 100644
index 0000000..0b41c13
--- /dev/null
+++ b/rsf-design/src/api/purchase-item.js
@@ -0,0 +1,95 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) {
+          return false
+        }
+        if (value === null || value === undefined) {
+          return false
+        }
+        if (typeof value === 'string' && value.trim() === '') {
+          return false
+        }
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildPurchaseItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildPurchaseItemSearchParams(params = {}) {
+  return filterParams(params)
+}
+
+export function fetchPurchaseItemPage(params = {}) {
+  return request.post({
+    url: '/purchaseItem/page',
+    params: buildPurchaseItemPageParams(params)
+  })
+}
+
+export function fetchPurchaseItemList(params = {}) {
+  return request.post({
+    url: '/purchaseItem/list',
+    data: buildPurchaseItemSearchParams(params)
+  })
+}
+
+export function fetchPurchaseItemMany(ids) {
+  return request.post({
+    url: `/purchaseItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchPurchaseItemDetail(id) {
+  return request.get({
+    url: `/purchaseItem/${id}`
+  })
+}
+
+export function fetchPurchaseItemQuery(condition = '') {
+  return request.post({
+    url: '/purchaseItem/query',
+    params: {
+      condition: normalizeText(condition)
+    }
+  })
+}
+
+export async function fetchExportPurchaseItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/purchaseItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/purchase.js b/rsf-design/src/api/purchase.js
new file mode 100644
index 0000000..bfa56bc
--- /dev/null
+++ b/rsf-design/src/api/purchase.js
@@ -0,0 +1,118 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildPurchasePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildPurchaseItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchPurchasePage(params = {}) {
+  return request.post({
+    url: '/purchase/page',
+    params: buildPurchasePageParams(params)
+  })
+}
+
+export function fetchPurchaseList() {
+  return request.post({
+    url: '/purchase/list',
+    data: {}
+  })
+}
+
+export function fetchPurchaseMany(ids) {
+  return request.post({
+    url: `/purchase/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchPurchaseDetail(id) {
+  return request.get({
+    url: `/purchase/${id}`
+  })
+}
+
+export function fetchSavePurchase(params = {}) {
+  return request.post({
+    url: '/purchase/save',
+    params
+  })
+}
+
+export function fetchUpdatePurchase(params = {}) {
+  return request.post({
+    url: '/purchase/update',
+    params
+  })
+}
+
+export function fetchDeletePurchase(ids) {
+  return request.post({
+    url: `/purchase/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchPurchaseQuery(condition = '') {
+  return request.post({
+    url: '/purchase/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchPurchaseItemPage(params = {}) {
+  return request.post({
+    url: '/purchaseItem/page',
+    params: buildPurchaseItemPageParams(params)
+  })
+}
+
+export async function fetchExportPurchaseReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/purchase/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/qly-inspect.js b/rsf-design/src/api/qly-inspect.js
new file mode 100644
index 0000000..1d37dde
--- /dev/null
+++ b/rsf-design/src/api/qly-inspect.js
@@ -0,0 +1,76 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+export function buildQlyInspectPageParams(params = {}) {
+  const entries = Object.entries(params).filter(([key, value]) => {
+    if (['current', 'pageSize', 'size'].includes(key)) {
+      return false
+    }
+    if (value === undefined || value === null) {
+      return false
+    }
+    if (typeof value === 'string' && value.trim() === '') {
+      return false
+    }
+    return true
+  })
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...Object.fromEntries(entries.map(([key, value]) => [key, normalizeText(value)]))
+  }
+}
+
+export function buildQlyInspectItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.ispectId !== undefined ? { ispectId: params.ispectId } : {})
+  }
+}
+
+export function fetchQlyInspectPage(params = {}) {
+  return request.post({
+    url: '/qlyInspect/page',
+    params: buildQlyInspectPageParams(params)
+  })
+}
+
+export function fetchQlyInspectItemPage(params = {}) {
+  return request.post({
+    url: '/qlyIsptItem/page',
+    params: buildQlyInspectItemPageParams(params)
+  })
+}
+
+export function fetchGetQlyInspectMany(ids) {
+  const normalizedIds = normalizeIds(ids)
+  return request.post({
+    url: `/qlyInspect/many/${normalizedIds}`
+  })
+}
+
+export async function fetchExportQlyInspectReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/qlyInspect/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/qly-ispt-item.js b/rsf-design/src/api/qly-ispt-item.js
new file mode 100644
index 0000000..9364484
--- /dev/null
+++ b/rsf-design/src/api/qly-ispt-item.js
@@ -0,0 +1,110 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function normalizeNumericParam(value) {
+  if (value === '' || value === null || value === undefined) {
+    return undefined
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : value
+}
+
+export function buildQlyIsptItemPageParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  ;[
+    'condition',
+    'matnrCode',
+    'maktx',
+    'label',
+    'splrName',
+    'splrBatch',
+    'stockBatch',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'picPath',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value !== undefined && value !== null && value !== '') {
+      result[key] = value
+    }
+  })
+
+  ;['ispectId', 'rcptQty', 'dlyQty', 'disQty', 'safeQty', 'status'].forEach((key) => {
+    const value = normalizeNumericParam(params[key])
+    if (value !== undefined && value !== '') {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildQlyIsptItemResultPageParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  const inspectId = normalizeNumericParam(params.id ?? params.ispectId)
+  if (inspectId !== undefined && inspectId !== '') {
+    result.id = inspectId
+  }
+
+  return result
+}
+
+export function fetchQlyIsptItemPage(params = {}) {
+  return request.post({
+    url: '/qlyIsptItem/page',
+    params: buildQlyIsptItemPageParams(params)
+  })
+}
+
+export function fetchQlyIsptItemResultPage(params = {}) {
+  return request.post({
+    url: '/qlyIsptItem/ispt/result/page',
+    params: buildQlyIsptItemResultPageParams(params)
+  })
+}
+
+export function fetchGetQlyIsptItemDetail(id) {
+  return request.get({
+    url: `/qlyIsptItem/${id}`
+  })
+}
+
+export function fetchGetQlyIsptItemMany(ids) {
+  return request.post({
+    url: `/qlyIsptItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportQlyIsptItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/qlyIsptItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/revise-log-item.js b/rsf-design/src/api/revise-log-item.js
new file mode 100644
index 0000000..0a23b61
--- /dev/null
+++ b/rsf-design/src/api/revise-log-item.js
@@ -0,0 +1,61 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) {
+          return false
+        }
+        if (value === undefined || value === null) {
+          return false
+        }
+        if (typeof value === 'string' && value.trim() === '') {
+          return false
+        }
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildReviseLogItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchReviseLogItemPage(params = {}) {
+  return request.post({
+    url: '/reviseLogItem/page',
+    params: buildReviseLogItemPageParams(params)
+  })
+}
+
+export function fetchGetReviseLogItemDetail(id) {
+  return request.get({
+    url: `/reviseLogItem/${id}`
+  })
+}
+
+export function fetchGetReviseLogItemMany(ids) {
+  return request.post({
+    url: `/reviseLogItem/many/${normalizeIds(ids)}`
+  })
+}
diff --git a/rsf-design/src/api/revise-log.js b/rsf-design/src/api/revise-log.js
new file mode 100644
index 0000000..08a9c6b
--- /dev/null
+++ b/rsf-design/src/api/revise-log.js
@@ -0,0 +1,61 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) {
+          return false
+        }
+        if (value === undefined || value === null) {
+          return false
+        }
+        if (typeof value === 'string' && value.trim() === '') {
+          return false
+        }
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildReviseLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchReviseLogPage(params = {}) {
+  return request.post({
+    url: '/reviseLog/page',
+    params: buildReviseLogPageParams(params)
+  })
+}
+
+export function fetchGetReviseLogDetail(id) {
+  return request.get({
+    url: `/reviseLog/${id}`
+  })
+}
+
+export function fetchGetReviseLogMany(ids) {
+  return request.post({
+    url: `/reviseLog/many/${normalizeIds(ids)}`
+  })
+}
diff --git a/rsf-design/src/api/serial-rule-item.js b/rsf-design/src/api/serial-rule-item.js
new file mode 100644
index 0000000..e365095
--- /dev/null
+++ b/rsf-design/src/api/serial-rule-item.js
@@ -0,0 +1,157 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
+  )
+}
+
+export function buildSerialRuleItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildSerialRuleItemSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.ruleId !== void 0 && formData.ruleId !== null && formData.ruleId !== ''
+      ? { ruleId: Number(formData.ruleId) }
+      : {}),
+    wkType: normalizeText(formData.wkType) || '',
+    feildValue: normalizeText(formData.feildValue) || '',
+    ...(formData.len !== void 0 && formData.len !== null && formData.len !== ''
+      ? { len: Number(formData.len) }
+      : {}),
+    ...(formData.lenStr !== void 0 && formData.lenStr !== null && formData.lenStr !== ''
+      ? { lenStr: Number(formData.lenStr) }
+      : {}),
+    ...(formData.sort !== void 0 && formData.sort !== null && formData.sort !== ''
+      ? { sort: Number(formData.sort) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildSerialRuleItemSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    ruleId:
+      params.ruleId !== void 0 && params.ruleId !== null && params.ruleId !== ''
+        ? Number(params.ruleId)
+        : void 0,
+    wkType: normalizeText(params.wkType),
+    feildValue: normalizeText(params.feildValue),
+    len:
+      params.len !== void 0 && params.len !== null && params.len !== ''
+        ? Number(params.len)
+        : void 0,
+    lenStr:
+      params.lenStr !== void 0 && params.lenStr !== null && params.lenStr !== ''
+        ? Number(params.lenStr)
+        : void 0,
+    sort:
+      params.sort !== void 0 && params.sort !== null && params.sort !== ''
+        ? Number(params.sort)
+        : void 0,
+    status:
+      params.status !== void 0 && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function fetchSerialRuleItemPage(params = {}) {
+  return request.post({
+    url: '/serialRuleItem/page',
+    params: buildSerialRuleItemPageParams(params)
+  })
+}
+
+export function fetchGetSerialRuleItemDetail(id) {
+  return request.get({
+    url: `/serialRuleItem/${id}`
+  })
+}
+
+export function fetchGetSerialRuleItemMany(ids) {
+  return request.post({
+    url: `/serialRuleItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveSerialRuleItem(params = {}) {
+  return request.post({
+    url: '/serialRuleItem/save',
+    params: buildSerialRuleItemSavePayload(params)
+  })
+}
+
+export function fetchUpdateSerialRuleItem(params = {}) {
+  return request.post({
+    url: '/serialRuleItem/update',
+    params: buildSerialRuleItemSavePayload(params)
+  })
+}
+
+export function fetchDeleteSerialRuleItem(ids) {
+  return request.post({
+    url: `/serialRuleItem/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSerialRuleItemQuery(condition = '') {
+  return request.post({
+    url: '/serialRuleItem/query',
+    params: {
+      condition: normalizeText(condition)
+    }
+  })
+}
+
+export async function fetchExportSerialRuleItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/serialRuleItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/statistic-count.js b/rsf-design/src/api/statistic-count.js
new file mode 100644
index 0000000..00aec66
--- /dev/null
+++ b/rsf-design/src/api/statistic-count.js
@@ -0,0 +1,33 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+export function buildStatisticCountPageParams(params = {}) {
+  const entries = Object.entries(params).filter(([key, value]) => {
+    if (['current', 'pageSize', 'size'].includes(key)) {
+      return false
+    }
+    if (value === undefined || value === null) {
+      return false
+    }
+    if (typeof value === 'string' && value.trim() === '') {
+      return false
+    }
+    return true
+  })
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...Object.fromEntries(entries.map(([key, value]) => [key, normalizeText(value)]))
+  }
+}
+
+export function fetchStatisticCountPage(params = {}) {
+  return request.post({
+    url: '/statistic/num/page',
+    params: buildStatisticCountPageParams(params)
+  })
+}
diff --git a/rsf-design/src/api/stock-item.js b/rsf-design/src/api/stock-item.js
new file mode 100644
index 0000000..87e8c1e
--- /dev/null
+++ b/rsf-design/src/api/stock-item.js
@@ -0,0 +1,82 @@
+import request from '@/utils/http'
+import {
+  buildStockItemPageQueryParams,
+  buildStockItemSavePayload,
+  buildStockItemSearchParams
+} from '@/views/manager/stock-item/stockItemPage.helpers.js'
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+export function fetchStockItemPage(params = {}) {
+  return request.post({
+    url: '/stockItem/page',
+    params: buildStockItemPageQueryParams(params)
+  })
+}
+
+export function fetchStockItemList(params = {}) {
+  return request.post({
+    url: '/stockItem/list',
+    data: buildStockItemSearchParams(params)
+  })
+}
+
+export function fetchStockItemDetail(id) {
+  return request.get({
+    url: `/stockItem/${id}`
+  })
+}
+
+export function fetchStockItemMany(ids) {
+  return request.post({
+    url: `/stockItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchStockItemQuery(condition = '') {
+  return request.post({
+    url: '/stockItem/query',
+    params: {
+      condition
+    }
+  })
+}
+
+export function fetchSaveStockItem(params = {}) {
+  return request.post({
+    url: '/stockItem/save',
+    params: buildStockItemSavePayload(params)
+  })
+}
+
+export function fetchUpdateStockItem(params = {}) {
+  return request.post({
+    url: '/stockItem/update',
+    params: buildStockItemSavePayload(params)
+  })
+}
+
+export function fetchDeleteStockItem(ids) {
+  return request.post({
+    url: `/stockItem/remove/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportStockItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/stockItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/stock-transfer.js b/rsf-design/src/api/stock-transfer.js
new file mode 100644
index 0000000..530f688
--- /dev/null
+++ b/rsf-design/src/api/stock-transfer.js
@@ -0,0 +1,87 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((item) => String(item).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildStockTransferSourcePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    orderBy: 'create_time desc',
+    locCode: normalizeText(params.locCode || params.orgLoc || ''),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'locCode', 'orgLoc'])
+  }
+}
+
+export function buildStockTransferTargetLocPageParams(params = {}) {
+  const queryParams = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 50,
+    locCode: normalizeText(params.locCode || params.orgLoc || '')
+  }
+  const q = normalizeText(params.q)
+  if (q) {
+    queryParams.q = q
+  }
+  return {
+    ...queryParams,
+    ...filterParams(params, ['current', 'pageSize', 'size', 'locCode', 'orgLoc', 'q'])
+  }
+}
+
+export function buildStockTransferTaskPayload(params = {}) {
+  return {
+    orgLoc: normalizeText(params.orgLoc),
+    tarLoc: normalizeText(params.tarLoc),
+    memo: normalizeText(params.memo)
+  }
+}
+
+export function fetchStockTransferSourcePage(params = {}) {
+  return request.post({ url: '/locItem/useO/page', params: buildStockTransferSourcePageParams(params) })
+}
+
+export function fetchStockTransferSourceDetail(id) {
+  return request.get({ url: `/locItem/${id}` })
+}
+
+export function fetchStockTransferTargetLocPage(params = {}) {
+  return request.post({ url: '/loc/areaNoUse/page', params: buildStockTransferTargetLocPageParams(params) })
+}
+
+export function fetchStockTransferMoveTask(params = {}) {
+  return request.post({ url: '/locItem/move/task', params: buildStockTransferTaskPayload(params) })
+}
+
+export function fetchStockTransferEnabledFields() {
+  return request.get({ url: '/fields/enable/list' })
+}
+
+export { normalizeIds }
diff --git a/rsf-design/src/api/stock.js b/rsf-design/src/api/stock.js
new file mode 100644
index 0000000..8b9c39c
--- /dev/null
+++ b/rsf-design/src/api/stock.js
@@ -0,0 +1,66 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildStockPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchStockPage(params = {}) {
+  return request.post({
+    url: '/stock/page',
+    params: buildStockPageParams(params)
+  })
+}
+
+export function fetchGetStockDetail(id) {
+  return request.get({
+    url: `/stock/${id}`
+  })
+}
+
+export function fetchGetStockMany(ids) {
+  return request.post({
+    url: `/stock/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportStockReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/stock/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/subsystem-flow-template.js b/rsf-design/src/api/subsystem-flow-template.js
new file mode 100644
index 0000000..574947a
--- /dev/null
+++ b/rsf-design/src/api/subsystem-flow-template.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildSubsystemFlowTemplatePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchSubsystemFlowTemplatePage(params = {}) {
+  return request.post({
+    url: '/subsystemFlowTemplate/page',
+    params: buildSubsystemFlowTemplatePageParams(params)
+  })
+}
+
+export function fetchGetSubsystemFlowTemplateDetail(id) {
+  return request.get({
+    url: `/subsystemFlowTemplate/${id}`
+  })
+}
+
+export function fetchGetSubsystemFlowTemplateMany(ids) {
+  return request.post({
+    url: `/subsystemFlowTemplate/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportSubsystemFlowTemplateReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/subsystemFlowTemplate/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/system-manage.js b/rsf-design/src/api/system-manage.js
index f28243d..a9b0a8d 100644
--- a/rsf-design/src/api/system-manage.js
+++ b/rsf-design/src/api/system-manage.js
@@ -24,6 +24,161 @@
   }
 }
 
+export function buildOperationRecordPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.namespace !== undefined ? { namespace: params.namespace } : {}),
+    ...(params.url !== undefined ? { url: params.url } : {}),
+    ...(params.appkey !== undefined ? { appkey: params.appkey } : {}),
+    ...(params.clientIp !== undefined ? { clientIp: params.clientIp } : {}),
+    ...(params.request !== undefined ? { request: params.request } : {}),
+    ...(params.response !== undefined ? { response: params.response } : {}),
+    ...(params.spendTime !== undefined ? { spendTime: params.spendTime } : {}),
+    ...(params.result !== undefined ? { result: params.result } : {}),
+    ...(params.userId !== undefined ? { userId: params.userId } : {}),
+    ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildConfigPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.uuid !== undefined ? { uuid: params.uuid } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.flag !== undefined ? { flag: params.flag } : {}),
+    ...(params.type !== undefined ? { type: params.type } : {}),
+    ...(params.val !== undefined ? { val: params.val } : {}),
+    ...(params.content !== undefined ? { content: params.content } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildSerialRulePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.code !== undefined ? { code: params.code } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.delimit !== undefined ? { delimit: params.delimit } : {}),
+    ...(params.reset !== undefined ? { reset: params.reset } : {}),
+    ...(params.resetDep !== undefined ? { resetDep: params.resetDep } : {}),
+    ...(params.currValue !== undefined ? { currValue: params.currValue } : {}),
+    ...(params.lastCode !== undefined ? { lastCode: params.lastCode } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildDictTypePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.code !== undefined ? { code: params.code } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.description !== undefined ? { description: params.description } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildDictDataPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.dictTypeId !== undefined ? { dictTypeId: params.dictTypeId } : {}),
+    ...(params.dictTypeCode !== undefined ? { dictTypeCode: params.dictTypeCode } : {}),
+    ...(params.value !== undefined ? { value: params.value } : {}),
+    ...(params.label !== undefined ? { label: params.label } : {}),
+    ...(params.sort !== undefined ? { sort: params.sort } : {}),
+    ...(params.group !== undefined ? { group: params.group } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildWaveRulePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.code !== undefined ? { code: params.code } : {}),
+    ...(params.type !== undefined ? { type: params.type } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.timeStart !== undefined ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd !== undefined ? { timeEnd: params.timeEnd } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildFieldsPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.fields !== undefined ? { fields: params.fields } : {}),
+    ...(params.fieldsAlise !== undefined ? { fieldsAlise: params.fieldsAlise } : {}),
+    ...(params.unique !== undefined ? { unique: params.unique } : {}),
+    ...(params.flagEnable !== undefined ? { flagEnable: params.flagEnable } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildFieldsItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.uuid !== undefined ? { uuid: params.uuid } : {}),
+    ...(params.fieldsId !== undefined ? { fieldsId: params.fieldsId } : {}),
+    ...(params.value !== undefined ? { value: params.value } : {}),
+    ...(params.matnrId !== undefined ? { matnrId: params.matnrId } : {}),
+    ...(params.shiperId !== undefined ? { shiperId: params.shiperId } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildTenantPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.flag !== undefined ? { flag: params.flag } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
+export function buildHostPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.condition !== undefined ? { condition: params.condition } : {}),
+    ...(params.name !== undefined ? { name: params.name } : {}),
+    ...(params.status !== undefined ? { status: params.status } : {}),
+    ...(params.memo !== undefined ? { memo: params.memo } : {})
+  }
+}
+
 function fetchGetUserList(params) {
   return request.post({ url: '/user/page', params: buildUserListParams(params) })
 }
@@ -55,6 +210,208 @@
 
 function fetchGetRoleList(params) {
   return request.post({ url: '/role/page', params: buildRoleListParams(params) })
+}
+
+function fetchOperationRecordPage(params = {}) {
+  return request.post({ url: '/operationRecord/page', params: buildOperationRecordPageParams(params) })
+}
+
+function fetchConfigPage(params = {}) {
+  return request.post({ url: '/config/page', params: buildConfigPageParams(params) })
+}
+
+function fetchSerialRulePage(params = {}) {
+  return request.post({ url: '/serialRule/page', params: buildSerialRulePageParams(params) })
+}
+
+function fetchDictTypePage(params = {}) {
+  return request.post({ url: '/dictType/page', params: buildDictTypePageParams(params) })
+}
+
+function fetchDictDataPage(params = {}) {
+  return request.post({ url: '/dictData/page', params: buildDictDataPageParams(params) })
+}
+
+function fetchGetDictTypeDetail(id) {
+  return request.get({ url: `/dictType/${id}` })
+}
+
+function fetchSaveDictType(params) {
+  return request.post({ url: '/dictType/save', params })
+}
+
+function fetchUpdateDictType(params) {
+  return request.post({ url: '/dictType/update', params })
+}
+
+function fetchDeleteDictType(id) {
+  return request.post({ url: `/dictType/remove/${id}` })
+}
+
+function fetchWaveRulePage(params = {}) {
+  return request.post({ url: '/waveRule/page', params: buildWaveRulePageParams(params) })
+}
+
+function fetchGetWaveRuleDetail(id) {
+  return request.get({ url: `/waveRule/${id}` })
+}
+
+function fetchSaveWaveRule(params) {
+  return request.post({ url: '/waveRule/save', params })
+}
+
+function fetchUpdateWaveRule(params) {
+  return request.post({ url: '/waveRule/update', params })
+}
+
+function fetchDeleteWaveRule(id) {
+  return request.post({ url: `/waveRule/remove/${id}` })
+}
+
+function fetchFieldsPage(params = {}) {
+  return request.post({ url: '/fields/page', params: buildFieldsPageParams(params) })
+}
+
+function fetchGetFieldsDetail(id) {
+  return request.get({ url: `/fields/${id}` })
+}
+
+function fetchSaveFields(params) {
+  return request.post({ url: '/fields/save', params })
+}
+
+function fetchUpdateFields(params) {
+  return request.post({ url: '/fields/update', params })
+}
+
+function fetchDeleteFields(id) {
+  return request.post({ url: `/fields/remove/${id}` })
+}
+
+function fetchFieldsItemPage(params = {}) {
+  return request.post({ url: '/fieldsItem/page', params: buildFieldsItemPageParams(params) })
+}
+
+function fetchGetFieldsItemDetail(id) {
+  return request.get({ url: `/fieldsItem/${id}` })
+}
+
+function fetchSaveFieldsItem(params) {
+  return request.post({ url: '/fieldsItem/save', params })
+}
+
+function fetchUpdateFieldsItem(params) {
+  return request.post({ url: '/fieldsItem/update', params })
+}
+
+function fetchDeleteFieldsItem(id) {
+  return request.post({ url: `/fieldsItem/remove/${id}` })
+}
+
+function fetchTenantPage(params = {}) {
+  return request.post({ url: '/tenant/page', params: buildTenantPageParams(params) })
+}
+
+function fetchGetTenantDetail(id) {
+  return request.get({ url: `/tenant/${id}` })
+}
+
+function fetchInitTenant(params) {
+  return request.post({ url: '/tenant/init', params })
+}
+
+function fetchUpdateTenant(params) {
+  return request.post({ url: '/tenant/update', params })
+}
+
+function fetchDeleteTenant(id) {
+  return request.post({ url: `/tenant/remove/${id}` })
+}
+
+function fetchHostPage(params = {}) {
+  return request.post({ url: '/host/page', params: buildHostPageParams(params) })
+}
+
+function fetchGetHostDetail(id) {
+  return request.get({ url: `/host/${id}` })
+}
+
+function fetchSaveHost(params) {
+  return request.post({ url: '/host/save', params })
+}
+
+function fetchUpdateHost(params) {
+  return request.post({ url: '/host/update', params })
+}
+
+function fetchDeleteHost(id) {
+  return request.post({ url: `/host/remove/${id}` })
+}
+
+function fetchGetSerialRuleDetail(id) {
+  return request.get({ url: `/serialRule/${id}` })
+}
+
+function fetchSaveSerialRule(params) {
+  return request.post({ url: '/serialRule/save', params })
+}
+
+function fetchUpdateSerialRule(params) {
+  return request.post({ url: '/serialRule/update', params })
+}
+
+function fetchDeleteSerialRule(id) {
+  return request.post({ url: `/serialRule/remove/${id}` })
+}
+
+function fetchGetConfigDetail(id) {
+  return request.get({ url: `/config/${id}` })
+}
+
+function fetchSaveConfig(params) {
+  return request.post({ url: '/config/save', params })
+}
+
+function fetchUpdateConfig(params) {
+  return request.post({ url: '/config/update', params })
+}
+
+function fetchDeleteConfig(id) {
+  return request.post({ url: `/config/remove/${id}` })
+}
+
+function fetchGetOperationRecordDetail(id) {
+  return request.get({ url: `/operationRecord/${id}` })
+}
+
+function fetchDeleteOperationRecord(id) {
+  return request.post({ url: `/operationRecord/remove/${id}` })
+}
+
+function normalizeOperationRecordManyIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => normalizeLegacyId(id))
+      .filter((id) => id !== '')
+      .join(',')
+  }
+  return normalizeLegacyId(ids)
+}
+
+function fetchGetOperationRecordMany(ids) {
+  const normalizedIds = normalizeOperationRecordManyIds(ids)
+  return request.post({ url: `/operationRecord/many/${normalizedIds}` })
+}
+
+async function fetchExportOperationRecordReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/operationRecord/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
 }
 
 function fetchSaveRole(params) {
@@ -121,6 +478,22 @@
   return request.post({ url: '/dept/tree', params })
 }
 
+function fetchGetDeptDetail(id) {
+  return request.get({ url: `/dept/${id}` })
+}
+
+function fetchSaveDept(params) {
+  return request.post({ url: '/dept/save', params })
+}
+
+function fetchUpdateDept(params) {
+  return request.post({ url: '/dept/update', params })
+}
+
+function fetchDeleteDept(id) {
+  return request.post({ url: `/dept/remove/${id}` })
+}
+
 function fetchGetMenuTree(params = {}) {
   return request.post({ url: '/menu/tree', params })
 }
@@ -135,6 +508,22 @@
 
 function fetchDeleteMenu(id) {
   return request.post({ url: `/menu/remove/${id}` })
+}
+
+function fetchGetMenuPdaTree(params = {}) {
+  return request.post({ url: '/menuPda/tree', params })
+}
+
+function fetchSaveMenuPda(params) {
+  return request.post({ url: '/menuPda/save', params })
+}
+
+function fetchUpdateMenuPda(params) {
+  return request.post({ url: '/menuPda/update', params })
+}
+
+function fetchDeleteMenuPda(id) {
+  return request.post({ url: `/menuPda/remove/${id}` })
 }
 
 function assertAdminPasswordUpdatePayload(params) {
@@ -468,6 +857,52 @@
   fetchUpdateUserStatus,
   fetchGetUserDetail,
   fetchGetRoleList,
+  fetchOperationRecordPage,
+  fetchGetOperationRecordDetail,
+  fetchDeleteOperationRecord,
+  fetchGetOperationRecordMany,
+  fetchExportOperationRecordReport,
+  fetchConfigPage,
+  fetchGetConfigDetail,
+  fetchSaveConfig,
+  fetchUpdateConfig,
+  fetchDeleteConfig,
+  fetchSerialRulePage,
+  fetchGetSerialRuleDetail,
+  fetchSaveSerialRule,
+  fetchUpdateSerialRule,
+  fetchDeleteSerialRule,
+  fetchDictTypePage,
+  fetchGetDictTypeDetail,
+  fetchSaveDictType,
+  fetchUpdateDictType,
+  fetchDeleteDictType,
+  fetchDictDataPage,
+  fetchWaveRulePage,
+  fetchGetWaveRuleDetail,
+  fetchSaveWaveRule,
+  fetchUpdateWaveRule,
+  fetchDeleteWaveRule,
+  fetchFieldsPage,
+  fetchGetFieldsDetail,
+  fetchSaveFields,
+  fetchUpdateFields,
+  fetchDeleteFields,
+  fetchFieldsItemPage,
+  fetchGetFieldsItemDetail,
+  fetchSaveFieldsItem,
+  fetchUpdateFieldsItem,
+  fetchDeleteFieldsItem,
+  fetchTenantPage,
+  fetchGetTenantDetail,
+  fetchInitTenant,
+  fetchUpdateTenant,
+  fetchDeleteTenant,
+  fetchHostPage,
+  fetchGetHostDetail,
+  fetchSaveHost,
+  fetchUpdateHost,
+  fetchDeleteHost,
   fetchSaveRole,
   fetchUpdateRole,
   fetchDeleteRole,
@@ -477,10 +912,18 @@
   fetchExportRoleReport,
   fetchGetRoleMany,
   fetchGetDeptTree,
+  fetchGetDeptDetail,
+  fetchSaveDept,
+  fetchUpdateDept,
+  fetchDeleteDept,
   fetchGetMenuTree,
+  fetchGetMenuPdaTree,
   fetchSaveMenu,
+  fetchSaveMenuPda,
   fetchUpdateMenu,
+  fetchUpdateMenuPda,
   fetchDeleteMenu,
+  fetchDeleteMenuPda,
   fetchGetRoleScopeList,
   fetchGetRoleScopeTree,
   fetchUpdateRoleScope,
diff --git a/rsf-design/src/api/task-instance-node.js b/rsf-design/src/api/task-instance-node.js
new file mode 100644
index 0000000..9ef6dae
--- /dev/null
+++ b/rsf-design/src/api/task-instance-node.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskInstanceNodePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchTaskInstanceNodePage(params = {}) {
+  return request.post({
+    url: '/taskInstanceNode/page',
+    params: buildTaskInstanceNodePageParams(params)
+  })
+}
+
+export function fetchGetTaskInstanceNodeDetail(id) {
+  return request.get({
+    url: `/taskInstanceNode/${id}`
+  })
+}
+
+export function fetchGetTaskInstanceNodeMany(ids) {
+  return request.post({
+    url: `/taskInstanceNode/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportTaskInstanceNodeReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskInstanceNode/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-instance.js b/rsf-design/src/api/task-instance.js
new file mode 100644
index 0000000..bec15c7
--- /dev/null
+++ b/rsf-design/src/api/task-instance.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskInstancePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchTaskInstancePage(params = {}) {
+  return request.post({
+    url: '/taskInstance/page',
+    params: buildTaskInstancePageParams(params)
+  })
+}
+
+export function fetchGetTaskInstanceDetail(id) {
+  return request.get({
+    url: `/taskInstance/${id}`
+  })
+}
+
+export function fetchGetTaskInstanceMany(ids) {
+  return request.post({
+    url: `/taskInstance/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportTaskInstanceReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskInstance/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-item-log.js b/rsf-design/src/api/task-item-log.js
new file mode 100644
index 0000000..aee4273
--- /dev/null
+++ b/rsf-design/src/api/task-item-log.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskItemLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchTaskItemLogPage(params = {}) {
+  return request.post({
+    url: '/taskItemLog/page',
+    params: buildTaskItemLogPageParams(params)
+  })
+}
+
+export function fetchGetTaskItemLogDetail(id) {
+  return request.get({
+    url: `/taskItemLog/${id}`
+  })
+}
+
+export function fetchGetTaskItemLogMany(ids) {
+  return request.post({
+    url: `/taskItemLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportTaskItemLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskItemLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-item.js b/rsf-design/src/api/task-item.js
new file mode 100644
index 0000000..a0df397
--- /dev/null
+++ b/rsf-design/src/api/task-item.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchTaskItemPage(params = {}) {
+  return request.post({
+    url: '/taskItem/page',
+    params: buildTaskItemPageParams(params)
+  })
+}
+
+export function fetchGetTaskItemDetail(id) {
+  return request.get({
+    url: `/taskItem/${id}`
+  })
+}
+
+export function fetchGetTaskItemMany(ids) {
+  return request.post({
+    url: `/taskItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportTaskItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-log.js b/rsf-design/src/api/task-log.js
new file mode 100644
index 0000000..342f9ac
--- /dev/null
+++ b/rsf-design/src/api/task-log.js
@@ -0,0 +1,69 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchTaskLogPage(params = {}) {
+  return request.post({
+    url: '/taskLog/page',
+    params: buildTaskLogPageParams(params)
+  })
+}
+
+export function fetchGetTaskLogDetail(id) {
+  return request.get({
+    url: `/taskLog/${id}`
+  })
+}
+
+export function fetchGetTaskLogMany(ids) {
+  return request.post({
+    url: `/taskLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportTaskLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-path-template-merge.js b/rsf-design/src/api/task-path-template-merge.js
new file mode 100644
index 0000000..614b13d
--- /dev/null
+++ b/rsf-design/src/api/task-path-template-merge.js
@@ -0,0 +1,298 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+function normalizeStringList(value) {
+  if (Array.isArray(value)) {
+    return value.map((item) => normalizeText(item)).filter(Boolean)
+  }
+  if (value === null || value === undefined || value === '') {
+    return []
+  }
+  if (typeof value === 'string') {
+    const text = value.trim()
+    if (!text) {
+      return []
+    }
+    if (text.startsWith('[')) {
+      try {
+        const parsed = JSON.parse(text)
+        if (Array.isArray(parsed)) {
+          return parsed.map((item) => normalizeText(item)).filter(Boolean)
+        }
+      } catch {
+        return [text]
+      }
+    }
+    return text.split(/[;,锛孿n\r]+/g).map((item) => item.trim()).filter(Boolean)
+  }
+  return [normalizeText(value)].filter(Boolean)
+}
+
+function normalizeIntegerList(value) {
+  if (Array.isArray(value)) {
+    return value
+      .map((item) => {
+        if (item === null || item === undefined || item === '') {
+          return null
+        }
+        const numberValue = Number(item)
+        return Number.isNaN(numberValue) ? null : numberValue
+      })
+      .filter((item) => item !== null)
+  }
+  if (value === null || value === undefined || value === '') {
+    return []
+  }
+  if (typeof value === 'string') {
+    const text = value.trim()
+    if (!text) {
+      return []
+    }
+    if (text.startsWith('[')) {
+      try {
+        const parsed = JSON.parse(text)
+        if (Array.isArray(parsed)) {
+          return parsed
+            .map((item) => {
+              const numberValue = Number(item)
+              return Number.isNaN(numberValue) ? null : numberValue
+            })
+            .filter((item) => item !== null)
+        }
+      } catch {
+        return text
+          .split(/[;,锛孿n\r]+/g)
+          .map((item) => Number(item.trim()))
+          .filter((item) => !Number.isNaN(item))
+      }
+    }
+    const numberValue = Number(text)
+    return Number.isNaN(numberValue) ? [] : [numberValue]
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? [] : [numberValue]
+}
+
+export function buildTaskPathTemplateMergePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildTaskPathTemplateMergeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    templateCode: normalizeText(params.templateCode),
+    templateName: normalizeText(params.templateName),
+    sourceType: normalizeText(params.sourceType),
+    targetType: normalizeText(params.targetType),
+    conditionExpression: normalizeText(params.conditionExpression),
+    conditionDesc: normalizeText(params.conditionDesc),
+    version:
+      params.version !== undefined && params.version !== null && params.version !== ''
+        ? Number(params.version)
+        : void 0,
+    isCurrent:
+      params.isCurrent !== undefined && params.isCurrent !== null && params.isCurrent !== ''
+        ? Number(params.isCurrent)
+        : void 0,
+    priority:
+      params.priority !== undefined && params.priority !== null && params.priority !== ''
+        ? Number(params.priority)
+        : void 0,
+    timeoutMinutes:
+      params.timeoutMinutes !== undefined && params.timeoutMinutes !== null && params.timeoutMinutes !== ''
+        ? Number(params.timeoutMinutes)
+        : void 0,
+    maxRetryTimes:
+      params.maxRetryTimes !== undefined && params.maxRetryTimes !== null && params.maxRetryTimes !== ''
+        ? Number(params.maxRetryTimes)
+        : void 0,
+    retryIntervalSeconds:
+      params.retryIntervalSeconds !== undefined && params.retryIntervalSeconds !== null && params.retryIntervalSeconds !== ''
+        ? Number(params.retryIntervalSeconds)
+        : void 0,
+    stepSize:
+      params.stepSize !== undefined && params.stepSize !== null && params.stepSize !== ''
+        ? Number(params.stepSize)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    effectiveTime: normalizeText(params.effectiveTime),
+    expireTime: normalizeText(params.expireTime),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplateMergeSavePayload(formData = {}) {
+  const isEditMode = formData.id !== void 0 && formData.id !== null && formData.id !== ''
+  const sourceTypeR = normalizeStringList(formData.sourceTypeR)
+  const targetTypeR = normalizeStringList(formData.targetTypeR)
+  const sourceType = normalizeText(formData.sourceType || '')
+  const targetType = normalizeText(formData.targetType || '')
+  const conditionExpression = normalizeIntegerList(formData.conditionExpression)
+  const derivedSource = sourceType || sourceTypeR[0] || ''
+  const derivedTarget = targetType || targetTypeR[0] || ''
+  const templateName = normalizeText(formData.templateName || '') || (derivedSource && derivedTarget ? `${derivedSource}==>${derivedTarget}` : '')
+  const templateCode = normalizeText(formData.templateCode || '') || templateName
+
+  const payload = {
+    ...(isEditMode ? { id: Number(formData.id) } : {}),
+    templateCode,
+    templateName,
+    conditionExpression,
+    conditionDesc: normalizeText(formData.conditionDesc || ''),
+    version:
+      formData.version !== void 0 && formData.version !== null && formData.version !== ''
+        ? Number(formData.version)
+        : 1,
+    isCurrent:
+      formData.isCurrent !== void 0 && formData.isCurrent !== null && formData.isCurrent !== ''
+        ? Number(formData.isCurrent)
+        : 1,
+    effectiveTime: normalizeText(formData.effectiveTime || ''),
+    expireTime: normalizeText(formData.expireTime || ''),
+    priority:
+      formData.priority !== void 0 && formData.priority !== null && formData.priority !== ''
+        ? Number(formData.priority)
+        : 1,
+    ...(formData.timeoutMinutes !== void 0 && formData.timeoutMinutes !== null && formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    maxRetryTimes:
+      formData.maxRetryTimes !== void 0 && formData.maxRetryTimes !== null && formData.maxRetryTimes !== ''
+        ? Number(formData.maxRetryTimes)
+        : 3,
+    retryIntervalSeconds:
+      formData.retryIntervalSeconds !== void 0 &&
+      formData.retryIntervalSeconds !== null &&
+      formData.retryIntervalSeconds !== ''
+        ? Number(formData.retryIntervalSeconds)
+        : 60,
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    ...(formData.stepSize !== void 0 && formData.stepSize !== null && formData.stepSize !== ''
+      ? { stepSize: Number(formData.stepSize) }
+      : {})
+  }
+
+  if (isEditMode) {
+    payload.sourceType = sourceType
+    payload.targetType = targetType
+  } else {
+    payload.sourceTypeR = sourceTypeR
+    payload.targetTypeR = targetTypeR
+  }
+
+  return payload
+}
+
+export function fetchTaskPathTemplateMergeCreateSelectList() {
+  return request.post({
+    url: '/taskPathTemplateMerge/createSelectList'
+  })
+}
+
+export function fetchTaskPathTemplateMergePage(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateMerge/page',
+    params: buildTaskPathTemplateMergePageParams(params)
+  })
+}
+
+export function fetchTaskPathTemplateMergeList(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateMerge/list',
+    params: filterParams(params)
+  })
+}
+
+export function fetchGetTaskPathTemplateMergeDetail(id) {
+  return request.get({
+    url: `/taskPathTemplateMerge/${id}`
+  })
+}
+
+export function fetchGetTaskPathTemplateMergeMany(ids) {
+  return request.post({
+    url: `/taskPathTemplateMerge/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveTaskPathTemplateMerge(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateMerge/save',
+    params: buildTaskPathTemplateMergeSavePayload(params)
+  })
+}
+
+export function fetchUpdateTaskPathTemplateMerge(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateMerge/update',
+    params: buildTaskPathTemplateMergeSavePayload(params)
+  })
+}
+
+export function fetchDeleteTaskPathTemplateMerge(ids) {
+  return request.post({
+    url: `/taskPathTemplateMerge/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchTaskPathTemplateMergeQuery(condition = '') {
+  return request.post({
+    url: '/taskPathTemplateMerge/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportTaskPathTemplateMergeReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskPathTemplateMerge/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-path-template-node.js b/rsf-design/src/api/task-path-template-node.js
new file mode 100644
index 0000000..783f0bb
--- /dev/null
+++ b/rsf-design/src/api/task-path-template-node.js
@@ -0,0 +1,175 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+function toNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? void 0 : numberValue
+}
+
+export function buildTaskPathTemplateNodePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildTaskPathTemplateNodeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    templateId: toNumber(params.templateId),
+    templateCode: normalizeText(params.templateCode),
+    nodeOrder: toNumber(params.nodeOrder),
+    nodeCode: normalizeText(params.nodeCode),
+    nodeName: normalizeText(params.nodeName),
+    nodeType: normalizeText(params.nodeType),
+    systemCode: normalizeText(params.systemCode),
+    systemName: normalizeText(params.systemName),
+    executeParams: normalizeText(params.executeParams),
+    resultSchema: normalizeText(params.resultSchema),
+    timeoutMinutes: toNumber(params.timeoutMinutes),
+    mandatory: toNumber(params.mandatory),
+    parallelExecutable: toNumber(params.parallelExecutable),
+    preCondition: normalizeText(params.preCondition),
+    postCondition: normalizeText(params.postCondition),
+    nextNodeRules: normalizeText(params.nextNodeRules),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplateNodeSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.templateId !== undefined && formData.templateId !== null && formData.templateId !== ''
+      ? { templateId: Number(formData.templateId) }
+      : {}),
+    templateCode: normalizeText(formData.templateCode) || '',
+    ...(formData.nodeOrder !== undefined && formData.nodeOrder !== null && formData.nodeOrder !== ''
+      ? { nodeOrder: Number(formData.nodeOrder) }
+      : {}),
+    nodeCode: normalizeText(formData.nodeCode) || '',
+    nodeName: normalizeText(formData.nodeName) || '',
+    nodeType: normalizeText(formData.nodeType) || 'EXECUTE',
+    systemCode: normalizeText(formData.systemCode) || '',
+    systemName: normalizeText(formData.systemName) || '',
+    executeParams: normalizeText(formData.executeParams) || '',
+    resultSchema: normalizeText(formData.resultSchema) || '',
+    ...(formData.timeoutMinutes !== undefined && formData.timeoutMinutes !== null && formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    ...(formData.mandatory !== undefined && formData.mandatory !== null && formData.mandatory !== ''
+      ? { mandatory: Number(formData.mandatory) }
+      : { mandatory: 1 }),
+    ...(formData.parallelExecutable !== undefined &&
+    formData.parallelExecutable !== null &&
+    formData.parallelExecutable !== ''
+      ? { parallelExecutable: Number(formData.parallelExecutable) }
+      : { parallelExecutable: 0 }),
+    preCondition: normalizeText(formData.preCondition) || '',
+    postCondition: normalizeText(formData.postCondition) || '',
+    nextNodeRules: normalizeText(formData.nextNodeRules) || ''
+  }
+}
+
+export function fetchTaskPathTemplateNodePage(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateNode/page',
+    params: buildTaskPathTemplateNodePageParams(params)
+  })
+}
+
+export function fetchTaskPathTemplateNodeList(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateNode/list',
+    params: filterParams(params)
+  })
+}
+
+export function fetchGetTaskPathTemplateNodeDetail(id) {
+  return request.get({
+    url: `/taskPathTemplateNode/${id}`
+  })
+}
+
+export function fetchGetTaskPathTemplateNodeMany(ids) {
+  return request.post({
+    url: `/taskPathTemplateNode/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveTaskPathTemplateNode(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateNode/save',
+    params: buildTaskPathTemplateNodeSavePayload(params)
+  })
+}
+
+export function fetchUpdateTaskPathTemplateNode(params = {}) {
+  return request.post({
+    url: '/taskPathTemplateNode/update',
+    params: buildTaskPathTemplateNodeSavePayload(params)
+  })
+}
+
+export function fetchDeleteTaskPathTemplateNode(ids) {
+  return request.post({
+    url: `/taskPathTemplateNode/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchTaskPathTemplateNodeQuery(condition = '') {
+  return request.post({
+    url: '/taskPathTemplateNode/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportTaskPathTemplateNodeReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskPathTemplateNode/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task-path-template.js b/rsf-design/src/api/task-path-template.js
new file mode 100644
index 0000000..0bb5885
--- /dev/null
+++ b/rsf-design/src/api/task-path-template.js
@@ -0,0 +1,197 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildTaskPathTemplatePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildTaskPathTemplateSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    templateCode: normalizeText(params.templateCode),
+    templateName: normalizeText(params.templateName),
+    sourceType: normalizeText(params.sourceType),
+    targetType: normalizeText(params.targetType),
+    conditionExpression: normalizeText(params.conditionExpression),
+    conditionDesc: normalizeText(params.conditionDesc),
+    version:
+      params.version !== undefined && params.version !== null && params.version !== ''
+        ? Number(params.version)
+        : void 0,
+    isCurrent:
+      params.isCurrent !== undefined && params.isCurrent !== null && params.isCurrent !== ''
+        ? Number(params.isCurrent)
+        : void 0,
+    priority:
+      params.priority !== undefined && params.priority !== null && params.priority !== ''
+        ? Number(params.priority)
+        : void 0,
+    timeoutMinutes:
+      params.timeoutMinutes !== undefined && params.timeoutMinutes !== null && params.timeoutMinutes !== ''
+        ? Number(params.timeoutMinutes)
+        : void 0,
+    stepSize:
+      params.stepSize !== undefined && params.stepSize !== null && params.stepSize !== ''
+        ? Number(params.stepSize)
+        : void 0,
+    maxRetryTimes:
+      params.maxRetryTimes !== undefined && params.maxRetryTimes !== null && params.maxRetryTimes !== ''
+        ? Number(params.maxRetryTimes)
+        : void 0,
+    retryIntervalSeconds:
+      params.retryIntervalSeconds !== undefined && params.retryIntervalSeconds !== null && params.retryIntervalSeconds !== ''
+        ? Number(params.retryIntervalSeconds)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    remark: normalizeText(params.remark),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplateSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    templateCode: normalizeText(formData.templateCode) || '',
+    templateName: normalizeText(formData.templateName) || '',
+    sourceType: normalizeText(formData.sourceType) || '',
+    targetType: normalizeText(formData.targetType) || '',
+    conditionExpression: normalizeText(formData.conditionExpression) || '',
+    conditionDesc: normalizeText(formData.conditionDesc) || '',
+    ...(formData.version !== undefined && formData.version !== null && formData.version !== ''
+      ? { version: Number(formData.version) }
+      : {}),
+    ...(formData.isCurrent !== undefined && formData.isCurrent !== null && formData.isCurrent !== ''
+      ? { isCurrent: Number(formData.isCurrent) }
+      : {}),
+    effectiveTime: normalizeText(formData.effectiveTime) || '',
+    expireTime: normalizeText(formData.expireTime) || '',
+    ...(formData.priority !== undefined && formData.priority !== null && formData.priority !== ''
+      ? { priority: Number(formData.priority) }
+      : {}),
+    ...(formData.timeoutMinutes !== undefined && formData.timeoutMinutes !== null && formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    ...(formData.stepSize !== undefined && formData.stepSize !== null && formData.stepSize !== ''
+      ? { stepSize: Number(formData.stepSize) }
+      : {}),
+    ...(formData.maxRetryTimes !== undefined && formData.maxRetryTimes !== null && formData.maxRetryTimes !== ''
+      ? { maxRetryTimes: Number(formData.maxRetryTimes) }
+      : {}),
+    ...(formData.retryIntervalSeconds !== undefined &&
+    formData.retryIntervalSeconds !== null &&
+    formData.retryIntervalSeconds !== ''
+      ? { retryIntervalSeconds: Number(formData.retryIntervalSeconds) }
+      : {}),
+    ...(formData.status !== undefined && formData.status !== null && formData.status !== ''
+      ? { status: Number(formData.status) }
+      : {}),
+    remark: normalizeText(formData.remark) || ''
+  }
+}
+
+export function fetchTaskPathTemplatePage(params = {}) {
+  return request.post({
+    url: '/taskPathTemplate/page',
+    params: buildTaskPathTemplatePageParams(params)
+  })
+}
+
+export function fetchTaskPathTemplateList(params = {}) {
+  return request.post({
+    url: '/taskPathTemplate/list',
+    params: filterParams(params)
+  })
+}
+
+export function fetchGetTaskPathTemplateDetail(id) {
+  return request.get({
+    url: `/taskPathTemplate/${id}`
+  })
+}
+
+export function fetchGetTaskPathTemplateMany(ids) {
+  return request.post({
+    url: `/taskPathTemplate/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveTaskPathTemplate(params = {}) {
+  return request.post({
+    url: '/taskPathTemplate/save',
+    params: buildTaskPathTemplateSavePayload(params)
+  })
+}
+
+export function fetchUpdateTaskPathTemplate(params = {}) {
+  return request.post({
+    url: '/taskPathTemplate/update',
+    params: buildTaskPathTemplateSavePayload(params)
+  })
+}
+
+export function fetchDeleteTaskPathTemplate(ids) {
+  return request.post({
+    url: `/taskPathTemplate/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchTaskPathTemplateQuery(condition = '') {
+  return request.post({
+    url: '/taskPathTemplate/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportTaskPathTemplateReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/taskPathTemplate/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/task.js b/rsf-design/src/api/task.js
new file mode 100644
index 0000000..2157d26
--- /dev/null
+++ b/rsf-design/src/api/task.js
@@ -0,0 +1,95 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+export function buildTaskPageParams(params = {}) {
+  const entries = Object.entries(params).filter(([key, value]) => {
+    if (['current', 'pageSize', 'size'].includes(key)) {
+      return false
+    }
+    if (value === undefined || value === null) {
+      return false
+    }
+    if (typeof value === 'string' && value.trim() === '') {
+      return false
+    }
+    return true
+  })
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...Object.fromEntries(entries.map(([key, value]) => [key, normalizeText(value)]))
+  }
+}
+
+export function buildTaskItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.taskId !== undefined ? { taskId: params.taskId } : {})
+  }
+}
+
+export function fetchTaskPage(params = {}) {
+  return request.post({
+    url: '/task/page',
+    params: buildTaskPageParams(params)
+  })
+}
+
+export function fetchTaskDetail(id) {
+  return request.get({
+    url: `/task/${id}`
+  })
+}
+
+export function fetchTaskItemPage(params = {}) {
+  return request.post({
+    url: '/taskItem/page',
+    params: buildTaskItemPageParams(params)
+  })
+}
+
+export function fetchRemoveTask(ids) {
+  const normalizedIds = normalizeIds(ids)
+  return request.post({
+    url: `/task/remove/${normalizedIds}`
+  })
+}
+
+export function fetchCompleteTask(id) {
+  return request.post({
+    url: `/task/complete/${id}`
+  })
+}
+
+export function fetchPickTask(id) {
+  return request.post({
+    url: `/task/pick/${id}`
+  })
+}
+
+export function fetchCheckTask(id) {
+  return request.post({
+    url: `/task/check/${id}`
+  })
+}
+
+export function fetchTopTask(id) {
+  return request.post({
+    url: `/task/top/${id}`
+  })
+}
diff --git a/rsf-design/src/api/transfer-item.js b/rsf-design/src/api/transfer-item.js
new file mode 100644
index 0000000..6a901ea
--- /dev/null
+++ b/rsf-design/src/api/transfer-item.js
@@ -0,0 +1,125 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+const TEXT_FIELDS = [
+  'condition',
+  'timeStart',
+  'timeEnd',
+  'transferCode',
+  'matnrCode',
+  'maktx',
+  'unit',
+  'batch',
+  'spec',
+  'model',
+  'fieldsIndex',
+  'platItemId',
+  'platOrderCode',
+  'platWorkCode',
+  'projectCode',
+  'memo'
+]
+
+const NUMBER_FIELDS = ['transferId', 'matnrId', 'anfme', 'workQty', 'qty', 'splrId', 'status']
+
+function buildSearchParams(params = {}) {
+  const result = {}
+
+  TEXT_FIELDS.forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  NUMBER_FIELDS.forEach((key) => {
+    const value = normalizeNumber(params[key], void 0)
+    if (value !== void 0) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildTransferItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSearchParams(params)
+  }
+}
+
+export function buildTransferItemQueryParams(condition = '') {
+  const normalizedCondition = normalizeText(condition)
+  return normalizedCondition ? { condition: normalizedCondition } : {}
+}
+
+export function fetchTransferItemPage(params = {}) {
+  return request.post({
+    url: '/transferItem/page',
+    params: buildTransferItemPageParams(params)
+  })
+}
+
+export function fetchTransferItemList(params = {}) {
+  return request.post({
+    url: '/transferItem/list',
+    data: buildSearchParams(params)
+  })
+}
+
+export function fetchTransferItemMany(ids) {
+  return request.post({
+    url: `/transferItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchTransferItemDetail(id) {
+  return request.get({
+    url: `/transferItem/${id}`
+  })
+}
+
+export function fetchTransferItemQuery(condition = '') {
+  return request.post({
+    url: '/transferItem/query',
+    params: buildTransferItemQueryParams(condition)
+  })
+}
+
+export async function fetchExportTransferItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/transferItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
diff --git a/rsf-design/src/api/transfer.js b/rsf-design/src/api/transfer.js
new file mode 100644
index 0000000..bfb389b
--- /dev/null
+++ b/rsf-design/src/api/transfer.js
@@ -0,0 +1,137 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) return fallback
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) return ''
+  return String(ids).trim()
+}
+
+export function buildTransferSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'orgWareName', 'tarWareName', 'orgAreaName', 'tarAreaName', 'memo', 'timeStart', 'timeEnd'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) result[key] = value
+  })
+  ;['type', 'source', 'exceStatus', 'status', 'orgWareId', 'tarWareId', 'orgAreaId', 'tarAreaId'].forEach((key) => {
+    if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+      result[key] = normalizeNumber(params[key])
+    }
+  })
+  return result
+}
+
+export function buildTransferPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTransferSearchParams(params)
+  }
+}
+
+export function buildTransferOrderPageParams(params = {}) {
+  return {
+    condition: normalizeText(params.code || params.condition),
+    code: normalizeText(params.code || params.condition),
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function buildTransferSavePayload(formData = {}, areaOptions = []) {
+  const optionMap = new Map(
+    (Array.isArray(areaOptions) ? areaOptions : [])
+      .map((item) => {
+        const value = normalizeNumber(item?.value ?? item?.id, void 0)
+        if (value === void 0) return null
+        return [value, item?.raw || item]
+      })
+      .filter(Boolean)
+  )
+
+  const orgAreaId = normalizeNumber(formData.orgAreaId, void 0)
+  const tarAreaId = normalizeNumber(formData.tarAreaId, void 0)
+  const orgArea = optionMap.get(orgAreaId) || {}
+  const tarArea = optionMap.get(tarAreaId) || {}
+  const orgWareId = normalizeNumber(orgArea.warehouseId ?? orgArea.warehouse_id ?? orgArea.warehouseIdValue, void 0)
+  const tarWareId = normalizeNumber(tarArea.warehouseId ?? tarArea.warehouse_id ?? tarArea.warehouseIdValue, void 0)
+
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(normalizeText(formData.code) ? { code: normalizeText(formData.code) } : {}),
+    ...(formData.type !== undefined && formData.type !== null && formData.type !== ''
+      ? { type: normalizeNumber(formData.type) }
+      : {}),
+    ...(orgAreaId !== void 0 ? { orgAreaId } : {}),
+    ...(tarAreaId !== void 0 ? { tarAreaId } : {}),
+    ...(orgWareId !== void 0 ? { orgWareId } : {}),
+    ...(tarWareId !== void 0 ? { tarWareId } : {}),
+    ...(normalizeText(orgArea.name || orgArea.areaName) ? { orgAreaName: normalizeText(orgArea.name || orgArea.areaName) } : {}),
+    ...(normalizeText(tarArea.name || tarArea.areaName) ? { tarAreaName: normalizeText(tarArea.name || tarArea.areaName) } : {}),
+    ...(normalizeText(orgArea.warehouseId$ || orgArea.warehouseName) ? { orgWareName: normalizeText(orgArea.warehouseId$ || orgArea.warehouseName) } : {}),
+    ...(normalizeText(tarArea.warehouseId$ || tarArea.warehouseName) ? { tarWareName: normalizeText(tarArea.warehouseId$ || tarArea.warehouseName) } : {}),
+    ...(formData.status !== undefined && formData.status !== null && formData.status !== ''
+      ? { status: normalizeNumber(formData.status) }
+      : { status: 1 }),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function fetchTransferPage(params = {}) {
+  return request.post({ url: '/transfer/page', params: buildTransferPageParams(params) })
+}
+
+export function fetchTransferDetail(id) {
+  return request.get({ url: `/transfer/${id}` })
+}
+
+export function fetchTransferMany(ids) {
+  return request.post({ url: `/transfer/many/${normalizeIds(ids)}` })
+}
+
+export function fetchDeleteTransfer(ids) {
+  return request.post({ url: `/transfer/remove/${normalizeIds(ids)}` })
+}
+
+export function fetchSaveTransfer(payload = {}) {
+  return request.post({ url: '/transfer/save', params: payload })
+}
+
+export function fetchUpdateTransfer(payload = {}) {
+  return request.post({ url: '/transfer/update', params: payload })
+}
+
+export function fetchTransferOrdersPage(params = {}) {
+  return request.post({ url: '/transfer/orders/page', params: buildTransferOrderPageParams(params) })
+}
+
+export function fetchTransferPubOutStock(payload = {}) {
+  return request.post({ url: '/transfer/pub/outStock', params: payload })
+}
+
+export async function fetchExportTransferReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/transfer/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wait-pakin-item-log.js b/rsf-design/src/api/wait-pakin-item-log.js
new file mode 100644
index 0000000..4e72cc2
--- /dev/null
+++ b/rsf-design/src/api/wait-pakin-item-log.js
@@ -0,0 +1,104 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+
+  return String(ids).trim()
+}
+
+function buildSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'asnCode',
+    'trackCode',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['logId', 'pakinId', 'pakinItemId', 'asnId', 'asnItemId', 'status'].forEach((key) => {
+    if (params[key] === '' || params[key] === undefined || params[key] === null) {
+      return
+    }
+
+    const numericValue = Number(params[key])
+    if (!Number.isNaN(numericValue)) {
+      result[key] = numericValue
+    }
+  })
+
+  return result
+}
+
+export function buildWaitPakinItemLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSearchParams(params)
+  }
+}
+
+export function fetchWaitPakinItemLogPage(params = {}) {
+  return request.post({
+    url: '/waitPakinItemLog/page',
+    params: buildWaitPakinItemLogPageParams(params)
+  })
+}
+
+export function fetchWaitPakinItemLogList(params = {}) {
+  return request.post({
+    url: '/waitPakinItemLog/list',
+    data: buildSearchParams(params)
+  })
+}
+
+export function fetchWaitPakinItemLogMany(ids) {
+  return request.post({
+    url: `/waitPakinItemLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWaitPakinItemLogDetail(id) {
+  return request.get({
+    url: `/waitPakinItemLog/${id}`
+  })
+}
+
+export function fetchWaitPakinItemLogQuery(condition = '') {
+  return request.post({
+    url: '/waitPakinItemLog/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportWaitPakinItemLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waitPakinItemLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wait-pakin-item.js b/rsf-design/src/api/wait-pakin-item.js
new file mode 100644
index 0000000..a513e75
--- /dev/null
+++ b/rsf-design/src/api/wait-pakin-item.js
@@ -0,0 +1,105 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+
+  return String(ids).trim()
+}
+
+function buildSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'type',
+    'asnCode',
+    'matnrCode',
+    'trackCode',
+    'batch',
+    'memo',
+    'fieldsIndex'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['pakinId', 'asnItemId', 'wkType', 'isptResult', 'status'].forEach((key) => {
+    if (params[key] === '' || params[key] === undefined || params[key] === null) {
+      return
+    }
+
+    const numericValue = Number(params[key])
+    if (!Number.isNaN(numericValue)) {
+      result[key] = numericValue
+    }
+  })
+
+  return result
+}
+
+export function buildWaitPakinItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSearchParams(params)
+  }
+}
+
+export function fetchWaitPakinItemPage(params = {}) {
+  return request.post({
+    url: '/waitPakinItem/page',
+    params: buildWaitPakinItemPageParams(params)
+  })
+}
+
+export function fetchWaitPakinItemList(params = {}) {
+  return request.post({
+    url: '/waitPakinItem/list',
+    data: buildSearchParams(params)
+  })
+}
+
+export function fetchWaitPakinItemMany(ids) {
+  return request.post({
+    url: `/waitPakinItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWaitPakinItemDetail(id) {
+  return request.get({
+    url: `/waitPakinItem/${id}`
+  })
+}
+
+export function fetchWaitPakinItemQuery(condition = '') {
+  return request.post({
+    url: '/waitPakinItem/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export async function fetchExportWaitPakinItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waitPakinItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wait-pakin-log.js b/rsf-design/src/api/wait-pakin-log.js
new file mode 100644
index 0000000..8588d51
--- /dev/null
+++ b/rsf-design/src/api/wait-pakin-log.js
@@ -0,0 +1,85 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWaitPakinLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildWaitPakinItemLogPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.logId !== undefined ? { logId: Number(params.logId) } : {}),
+    ...filterParams(params, ['current', 'pageSize', 'size', 'logId'])
+  }
+}
+
+export function fetchWaitPakinLogPage(params = {}) {
+  return request.post({
+    url: '/waitPakinLog/page',
+    params: buildWaitPakinLogPageParams(params)
+  })
+}
+
+export function fetchGetWaitPakinLogDetail(id) {
+  return request.get({
+    url: `/waitPakinLog/${id}`
+  })
+}
+
+export function fetchGetWaitPakinLogMany(ids) {
+  return request.post({
+    url: `/waitPakinLog/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWaitPakinItemLogPage(params = {}) {
+  return request.post({
+    url: '/waitPakinItemLog/page',
+    params: buildWaitPakinItemLogPageParams(params)
+  })
+}
+
+export async function fetchExportWaitPakinLogReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waitPakinLog/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wait-pakin.js b/rsf-design/src/api/wait-pakin.js
new file mode 100644
index 0000000..2edea99
--- /dev/null
+++ b/rsf-design/src/api/wait-pakin.js
@@ -0,0 +1,111 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWaitPakinPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildWaitPakinItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchWaitPakinPage(params = {}) {
+  return request.post({
+    url: '/waitPakin/page',
+    params: buildWaitPakinPageParams(params)
+  })
+}
+
+export function fetchWaitPakinList() {
+  return request.post({
+    url: '/waitPakin/list',
+    data: {}
+  })
+}
+
+export function fetchWaitPakinMany(ids) {
+  return request.post({
+    url: `/waitPakin/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWaitPakinDetail(id) {
+  return request.get({
+    url: `/waitPakin/${id}`
+  })
+}
+
+export function fetchDeleteWaitPakin(ids) {
+  return request.post({
+    url: `/waitPakin/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWaitPakinQuery(condition = '') {
+  return request.post({
+    url: '/waitPakin/query',
+    params: { condition: normalizeText(condition) }
+  })
+}
+
+export function fetchWaitPakinItemPage(params = {}) {
+  return request.post({
+    url: '/waitPakinItem/page',
+    params: buildWaitPakinItemPageParams(params)
+  })
+}
+
+export function fetchMergeWaitPakinTasks(params = {}) {
+  return request.post({
+    url: '/waitPakin/merge',
+    params
+  })
+}
+
+export async function fetchExportWaitPakinReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waitPakin/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/warehouse-areas-item.js b/rsf-design/src/api/warehouse-areas-item.js
new file mode 100644
index 0000000..4782284
--- /dev/null
+++ b/rsf-design/src/api/warehouse-areas-item.js
@@ -0,0 +1,80 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids.map((id) => String(id).trim()).filter(Boolean).join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+export function buildWarehouseAreasItemPageParams(params = {}) {
+  const entries = Object.entries(params).filter(([key, value]) => {
+    if (['current', 'pageSize', 'size'].includes(key)) {
+      return false
+    }
+    if (value === undefined || value === null) {
+      return false
+    }
+    if (typeof value === 'string' && value.trim() === '') {
+      return false
+    }
+    return true
+  })
+
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...Object.fromEntries(entries.map(([key, value]) => [key, normalizeText(value)]))
+  }
+}
+
+export function buildWarehouseAreasItemIsptPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.id !== undefined ? { id: params.id } : {})
+  }
+}
+
+export function fetchWarehouseAreasItemPage(params = {}) {
+  return request.post({
+    url: '/warehouseAreasItem/page',
+    params: buildWarehouseAreasItemPageParams(params)
+  })
+}
+
+export function fetchWarehouseAreasItemIsptPage(params = {}) {
+  return request.post({
+    url: '/warehouseAreasItem/ispts/page',
+    params: buildWarehouseAreasItemIsptPageParams(params)
+  })
+}
+
+export function fetchGetWarehouseAreasItemMany(ids) {
+  const normalizedIds = normalizeIds(ids)
+  return request.post({
+    url: `/warehouseAreasItem/many/${normalizedIds}`
+  })
+}
+
+export function fetchEnabledFields() {
+  return request.get({ url: '/fields/enable/list' })
+}
+
+export async function fetchExportWarehouseAreasItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/warehouseAreasItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/warehouse-stock.js b/rsf-design/src/api/warehouse-stock.js
new file mode 100644
index 0000000..edd01bd
--- /dev/null
+++ b/rsf-design/src/api/warehouse-stock.js
@@ -0,0 +1,60 @@
+import request from '@/utils/http'
+
+export function buildWarehouseStockPageParams(params = {}) {
+  const matnrCode = typeof params.matnrCode === 'string' ? params.matnrCode.trim() : params.matnrCode
+  const maktx = typeof params.maktx === 'string' ? params.maktx.trim() : params.maktx
+  const batch = typeof params.batch === 'string' ? params.batch.trim() : params.batch
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.aggType !== undefined ? { aggType: params.aggType } : {}),
+    ...(matnrCode ? { matnrCode } : {}),
+    ...(maktx ? { maktx } : {}),
+    ...(batch ? { batch } : {}),
+    ...Object.fromEntries(
+      Object.entries(params).filter(
+        ([key, value]) =>
+          !['current', 'pageSize', 'size', 'aggType', 'matnrCode', 'maktx', 'batch'].includes(key) &&
+          value !== undefined &&
+          value !== ''
+      )
+    )
+  }
+}
+
+export function buildWarehouseStockInfoParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.aggType !== undefined ? { aggType: params.aggType } : {}),
+    ...(params.stock !== undefined ? { stock: params.stock } : {})
+  }
+}
+
+export function buildWarehouseStockHistoriesParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.aggType !== undefined ? { aggType: params.aggType } : {}),
+    ...(params.stock !== undefined ? { stock: params.stock } : {})
+  }
+}
+
+export function fetchWarehouseStockPage(params = {}) {
+  return request.post({ url: '/warehouse/stock/page', params: buildWarehouseStockPageParams(params) })
+}
+
+export function fetchWarehouseStockInfoPage(params = {}) {
+  return request.post({ url: '/warehouse/stock/info', params: buildWarehouseStockInfoParams(params) })
+}
+
+export function fetchWarehouseStockHistoriesPage(params = {}) {
+  return request.post({
+    url: '/warehouse/stock/histories/page',
+    params: buildWarehouseStockHistoriesParams(params)
+  })
+}
+
+export function fetchEnabledFields() {
+  return request.get({ url: '/fields/enable/list' })
+}
diff --git a/rsf-design/src/api/warehouse.js b/rsf-design/src/api/warehouse.js
new file mode 100644
index 0000000..26a9e7d
--- /dev/null
+++ b/rsf-design/src/api/warehouse.js
@@ -0,0 +1,140 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWarehousePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildWarehouseSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    name: normalizeText(formData.name) || '',
+    code: normalizeText(formData.code) || '',
+    factory: normalizeText(formData.factory) || '',
+    address: normalizeText(formData.address) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildWarehouseSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    factory: normalizeText(params.factory),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    address: normalizeText(params.address),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildWarehouseQueryParams(condition = '') {
+  return {
+    condition: normalizeText(condition)
+  }
+}
+
+export function fetchWarehousePage(params = {}) {
+  return request.post({
+    url: '/warehouse/page',
+    params: buildWarehousePageParams(params)
+  })
+}
+
+export function fetchGetWarehouseDetail(id) {
+  return request.get({
+    url: `/warehouse/${id}`
+  })
+}
+
+export function fetchGetWarehouseMany(ids) {
+  return request.post({
+    url: `/warehouse/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchSaveWarehouse(params = {}) {
+  return request.post({
+    url: '/warehouse/save',
+    params: buildWarehouseSavePayload(params)
+  })
+}
+
+export function fetchUpdateWarehouse(params = {}) {
+  return request.post({
+    url: '/warehouse/update',
+    params: buildWarehouseSavePayload(params)
+  })
+}
+
+export function fetchDeleteWarehouse(ids) {
+  return request.post({
+    url: `/warehouse/remove/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWarehouseQuery(condition = '') {
+  return request.post({
+    url: '/warehouse/query',
+    params: buildWarehouseQueryParams(condition)
+  })
+}
+
+export async function fetchExportWarehouseReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/warehouse/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wave-item.js b/rsf-design/src/api/wave-item.js
new file mode 100644
index 0000000..b85fd25
--- /dev/null
+++ b/rsf-design/src/api/wave-item.js
@@ -0,0 +1,71 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWaveItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchWaveItemPage(params = {}) {
+  return request.post({
+    url: '/waveItem/page',
+    params: buildWaveItemPageParams(params)
+  })
+}
+
+export function fetchGetWaveItemDetail(id) {
+  return request.get({
+    url: `/waveItem/${id}`
+  })
+}
+
+export function fetchGetWaveItemMany(ids) {
+  return request.post({
+    url: `/waveItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportWaveItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waveItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/api/wave.js b/rsf-design/src/api/wave.js
new file mode 100644
index 0000000..4695632
--- /dev/null
+++ b/rsf-design/src/api/wave.js
@@ -0,0 +1,145 @@
+import request from '@/utils/http'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeIds(ids) {
+  if (Array.isArray(ids)) {
+    return ids
+      .map((id) => String(id).trim())
+      .filter(Boolean)
+      .join(',')
+  }
+
+  if (ids === null || ids === undefined) {
+    return ''
+  }
+
+  return String(ids).trim()
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function buildWavePageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildWaveItemPageParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function fetchWavePage(params = {}) {
+  return request.post({
+    url: '/wave/page',
+    params: buildWavePageParams(params)
+  })
+}
+
+export function fetchGetWaveDetail(id) {
+  return request.get({
+    url: `/wave/${id}`
+  })
+}
+
+export function fetchGetWaveMany(ids) {
+  return request.post({
+    url: `/wave/many/${normalizeIds(ids)}`
+  })
+}
+
+export function fetchWavePreviewPage(params = {}) {
+  return request.post({
+    url: '/wave/locs/preview/page',
+    params: {
+      ...filterParams(params, ['current', 'pageSize', 'size']),
+      current: params.current || 1,
+      pageSize: params.pageSize || params.size || 20
+    }
+  })
+}
+
+export function fetchPauseWave(id) {
+  return request.post({
+    url: `/wave/pause/pub/${id}`
+  })
+}
+
+export function fetchContinueWave(id) {
+  return request.post({
+    url: `/wave/continue/pub/${id}`
+  })
+}
+
+export function fetchStopWave(id) {
+  return request.post({
+    url: `/wave/stop/pub/${id}`
+  })
+}
+
+export function fetchPublicWaveTask(payload = {}) {
+  return request.post({
+    url: '/wave/public/task',
+    data: payload
+  })
+}
+
+export async function fetchExportWaveReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/wave/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
+
+export function fetchWaveItemPage(params = {}) {
+  return request.post({
+    url: '/waveItem/page',
+    params: buildWaveItemPageParams(params)
+  })
+}
+
+export function fetchGetWaveItemDetail(id) {
+  return request.get({
+    url: `/waveItem/${id}`
+  })
+}
+
+export function fetchGetWaveItemMany(ids) {
+  return request.post({
+    url: `/waveItem/many/${normalizeIds(ids)}`
+  })
+}
+
+export async function fetchExportWaveItemReport(payload = {}, options = {}) {
+  return fetch(`${import.meta.env.VITE_API_URL}/waveItem/export`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      ...(options.headers || {})
+    },
+    body: JSON.stringify(payload)
+  })
+}
diff --git a/rsf-design/src/components/biz/list-export-print/index.vue b/rsf-design/src/components/biz/list-export-print/index.vue
index b5fee68..50e72b7 100644
--- a/rsf-design/src/components/biz/list-export-print/index.vue
+++ b/rsf-design/src/components/biz/list-export-print/index.vue
@@ -1,5 +1,5 @@
 <template>
-  <ElSpace wrap>
+  <ElSpace v-bind="attrs" wrap>
     <ElButton :disabled="disabled" @click="handleExport">瀵煎嚭</ElButton>
     <ElButton :disabled="disabled" @click="handlePrint">鎵撳嵃</ElButton>
   </ElSpace>
@@ -15,7 +15,7 @@
 </template>
 
 <script setup>
-  import { computed } from 'vue'
+  import { computed, useAttrs } from 'vue'
   import ListPrintPreviewDialog from './list-print-preview-dialog.vue'
   import {
     buildListExportPayload,
@@ -24,7 +24,12 @@
     buildReportStyleMeta
   } from './list-export-print.helpers.js'
 
-  defineOptions({ name: 'ListExportPrint' })
+  defineOptions({
+    name: 'ListExportPrint',
+    inheritAttrs: false
+  })
+
+  const attrs = useAttrs()
 
   const props = defineProps({
     reportTitle: { type: String, default: '鎶ヨ〃' },
diff --git a/rsf-design/src/components/core/layouts/art-breadcrumb/index.vue b/rsf-design/src/components/core/layouts/art-breadcrumb/index.vue
index 5a8c0a2..f3170a4 100644
--- a/rsf-design/src/components/core/layouts/art-breadcrumb/index.vue
+++ b/rsf-design/src/components/core/layouts/art-breadcrumb/index.vue
@@ -59,8 +59,10 @@
     if (currentRouteMeta?.isIframe && (items.length === 1 || items.every(isWrapperContainer))) {
       return [createBreadcrumbItem(currentRoute)]
     }
-    return items
+    return dedupeBreadcrumbItems(items)
   })
+  const dedupeBreadcrumbItems = (items = []) =>
+    items.filter((item, index) => index === 0 || item.path !== items[index - 1]?.path)
   const isWrapperContainer = (item) => item.path === '/outside' && !!item.meta?.isIframe
   const createBreadcrumbItem = (route2) => ({
     path: route2.path,
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index 3b7f747..6a5f6f8 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -280,6 +280,11 @@
     "department": "Department",
     "token": "Token",
     "operation": "Operation",
+    "flowInstance": "FlowInstance",
+    "flowStepInstance": "FlowStepInstance",
+    "flowStepLog": "FlowStepLog",
+    "taskInstance": "TaskInstance",
+    "taskInstanceNode": "TaskInstanceNode",
     "config": "Config",
     "aiParam": "AI Params",
     "aiPrompt": "Prompts",
@@ -299,6 +304,7 @@
     "locArea": "locArea",
     "locAreaMat": "Logic Areas",
     "locAreaMatRela": "LocAreaMatRela",
+    "locAreaRela": "LocAreaRela",
     "container": "Container",
     "contract": "Contract",
     "qlyInspect": "QlyInspect",
@@ -314,6 +320,7 @@
     "asnOrderItemLog": "asnOrderItemLog",
     "purchase": "Purchase",
     "purchaseItem": "PurchaseItem",
+    "preparationItem": "Preparation Item",
     "whMat": "Warehouse Mat",
     "fields": "Extend Fields",
     "fieldsItem": "Extend Fields Items",
@@ -335,6 +342,7 @@
     "logs": "Logs",
     "permissions": "Permissions",
     "delivery": "Delivery",
+    "deliveryItem": "Delivery Item",
     "outStock": "Out Stock",
     "outStockItem": "Out Stock Item",
     "inStockPoces": "In Stock Pocess",
@@ -343,6 +351,7 @@
     "deviceBind": "Device Bind",
     "tasks": "Tasks",
     "wave": "Wave Manage",
+    "waveItem": "Wave Item",
     "basStation": "BasStation",
     "basContainer": "BasContainer",
     "outBound": "Out Bound",
@@ -350,10 +359,14 @@
     "stockTransfer": "Stock Transfer",
     "waveRule": "Wave Rules",
     "checkOrder": "Check Order",
+    "checkItem": "Check Order Item",
+    "checkDiffItem": "Check Diff Item",
     "checkDiff": "Check Diff",
     "transfer": "Transfer",
     "transferItem": "Transfer Item",
     "locRevise": "Loc Revise",
+    "reviseLog": "Loc Revise Log",
+    "reviseLogItem": "Loc Revise Log Item",
     "statisticReport": "Statistical Report",
     "locDeadReport": "Locs Dead Report",
     "stockStatistic": "Stock Statistic",
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index fe64198..ba38b1d 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -280,6 +280,11 @@
     "department": "閮ㄩ棬绠$悊",
     "token": "鐧诲綍鏃ュ織",
     "operation": "鎿嶄綔鏃ュ織",
+    "flowInstance": "娴佺▼瀹炰緥",
+    "flowStepInstance": "娴佺▼姝ラ瀹炰緥",
+    "flowStepLog": "娴佺▼姝ラ鏃ュ織",
+    "taskInstance": "浠诲姟瀹炰緥",
+    "taskInstanceNode": "浠诲姟瀹炰緥鑺傜偣",
     "config": "閰嶇疆鍙傛暟",
     "aiParam": "AI 鍙傛暟",
     "aiPrompt": "Prompt 绠$悊",
@@ -299,8 +304,9 @@
     "locArea": "閫昏緫鍒嗗尯(搴�)",
     "locAreaMat": "閫昏緫鍒嗗尯",
     "locAreaMatRela": "搴撳尯鐗╂枡鍏崇郴",
+    "locAreaRela": "搴撳尯鍏崇郴",
     "container": "瀹瑰櫒绠$悊(搴�)",
-    "contract": "鍚堝悓淇℃伅(搴�)",
+    "contract": "鍚堝悓淇℃伅",
     "qlyInspect": "璐ㄦ淇℃伅",
     "qlyIsptItem": "璐ㄦ淇℃伅鏄庣粏",
     "dictType": "鏁版嵁瀛楀吀",
@@ -314,6 +320,7 @@
     "asnOrderItemLog": "鏀惰揣鍘嗗彶鏄庣粏",
     "purchase": "PO鍗�",
     "purchaseItem": "PO鍗曟槑缁�",
+    "preparationItem": "澶囨枡鍗曟槑缁�",
     "whMat": "搴撳尯鐗╂枡鍏崇郴",
     "fields": "鎵╁睍瀛楁",
     "fieldsItem": "鎵╁睍瀛楁鏄庣粏",
@@ -337,6 +344,7 @@
     "logs": "鏃ュ織",
     "permissions": "鏉冮檺绠$悊",
     "delivery": "DO鍗�",
+    "deliveryItem": "DO鍗曟槑缁�",
     "outStock": "鍑哄簱閫氱煡鍗�",
     "outStockItem": "鍑哄簱鍗曟槑缁�",
     "inStockPoces": "鍏ュ簱绠$悊",
@@ -345,6 +353,7 @@
     "deviceBind": "璁惧缁戝畾",
     "tasks": "浠诲姟绠$悊",
     "wave": "娉㈡绠$悊",
+    "waveItem": "娉㈡鏄庣粏",
     "basStation": "绔欑偣绠$悊",
     "basContainer": "瀹瑰櫒瑙勫垯",
     "outBound": "鍑哄簱浣滀笟",
@@ -352,10 +361,14 @@
     "stockTransfer": "搴撲綅杞Щ",
     "waveRule": "娉㈡绛栫暐",
     "checkOrder": "鐩樼偣鍗�",
+    "checkItem": "鐩樼偣鍗曟槑缁�",
+    "checkDiffItem": "鐩樼偣宸紓鏄庣粏",
     "checkDiff": "鐩樼偣宸紓鍗�",
     "transfer": "璋冩嫈鍗�",
     "transferItem": "璋冩嫈鍗曟槑缁�",
     "locRevise": "搴撳瓨璋冩暣",
+    "reviseLog": "搴撲綅璋冩暣鏃ュ織",
+    "reviseLogItem": "搴撲綅璋冩暣鏃ュ織鏄庣粏",
     "statisticReport": "鎶ヨ〃绠$悊",
     "locDeadReport": "搴撳瓨鍋滄粸鎶ヨ〃",
     "stockStatistic": "鏃ュ叆搴撴眹鎬绘煡璇�",
diff --git a/rsf-design/src/plugins/iconify.collections.js b/rsf-design/src/plugins/iconify.collections.js
index 632d84d..a56d4b7 100644
--- a/rsf-design/src/plugins/iconify.collections.js
+++ b/rsf-design/src/plugins/iconify.collections.js
@@ -58,11 +58,17 @@
       'add-fill': {
         body: '<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/>'
       },
+      'add-line': {
+        body: '<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/>'
+      },
       'align-right': {
         body: '<path fill="currentColor" d="M3 4h18v2H3zm4 15h14v2H7zm-4-5h18v2H3zm4-5h14v2H7z"/>'
       },
       'archive-line': {
         body: '<path fill="currentColor" d="M3 10H2V4.003C2 3.449 2.455 3 2.992 3h18.016A.99.99 0 0 1 22 4.003V10h-1v10.002a.996.996 0 0 1-.993.998H3.993A.996.996 0 0 1 3 20.002zm16 0H5v9h14zM4 5v3h16V5zm5 7h6v2H9z"/>'
+      },
+      'archive-stack-line': {
+        body: '<path fill="currentColor" d="M4 5h16V3H4zm16 4H4V7h16zM3 11h7v2h4v-2h7v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1zm13 2v2H8v-2H5v6h14v-6z"/>'
       },
       'arrow-down-wide-fill': {
         body: '<path fill="currentColor" d="m12 15.632l8.968-4.748l-.936-1.768L12 13.368L3.968 9.116l-.936 1.768z"/>'
@@ -109,6 +115,9 @@
       'check-fill': {
         body: '<path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z"/>'
       },
+      'check-line': {
+        body: '<path fill="currentColor" d="m10 15.17l9.192-9.191l1.414 1.414L10 17.999l-6.364-6.364l1.414-1.414z"/>'
+      },
       'checkbox-blank-circle-line': {
         body: '<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16"/>'
       },
@@ -124,6 +133,9 @@
       'close-large-fill': {
         body: '<path fill="currentColor" d="M10.586 12L2.793 4.207l1.414-1.414L12 10.586l7.793-7.793l1.414 1.414L13.414 12l7.793 7.793l-1.414 1.414L12 13.414l-7.793 7.793l-1.414-1.414z"/>'
       },
+      'coin-line': {
+        body: '<path fill="currentColor" d="M12.005 4.003c6.075 0 11 2.686 11 6v4c0 3.314-4.925 6-11 6c-5.967 0-10.824-2.591-10.995-5.823l-.005-.177v-4c0-3.314 4.925-6 11-6m0 12c-3.72 0-7.01-1.008-9-2.55v.55c0 1.882 3.883 4 9 4c5.01 0 8.838-2.03 8.995-3.882l.005-.118l.001-.55c-1.99 1.542-5.28 2.55-9.001 2.55m0-10c-5.117 0-9 2.118-9 4s3.883 4 9 4s9-2.118 9-4s-3.883-4-9-4"/>'
+      },
       'command-fill': {
         body: '<path fill="currentColor" d="M10 8h4V6.5a3.5 3.5 0 1 1 3.5 3.5H16v4h1.5a3.5 3.5 0 1 1-3.5 3.5V16h-4v1.5A3.5 3.5 0 1 1 6.5 14H8v-4H6.5A3.5 3.5 0 1 1 10 6.5zM8 8V6.5A1.5 1.5 0 1 0 6.5 8zm0 8H6.5A1.5 1.5 0 1 0 8 17.5zm8-8h1.5A1.5 1.5 0 1 0 16 6.5zm0 8v1.5a1.5 1.5 0 1 0 1.5-1.5zm-6-6v4h4v-4z"/>'
       },
@@ -136,8 +148,14 @@
       'delete-bin-5-line': {
         body: '<path fill="currentColor" d="M4 8h16v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm2 2v10h12V10zm3 2h2v6H9zm4 0h2v6h-2zM7 5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v2h5v2H2V5zm2-1v1h6V4z"/>'
       },
+      'delete-bin-6-line': {
+        body: '<path fill="currentColor" d="M7 4V2h10v2h5v2h-2v15a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6H2V4zM6 6v14h12V6zm3 3h2v8H9zm4 0h2v8h-2z"/>'
+      },
       'drag-move-2-fill': {
         body: '<path fill="currentColor" d="M18 11V8l4 4l-4 4v-3h-5v5h3l-4 4l-4-4h3v-5H6v3l-4-4l4-4v3h5V6H8l4-4l4 4h-3v5z"/>'
+      },
+      'drag-move-2-line': {
+        body: '<path fill="currentColor" d="M11 11V5.828L9.172 7.657L7.757 6.243L12 2l4.243 4.243l-1.415 1.414L13 5.828V11h5.172l-1.829-1.828l1.414-1.415L22 12l-4.243 4.243l-1.414-1.415L18.172 13H13v5.172l1.828-1.829l1.415 1.414L12 22l-4.243-4.243l1.415-1.414L11 18.172V13H5.828l1.829 1.828l-1.414 1.415L2 12l4.243-4.243l1.414 1.415L5.828 11z"/>'
       },
       'dribbble-fill': {
         body: '<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2m6.605 4.61a8.5 8.5 0 0 1 1.93 5.314c-.281-.054-3.101-.629-5.943-.271c-.065-.141-.12-.293-.184-.445a25 25 0 0 0-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362M12 3.475c2.17 0 4.154.814 5.662 2.148c-.152.216-1.443 1.941-4.48 3.08c-1.399-2.57-2.95-4.675-3.189-5A8.7 8.7 0 0 1 12 3.475m-3.633.803a54 54 0 0 1 3.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.58 8.58 0 0 1 4.729-5.975M3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215c.25.477.477.965.694 1.453c-.109.033-.228.065-.336.098c-4.404 1.42-6.747 5.303-6.942 5.629a8.52 8.52 0 0 1-2.19-5.705M12 20.547a8.48 8.48 0 0 1-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337c.022-.01.033-.01.054-.022a35.3 35.3 0 0 1 1.823 6.475a8.4 8.4 0 0 1-3.341.684m4.761-1.465c-.086-.52-.542-3.015-1.66-6.084c2.68-.423 5.023.271 5.315.369a8.47 8.47 0 0 1-3.655 5.715"/>'
@@ -154,8 +172,14 @@
       'eye-line': {
         body: '<path fill="currentColor" d="M12 3c5.392 0 9.878 3.88 10.819 9c-.94 5.12-5.427 9-10.819 9s-9.878-3.88-10.818-9C2.122 6.88 6.608 3 12 3m0 16a9.005 9.005 0 0 0 8.778-7a9.005 9.005 0 0 0-17.555 0A9.005 9.005 0 0 0 12 19m0-2.5a4.5 4.5 0 1 1 0-9a4.5 4.5 0 0 1 0 9m0-2a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5"/>'
       },
+      'file-check-line': {
+        body: '<path fill="currentColor" d="M12 20v2H3.993A1 1 0 0 1 3 21.008V2.992C3 2.444 3.447 2 3.999 2H16l5 5v7h-2V8h-4V4H5v16zm2.465-.535L18 23l4.95-4.95l-1.414-1.414L18 20.172l-2.12-2.122z"/>'
+      },
       'file-list-3-line': {
         body: '<path fill="currentColor" d="M19 22H5a3 3 0 0 1-3-3V3a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v12h4v4a3 3 0 0 1-3 3m-1-5v2a1 1 0 1 0 2 0v-2zm-2 3V4H4v15a1 1 0 0 0 1 1zM6 7h8v2H6zm0 4h8v2H6zm0 4h5v2H6z"/>'
+      },
+      'file-warning-line': {
+        body: '<path fill="currentColor" d="M15 4H5v16h14V8h-4zM3 2.992C3 2.444 3.447 2 3.999 2H16l5 5v13.993A1 1 0 0 1 20.007 22H3.993A1 1 0 0 1 3 21.008zM11 15h2v2h-2zm0-8h2v6h-2z"/>'
       },
       'fire-line': {
         body: '<path fill="currentColor" d="M12 23a7.5 7.5 0 0 0 7.5-7.5c0-.866-.23-1.697-.5-2.47q-2.5 2.47-3.8 2.47c3.995-7 1.8-10-4.2-14c.5 5-2.796 7.274-4.138 8.537A7.5 7.5 0 0 0 12 23m.71-17.765c3.241 2.75 3.257 4.887.753 9.274c-.761 1.333.202 2.991 1.737 2.991c.688 0 1.384-.2 2.119-.595a5.5 5.5 0 1 1-9.087-5.412c.126-.118.765-.685.793-.71c.424-.38.773-.717 1.118-1.086c1.23-1.318 2.114-2.78 2.566-4.462"/>'
@@ -238,8 +262,14 @@
       'notification-3-line': {
         body: '<path fill="currentColor" d="M20 17h2v2H2v-2h2v-7a8 8 0 1 1 16 0zm-2 0v-7a6 6 0 0 0-12 0v7zm-9 4h6v2H9z"/>'
       },
+      'paint-line': {
+        body: '<path fill="currentColor" d="m19.228 18.732l1.767-1.767l1.768 1.767a2.5 2.5 0 1 1-3.535 0M8.878 1.08l11.314 11.313a1 1 0 0 1 0 1.415l-8.485 8.485a1 1 0 0 1-1.414 0l-8.485-8.485a1 1 0 0 1 0-1.415l7.778-7.778l-2.122-2.121zM11 6.03L3.929 13.1l7.07 7.072l7.072-7.071z"/>'
+      },
       'palette-line': {
         body: '<path fill="currentColor" d="M12 2c5.522 0 10 3.978 10 8.889a5.56 5.56 0 0 1-5.556 5.555h-1.966c-.922 0-1.667.745-1.667 1.667c0 .422.167.811.422 1.1c.267.3.434.689.434 1.122C13.667 21.256 12.9 22 12 22C6.478 22 2 17.522 2 12S6.478 2 12 2m-1.189 16.111a3.664 3.664 0 0 1 3.667-3.667h1.966A3.56 3.56 0 0 0 20 10.89C20 7.139 16.468 4 12 4a8 8 0 0 0-.676 15.972a3.65 3.65 0 0 1-.513-1.86M7.5 12a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3m9 0a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3M12 9a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3"/>'
+      },
+      'pause-circle-line': {
+        body: '<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M9 9h2v6H9zm4 0h2v6h-2z"/>'
       },
       'pencil-line': {
         body: '<path fill="currentColor" d="m15.728 9.576l-1.414-1.414L5 17.476v1.414h1.414zm1.414-1.414l1.414-1.414l-1.414-1.414l-1.414 1.414zm-9.9 12.728H3v-4.243L16.435 3.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414z"/>'
@@ -247,17 +277,35 @@
       'pie-chart-line': {
         body: '<path fill="currentColor" d="M9 2.458v2.124A8.003 8.003 0 0 0 12 20a8 8 0 0 0 7.419-5h2.123c-1.274 4.057-5.064 7-9.542 7c-5.523 0-10-4.477-10-10c0-4.478 2.943-8.268 7-9.542M12 2c5.523 0 10 4.477 10 10q0 .507-.05 1H11V2.05Q11.493 2 12 2m1 2.062V11h6.938A8.004 8.004 0 0 0 13 4.062"/>'
       },
+      'play-circle-line': {
+        body: '<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M10.622 8.415l4.879 3.252a.4.4 0 0 1 0 .666l-4.88 3.252a.4.4 0 0 1-.621-.332V8.747a.4.4 0 0 1 .622-.332"/>'
+      },
+      'play-line': {
+        body: '<path fill="currentColor" d="M16.394 12L10 7.737v8.526zm2.982.416L8.777 19.482A.5.5 0 0 1 8 19.066V4.934a.5.5 0 0 1 .777-.416l10.599 7.066a.5.5 0 0 1 0 .832"/>'
+      },
+      'play-list-add-line': {
+        body: '<path fill="currentColor" d="M2 18h10v2H2zm0-7h20v2H2zm0-7h20v2H2zm16 14v-3h2v3h3v2h-3v3h-2v-3h-3v-2z"/>'
+      },
       'plug-2-line': {
         body: '<path fill="currentColor" d="M13 18v2h6v2h-6a2 2 0 0 1-2-2v-2H8a4 4 0 0 1-4-4V7a1 1 0 0 1 1-1h2V2h2v4h6V2h2v4h2a1 1 0 0 1 1 1v7a4 4 0 0 1-4 4zm-5-2h8a2 2 0 0 0 2-2v-3H6v3a2 2 0 0 0 2 2m10-8H6v1h12zm-6 6.5a1 1 0 1 1 0-2a1 1 0 0 1 0 2M11 2h2v3h-2z"/>'
       },
       'price-tag-line': {
         body: '<path fill="currentColor" d="m3.005 7l8.445-5.63a1 1 0 0 1 1.11 0L21.005 7v14a1 1 0 0 1-1 1h-16a1 1 0 0 1-1-1zm2 1.07V20h14V8.07l-7-4.667zm7 2.93a2 2 0 1 1 0-4a2 2 0 0 1 0 4"/>'
       },
+      'printer-line': {
+        body: '<path fill="currentColor" d="M17 2a1 1 0 0 1 1 1v4h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-3v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2H3a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h3V3a1 1 0 0 1 1-1zm-1 15H8v3h8zm4-8H4v8h2v-1a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1h2zM8 10v2H5v-2zm8-6H8v3h8z"/>'
+      },
       'progress-2-line': {
         body: '<path fill="currentColor" d="M2 12c0 5.523 4.477 10 10 10s10-4.477 10-10S17.523 2 12 2S2 6.477 2 12m18 0a8 8 0 1 1-16 0a8 8 0 0 1 16 0m-8 0V6a6 6 0 0 1 6 6z"/>'
       },
+      'pulse-line': {
+        body: '<path fill="currentColor" d="m9 7.539l6 14L18.66 13H23v-2h-5.66L15 16.461l-6-14L5.34 11H1v2h5.66z"/>'
+      },
       'pushpin-2-line': {
         body: '<path fill="currentColor" d="M18 3v2h-1v6l2 3v2h-6v7h-2v-7H5v-2l2-3V5H6V3zM9 5v6.606L7.404 14h9.192L15 11.606V5z"/>'
+      },
+      'pushpin-line': {
+        body: '<path fill="currentColor" d="m13.827 1.69l8.486 8.485l-1.415 1.414l-.707-.707l-4.242 4.243l-.707 3.536l-1.415 1.414l-4.242-4.243l-4.95 4.95l-1.414-1.414l4.95-4.95l-4.243-4.243l1.414-1.414l3.536-.707l4.242-4.243l-.707-.707zm.707 3.536l-4.67 4.67l-2.822.565l6.5 6.5l.564-2.822l4.671-4.67z"/>'
       },
       'refresh-line': {
         body: '<path fill="currentColor" d="M5.463 4.433A9.96 9.96 0 0 1 12 2c5.523 0 10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3A8 8 0 0 0 6.46 6.228zm13.074 15.134A9.96 9.96 0 0 1 12 22C6.477 22 2 17.523 2 12c0-2.136.67-4.116 1.81-5.74L7 12H4a8 8 0 0 0 13.54 5.772z"/>'
@@ -271,8 +319,14 @@
       'search-line': {
         body: '<path fill="currentColor" d="m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z"/>'
       },
+      'send-plane-line': {
+        body: '<path fill="currentColor" d="m21.727 2.957l-5.454 19.086c-.15.529-.475.553-.717.07L11 13L1.923 9.37c-.51-.205-.503-.51.034-.689L21.043 2.32c.529-.176.832.12.684.638m-2.692 2.14L6.812 9.17l5.637 2.255l3.04 6.08z"/>'
+      },
       'server-line': {
         body: '<path fill="currentColor" d="M5 11h14V5H5zm16-7v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1m-2 9H5v6h14zM7 15h3v2H7zm0-8h3v2H7z"/>'
+      },
+      'service-line': {
+        body: '<path fill="currentColor" d="M3.161 4.469a6.5 6.5 0 0 1 8.84-.328a6.5 6.5 0 0 1 9.178 9.154l-7.765 7.79a2 2 0 0 1-2.719.102l-.11-.101l-7.764-7.791a6.5 6.5 0 0 1 .34-8.826m1.414 1.414a4.5 4.5 0 0 0-.146 6.21l.146.154L12 19.672l5.303-5.305l-3.535-3.534l-1.06 1.06a3 3 0 0 1-4.244-4.242l2.102-2.103a4.5 4.5 0 0 0-5.837.189zm8.486 2.828a1 1 0 0 1 1.414 0l4.242 4.242l.708-.706a4.5 4.5 0 0 0-6.211-6.51l-.153.146l-3.182 3.182a1 1 0 0 0-.078 1.327l.078.087a1 1 0 0 0 1.327.078l.087-.078z"/>'
       },
       'settings-line': {
         body: '<path fill="currentColor" d="m12 1l9.5 5.5v11L12 23l-9.5-5.5v-11zm0 2.311L4.5 7.653v8.694l7.5 4.342l7.5-4.342V7.653zM12 16a4 4 0 1 1 0-8a4 4 0 0 1 0 8m0-2a2 2 0 1 0 0-4a2 2 0 0 0 0 4"/>'
@@ -283,6 +337,12 @@
       'smartphone-line': {
         body: '<path fill="currentColor" d="M7 4v16h10V4zM6 2h12a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1m6 15a1 1 0 1 1 0 2a1 1 0 0 1 0-2"/>'
       },
+      'snowy-line': {
+        body: '<path fill="currentColor" d="m13 16.268l1.964-1.134l1 1.732L14 18l1.964 1.134l-1 1.732L13 19.732V22h-2v-2.268l-1.964 1.134l-1-1.732L10 18l-1.964-1.134l1-1.732L11 16.268V14h2zM17 18v-2h.5a3.5 3.5 0 1 0-2.5-5.95V10a6 6 0 1 0-8 5.659v2.089a8 8 0 1 1 9.458-10.65A5.5 5.5 0 1 1 17.5 18z"/>'
+      },
+      'stop-circle-line': {
+        body: '<path fill="currentColor" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10m0-2a8 8 0 1 0 0-16a8 8 0 0 0 0 16M9 9h6v6H9z"/>'
+      },
       'store-2-line': {
         body: '<path fill="currentColor" d="M21 13.242V20h1v2H2v-2h1v-6.758A4.5 4.5 0 0 1 1 9.5c0-.827.224-1.624.633-2.303L4.345 2.5a1 1 0 0 1 .866-.5H18.79a1 1 0 0 1 .866.5l2.703 4.682c.418.694.642 1.49.642 2.318c0 1.56-.794 2.935-2 3.742m-2 .73a4.5 4.5 0 0 1-3.75-1.36A4.5 4.5 0 0 1 12 14.001a4.5 4.5 0 0 1-3.25-1.387A4.5 4.5 0 0 1 5 13.973V20h14zM5.789 4L3.356 8.213a2.5 2.5 0 1 0 4.466 2.216c.335-.837 1.52-.837 1.856 0a2.5 2.5 0 0 0 4.644 0c.335-.837 1.52-.837 1.856 0a2.5 2.5 0 1 0 4.457-2.232L18.21 4z"/>'
       },
@@ -292,6 +352,12 @@
       'table-line': {
         body: '<path fill="currentColor" d="M4 8h16V5H4zm10 11v-9h-4v9zm2 0h4v-9h-4zm-8 0v-9H4v9zM3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1"/>'
       },
+      'task-line': {
+        body: '<path fill="currentColor" d="M19 4H5v16h14zM3 2.992C3 2.444 3.447 2 3.999 2h16a1 1 0 0 1 1 1L21 20.993A1 1 0 0 1 20.007 22H3.993A1 1 0 0 1 3 21.008zm8.293 10.13l4.243-4.243l1.414 1.414l-5.657 5.657l-3.89-3.89l1.415-1.414z"/>'
+      },
+      'timer-flash-line': {
+        body: '<path fill="currentColor" d="M6.382 5.968A8.96 8.96 0 0 1 12 4c2.125 0 4.078.736 5.618 1.968l1.453-1.453l1.414 1.414l-1.453 1.453A9 9 0 1 1 3 13c0-2.125.736-4.078 1.968-5.618L3.515 5.93l1.414-1.414zM12 20a7 7 0 1 0 0-14a7 7 0 0 0 0 14m1-8h3l-5 6.5V14H8l5-6.505zM8 1h8v2H8z"/>'
+      },
       'tools-line': {
         body: '<path fill="currentColor" d="M5.33 3.272a3.5 3.5 0 0 1 4.254 4.962l10.709 10.71l-1.414 1.414l-10.71-10.71a3.502 3.502 0 0 1-4.962-4.255L5.444 7.63a1.5 1.5 0 0 0 2.121-2.121zm10.367 1.883l3.182-1.768l1.414 1.415l-1.768 3.182l-1.768.353l-2.12 2.121l-1.415-1.414l2.121-2.121zm-6.718 8.132l1.415 1.414l-5.304 5.303a1 1 0 0 1-1.492-1.327l.078-.087z"/>'
       },
diff --git a/rsf-design/src/router/adapters/backendMenuAdapter.js b/rsf-design/src/router/adapters/backendMenuAdapter.js
index cabcb5d..fae5b8a 100644
--- a/rsf-design/src/router/adapters/backendMenuAdapter.js
+++ b/rsf-design/src/router/adapters/backendMenuAdapter.js
@@ -20,22 +20,85 @@
   whMat: '/basic-info/wh-mat',
   matnr: '/basic-info/wh-mat',
   matnrGroup: '/basic-info/matnr-group',
+  locType: '/basic-info/loc-type',
+  taskPathTemplate: '/basic-info/task-path-template',
+  taskPathTemplateMerge: '/basic-info/task-path-template-merge',
+  taskPathTemplateNode: '/basic-info/task-path-template-node',
   basContainer: '/basic-info/bas-container',
+  container: '/basic-info/bas-container',
+  contract: '/basic-info/contract',
+  basStation: '/basic-info/bas-station',
+  basStationArea: '/basic-info/bas-station-area',
+  deviceSite: '/basic-info/device-site',
+  deviceBind: '/basic-info/device-bind',
+  companys: '/basic-info/companys',
   warehouse: '/basic-info/warehouse',
   warehouseAreas: '/basic-info/warehouse-areas',
   loc: '/basic-info/loc',
+  locArea: '/basic-info/loc-area',
+  locAreaMat: '/basic-info/loc-area-mat',
+  locAreaMatRela: '/basic-info/loc-area-mat-rela',
+  locAreaRela: '/basic-info/loc-area-rela',
+  asnOrder: '/orders/asn-order',
+  asnOrderItem: '/orders/asn-order-item',
+  asnOrderLog: '/orders/asn-order-log',
+  purchase: '/orders/purchase',
+  purchaseItem: '/orders/purchase-item',
+  qlyIsptItem: '/manager/qly-ispt-item',
+  waitPakin: '/orders/wait-pakin',
+  waitPakinItem: '/orders/wait-pakin-item',
+  waitPakinLog: '/orders/wait-pakin-log',
+  waitPakinItemLog: '/orders/wait-pakin-item-log',
+  delivery: '/orders/delivery',
+  deliveryItem: '/orders/delivery-item',
+  outStock: '/orders/out-stock',
+  outStockItem: '/orders/out-stock-item',
+  check: '/orders/check',
+  checkItem: '/orders/check-item',
+  checkDiff: '/orders/check-diff',
+  checkDiffItem: '/orders/check-diff-item',
+  preparation: '/orders/preparation',
+  abnormal: '/abnormal',
   warehouseStock: '/stock/warehouse-stock',
+  locItem: '/manager/loc-item',
   warehouseAreasItem: '/stock/warehouse-areas-item',
   qlyInspect: '/manager/qly-inspect',
   locRevise: '/manager/loc-revise',
+  reviseLog: '/manager/revise-log',
+  reviseLogItem: '/manager/revise-log-item',
   freeze: '/manager/freeze',
   stock: '/manager/stock',
   task: '/manager/task',
+  taskItem: '/manager/task-item',
+  taskLog: '/manager/task-log',
+  taskItemLog: '/manager/task-item-log',
   locPreview: '/manager/loc-preview',
+  locDeadReport: '/manager/loc-dead-report',
+  checkOutBound: '/work/check-out-bound',
+  outBound: '/work/out-bound',
+  wave: '/orders/wave',
+  waveItem: '/orders/wave-item',
+  transfer: '/orders/transfer',
+  transferItem: '/orders/transfer-item',
+  statisticCount: '/reports/statistic-count',
+  inStatistic: '/statistics/in-statistic',
+  inStatisticItem: '/statistics/in-statistic-item',
+  outStatistic: '/statistics/out-statistic',
+  outStatisticItem: '/statistics/out-statistic-item',
+  stockTransfer: '/stock/stock-transfer',
   waveRule: '/manager/wave-rule',
   menuPda: '/manager/menu-pda',
+  stockItem: '/manager/stock-item',
   serialRule: '/system/serial-rule',
+  serialRuleItem: '/system/serial-rule-item',
   operationRecord: '/system/operation-record',
+  flowInstance: '/system/flow-instance',
+  flowStepInstance: '/system/flow-step-instance',
+  flowStepTemplate: '/system/flow-step-template',
+  subsystemFlowTemplate: '/system/subsystem-flow-template',
+  flowStepLog: '/system/flow-step-log',
+  taskInstance: '/system/task-instance',
+  taskInstanceNode: '/system/task-instance-node',
   userLogin: '/system/user-login'
 }
 
diff --git a/rsf-design/src/router/core/RouteRegistry.js b/rsf-design/src/router/core/RouteRegistry.js
index bccc392..3132e78 100644
--- a/rsf-design/src/router/core/RouteRegistry.js
+++ b/rsf-design/src/router/core/RouteRegistry.js
@@ -1,10 +1,10 @@
-import { ComponentLoader } from './ComponentLoader'
-import { RouteValidator } from './RouteValidator'
-import { RouteTransformer } from './RouteTransformer'
+import { ComponentLoader } from './ComponentLoader.js'
+import { RouteValidator } from './RouteValidator.js'
+import { RouteTransformer } from './RouteTransformer.js'
 class RouteRegistry {
-  constructor(router) {
+  constructor(router, options = {}) {
     this.router = router
-    this.componentLoader = new ComponentLoader()
+    this.componentLoader = options.componentLoader || new ComponentLoader()
     this.validator = new RouteValidator()
     this.transformer = new RouteTransformer(this.componentLoader)
     this.removeRouteFns = []
@@ -23,11 +23,27 @@
       throw new Error(`璺敱閰嶇疆楠岃瘉澶辫触: ${validationResult.errors.join(', ')}`)
     }
     const removeRouteFns = []
+    const existingRoutePaths = new Set(
+      (this.router.getRoutes?.() || []).map((route) => route?.path).filter(Boolean)
+    )
     menuList.forEach((route) => {
-      if (route.name && !this.router.hasRoute(route.name)) {
-        const routeConfig = this.transformer.transform(route)
-        const removeRouteFn = this.router.addRoute(routeConfig)
-        removeRouteFns.push(removeRouteFn)
+      const routeConfig = this.transformer.transform(route)
+      const routePath = routeConfig?.path
+
+      if (routePath && existingRoutePaths.has(routePath)) {
+        console.warn(`[RouteRegistry] 妫�娴嬪埌閲嶅璺緞锛屽凡璺宠繃鍔ㄦ�佹敞鍐�: ${routePath}`)
+        return
+      }
+
+      if (route.name && this.router.hasRoute(route.name)) {
+        console.warn(`[RouteRegistry] 妫�娴嬪埌閲嶅鍚嶇О锛屽凡璺宠繃鍔ㄦ�佹敞鍐�: ${route.name}`)
+        return
+      }
+
+      const removeRouteFn = this.router.addRoute(routeConfig)
+      removeRouteFns.push(removeRouteFn)
+      if (routePath) {
+        existingRoutePaths.add(routePath)
       }
     })
     this.removeRouteFns = removeRouteFns
diff --git a/rsf-design/src/router/core/RouteTransformer.js b/rsf-design/src/router/core/RouteTransformer.js
index 37456f8..a653a1d 100644
--- a/rsf-design/src/router/core/RouteTransformer.js
+++ b/rsf-design/src/router/core/RouteTransformer.js
@@ -1,4 +1,4 @@
-import { IframeRouteManager } from './IframeRouteManager'
+import { IframeRouteManager } from './IframeRouteManager.js'
 class RouteTransformer {
   constructor(componentLoader) {
     this.componentLoader = componentLoader
diff --git a/rsf-design/src/router/core/RouteValidator.js b/rsf-design/src/router/core/RouteValidator.js
index 1be4918..044887c 100644
--- a/rsf-design/src/router/core/RouteValidator.js
+++ b/rsf-design/src/router/core/RouteValidator.js
@@ -1,4 +1,4 @@
-import { RoutesAlias } from '../routesAlias'
+import { RoutesAlias } from '../routesAlias.js'
 class RouteValidator {
   constructor() {
     this.warnedRoutes = new Set()
@@ -23,11 +23,19 @@
    */
   checkDuplicates(routes, errors, warnings, parentPath = '') {
     const routeNameMap = /* @__PURE__ */ new Map()
+    const routePathMap = /* @__PURE__ */ new Map()
     const componentPathMap = /* @__PURE__ */ new Map()
     const checkRoutes = (routes2, parentPath2 = '') => {
       routes2.forEach((route) => {
         const currentPath = route.path || ''
         const fullPath = this.resolvePath(parentPath2, currentPath)
+        if (fullPath) {
+          if (routePathMap.has(fullPath)) {
+            warnings.push(`璺敱璺緞閲嶅: "${fullPath}"`)
+          } else {
+            routePathMap.set(fullPath, String(route.name || fullPath))
+          }
+        }
         if (route.name) {
           const routeName = String(route.name)
           if (routeNameMap.has(routeName)) {
diff --git a/rsf-design/src/router/guards/beforeEach.js b/rsf-design/src/router/guards/beforeEach.js
index d49d94a..8a71148 100644
--- a/rsf-design/src/router/guards/beforeEach.js
+++ b/rsf-design/src/router/guards/beforeEach.js
@@ -99,20 +99,41 @@
   })
   return false
 }
+function normalizeRoutePathSegment(path = '') {
+  if (!path) {
+    return ''
+  }
+  return path.replace(/^\/+/, '').replace(/\/+$/, '')
+}
+
+function joinRoutePath(parentPath = '', childPath = '') {
+  if (!childPath) {
+    return parentPath || '/'
+  }
+  if (childPath.startsWith('/')) {
+    return childPath
+  }
+
+  const normalizedParent = parentPath === '/' ? '' : normalizeRoutePathSegment(parentPath)
+  const normalizedChild = normalizeRoutePathSegment(childPath)
+  const fullPath = [normalizedParent, normalizedChild].filter(Boolean).join('/')
+  return `/${fullPath}`.replace(/\/+/g, '/')
+}
+
 function isStaticRoute(path) {
-  const checkRoute = (routes, targetPath) => {
+  const checkRoute = (routes, targetPath, parentPath = '') => {
     return routes.some((route) => {
       if (route.name === 'Exception404') {
         return false
       }
-      const routePath = route.path
+      const routePath = joinRoutePath(parentPath, route.path)
       const pattern = routePath.replace(/:[^/]+/g, '[^/]+').replace(/\*/g, '.*')
       const regex = new RegExp(`^${pattern}$`)
       if (regex.test(targetPath)) {
         return true
       }
       if (route.children && route.children.length > 0) {
-        return checkRoute(route.children, targetPath)
+        return checkRoute(route.children, targetPath, routePath)
       }
       return false
     })
diff --git a/rsf-design/src/router/index.js b/rsf-design/src/router/index.js
index 5e2da45..7f51332 100644
--- a/rsf-design/src/router/index.js
+++ b/rsf-design/src/router/index.js
@@ -14,5 +14,5 @@
   setupAfterEachGuard(router)
   app.use(router)
 }
-const HOME_PAGE_PATH = ''
+const HOME_PAGE_PATH = '/dashboard/console'
 export { HOME_PAGE_PATH, initRouter, router }
diff --git a/rsf-design/src/router/routes/staticRoutes.js b/rsf-design/src/router/routes/staticRoutes.js
index 13cebd2..7dafda8 100644
--- a/rsf-design/src/router/routes/staticRoutes.js
+++ b/rsf-design/src/router/routes/staticRoutes.js
@@ -1,11 +1,4 @@
 const staticRoutes = [
-  // 涓嶉渶瑕佺櫥褰曞氨鑳借闂殑璺敱绀轰緥
-  // {
-  //   path: '/welcome',
-  //   name: 'WelcomeStatic',
-  //   component: () => import('@views/dashboard/console/index.vue'),
-  //   meta: { title: 'menus.dashboard.title' }
-  // },
   {
     path: '/dashboard',
     component: () => import('@views/index/index.vue'),
@@ -21,74 +14,6 @@
           icon: 'ri:home-smile-2-line',
           keepAlive: false,
           fixedTab: true
-        }
-      }
-    ]
-  },
-  {
-    path: '/basic-info',
-    component: () => import('@views/index/index.vue'),
-    name: 'BasicInfo',
-    meta: { title: 'menu.basicInfo' },
-    children: [
-      {
-        path: 'wh-mat',
-        name: 'WhMat',
-        component: () => import('@views/basic-info/wh-mat/index.vue'),
-        meta: {
-          title: 'menu.matnr',
-          icon: 'ri:bill-line',
-          keepAlive: false
-        }
-      },
-      {
-        path: 'matnr-group',
-        name: 'MatnrGroup',
-        component: () => import('@views/basic-info/matnr-group/index.vue'),
-        meta: {
-          title: 'menu.matnrGroup',
-          icon: 'ri:git-branch-line',
-          keepAlive: false
-        }
-      },
-      {
-        path: 'bas-container',
-        name: 'BasContainer',
-        component: () => import('@views/basic-info/bas-container/index.vue'),
-        meta: {
-          title: 'menu.basContainer',
-          icon: 'ri:archive-line',
-          keepAlive: false
-        }
-      },
-      {
-        path: 'warehouse',
-        name: 'Warehouse',
-        component: () => import('@views/basic-info/warehouse/index.vue'),
-        meta: {
-          title: 'menu.warehouse',
-          icon: 'ri:store-2-line',
-          keepAlive: false
-        }
-      },
-      {
-        path: 'warehouse-areas',
-        name: 'WarehouseAreas',
-        component: () => import('@views/basic-info/warehouse-areas/index.vue'),
-        meta: {
-          title: 'menu.warehouseAreas',
-          icon: 'ri:layout-grid-line',
-          keepAlive: false
-        }
-      },
-      {
-        path: 'loc',
-        name: 'Loc',
-        component: () => import('@views/basic-info/loc/index.vue'),
-        meta: {
-          title: 'menu.loc',
-          icon: 'ri:map-pin-2-line',
-          keepAlive: false
         }
       }
     ]
@@ -118,12 +43,6 @@
     meta: { title: '403', isHideTab: true }
   },
   {
-    path: '/:pathMatch(.*)*',
-    name: 'Exception404',
-    component: () => import('@views/exception/404/index.vue'),
-    meta: { title: '404', isHideTab: true }
-  },
-  {
     path: '/500',
     name: 'Exception500',
     component: () => import('@views/exception/500/index.vue'),
@@ -135,7 +54,6 @@
     name: 'Outside',
     meta: { title: 'menus.outside.title' },
     children: [
-      // iframe 鍐呭祵椤甸潰
       {
         path: '/outside/iframe/:path',
         name: 'Iframe',
@@ -143,6 +61,13 @@
         meta: { title: 'iframe' }
       }
     ]
+  },
+  {
+    path: '/:pathMatch(.*)*',
+    name: 'Exception404',
+    component: () => import('@views/exception/404/index.vue'),
+    meta: { title: '404', isHideTab: true }
   }
 ]
+
 export { staticRoutes }
diff --git a/rsf-design/src/store/modules/menu.js b/rsf-design/src/store/modules/menu.js
index db546d2..f7dded2 100644
--- a/rsf-design/src/store/modules/menu.js
+++ b/rsf-design/src/store/modules/menu.js
@@ -1,6 +1,5 @@
 import { defineStore } from 'pinia'
 import { ref } from 'vue'
-import { getFirstMenuPath } from '@/utils'
 import { HOME_PAGE_PATH } from '@/router'
 const useMenuStore = defineStore('menuStore', () => {
   const homePath = ref(HOME_PAGE_PATH)
@@ -9,7 +8,7 @@
   const removeRouteFns = ref([])
   const setMenuList = (list) => {
     menuList.value = list
-    setHomePath(HOME_PAGE_PATH || getFirstMenuPath(list))
+    setHomePath(HOME_PAGE_PATH)
   }
   const getHomePath = () => homePath.value
   const setHomePath = (path) => {
diff --git a/rsf-design/src/store/modules/worktab.js b/rsf-design/src/store/modules/worktab.js
index da66bf0..b06c68b 100644
--- a/rsf-design/src/store/modules/worktab.js
+++ b/rsf-design/src/store/modules/worktab.js
@@ -4,6 +4,14 @@
 import { useCommon } from '@/hooks/core/useCommon'
 import { normalizeIcon } from '@/router/adapters/backendMenuAdapter.js'
 
+function normalizeLegacyTabPath(path) {
+  if (path === '/dashboard') {
+    return '/dashboard/console'
+  }
+
+  return path
+}
+
 const normalizeTabState = (tab) => {
   if (!tab || typeof tab !== 'object') {
     return tab
@@ -11,6 +19,7 @@
 
   return {
     ...tab,
+    path: normalizeLegacyTabPath(tab.path),
     icon: normalizeIcon(tab.icon)
   }
 }
@@ -24,25 +33,30 @@
     const hasOpenedTabs = computed(() => opened.value.length > 0)
     const hasMultipleTabs = computed(() => opened.value.length > 1)
     const currentTabIndex = computed(() =>
-      current.value.path ? opened.value.findIndex((tab) => tab.path === current.value.path) : -1
+      current.value.path
+        ? opened.value.findIndex((tab) => tab.path === normalizeLegacyTabPath(current.value.path))
+        : -1
     )
     const findTabIndex = (path) => {
-      return opened.value.findIndex((tab) => tab.path === path)
+      const resolvedPath = normalizeLegacyTabPath(path)
+      return opened.value.findIndex((tab) => tab.path === resolvedPath)
     }
     const getTab = (path) => {
-      return opened.value.find((tab) => tab.path === path)
+      const resolvedPath = normalizeLegacyTabPath(path)
+      return opened.value.find((tab) => tab.path === resolvedPath)
     }
     const isTabClosable = (tab) => {
       return !tab.fixedTab
     }
     const safeRouterPush = (tab) => {
-      if (!tab.path) {
+      const resolvedPath = normalizeLegacyTabPath(tab.path)
+      if (!resolvedPath) {
         console.warn('灏濊瘯璺宠浆鍒版棤鏁堣矾寰勭殑鏍囩椤�')
         return
       }
       try {
         router.push({
-          path: tab.path,
+          path: resolvedPath,
           query: tab.query
         })
       } catch (error) {
@@ -50,7 +64,8 @@
       }
     }
     const openTab = (tab) => {
-      if (!tab.path) {
+      const resolvedPath = normalizeLegacyTabPath(tab.path)
+      if (!resolvedPath) {
         console.warn('灏濊瘯鎵撳紑鏃犳晥鐨勬爣绛鹃〉')
         return
       }
@@ -62,11 +77,14 @@
         existingIndex = opened.value.findIndex((t) => t.name === tab.name)
       }
       if (existingIndex === -1) {
-        existingIndex = findTabIndex(tab.path)
+        existingIndex = findTabIndex(resolvedPath)
       }
       if (existingIndex === -1) {
         const insertIndex = tab.fixedTab ? findFixedTabInsertIndex() : opened.value.length
-        const newTab = normalizeTabState(tab)
+        const newTab = normalizeTabState({
+          ...tab,
+          path: resolvedPath
+        })
         if (tab.fixedTab) {
           opened.value.splice(insertIndex, 0, newTab)
         } else {
@@ -77,7 +95,7 @@
         const existingTab = opened.value[existingIndex]
         opened.value[existingIndex] = normalizeTabState({
           ...existingTab,
-          path: tab.path,
+          path: resolvedPath,
           params: tab.params,
           query: tab.query,
           title: tab.title || existingTab.title,
@@ -259,8 +277,9 @@
               if (routes.some((r) => r.name === tab.name)) return true
             }
             if (tab.path) {
+              const resolvedPath = normalizeLegacyTabPath(tab.path)
               const resolved = routerInstance.resolve({
-                path: tab.path,
+                path: resolvedPath,
                 query: tab.query || void 0
               })
               return resolved.matched.length > 0
diff --git a/rsf-design/src/utils/backend-menu-title.js b/rsf-design/src/utils/backend-menu-title.js
index 4599d84..8db1f59 100644
--- a/rsf-design/src/utils/backend-menu-title.js
+++ b/rsf-design/src/utils/backend-menu-title.js
@@ -50,6 +50,8 @@
   'menu.locItem': '搴撳瓨鏄庣粏',
   'menu.locPreview': '搴撲綅鏄庣粏',
   'menu.locRevise': '搴撳瓨璋冩暣',
+  'menu.reviseLog': '搴撲綅璋冩暣鏃ュ織',
+  'menu.reviseLogItem': '搴撲綅璋冩暣鏃ュ織鏄庣粏',
   'menu.locType': '搴撲綅绫诲瀷(搴�)',
   'menu.logs': '鏃ュ織',
   'menu.matnr': '鐗╂枡',
diff --git a/rsf-design/src/utils/sys/requestGuard.js b/rsf-design/src/utils/sys/requestGuard.js
new file mode 100644
index 0000000..c98bc04
--- /dev/null
+++ b/rsf-design/src/utils/sys/requestGuard.js
@@ -0,0 +1,66 @@
+import { ElMessage } from 'element-plus'
+
+const DEFAULT_REQUEST_GUARD_TIMEOUT = 8e3
+const DEFAULT_REQUEST_TIMEOUT_MESSAGE = '璇锋眰瓒呮椂锛屽凡鍋滄绛夊緟'
+
+function withRequestGuard(task, fallbackValue, options = {}) {
+  const { timeoutMs = DEFAULT_REQUEST_GUARD_TIMEOUT, onFallback } = options
+
+  return new Promise((resolve) => {
+    let settled = false
+
+    const finish = (value, reason, error) => {
+      if (settled) return
+      settled = true
+      clearTimeout(timer)
+      if (typeof onFallback === 'function' && reason) {
+        onFallback(reason, error)
+      }
+      resolve(value)
+    }
+
+    const timer = setTimeout(() => finish(fallbackValue, 'timeout'), timeoutMs)
+
+    Promise.resolve(task).then(
+      (value) => finish(value),
+      (error) => finish(fallbackValue, 'error', error)
+    )
+  })
+}
+
+function guardRequestWithMessage(task, fallbackValue, options = {}) {
+  const {
+    timeoutMs = DEFAULT_REQUEST_GUARD_TIMEOUT,
+    timeoutMessage = DEFAULT_REQUEST_TIMEOUT_MESSAGE,
+    errorMessage,
+    resolveErrorMessage
+  } = options
+
+  return withRequestGuard(task, fallbackValue, {
+    timeoutMs,
+    onFallback: (reason, error) => {
+      if (reason === 'timeout') {
+        if (timeoutMessage) {
+          ElMessage.warning(timeoutMessage)
+        }
+        return
+      }
+
+      const resolvedMessage =
+        typeof resolveErrorMessage === 'function'
+          ? resolveErrorMessage(error)
+          : errorMessage || error?.message
+
+      if (resolvedMessage) {
+        ElMessage.error(resolvedMessage)
+      }
+    }
+  })
+}
+
+export {
+  DEFAULT_REQUEST_GUARD_TIMEOUT,
+  DEFAULT_REQUEST_TIMEOUT_MESSAGE,
+  guardRequestWithMessage,
+  withRequestGuard
+}
diff --git a/rsf-design/src/views/abnormal/abnormalPage.helpers.js b/rsf-design/src/views/abnormal/abnormalPage.helpers.js
new file mode 100644
index 0000000..115e753
--- /dev/null
+++ b/rsf-design/src/views/abnormal/abnormalPage.helpers.js
@@ -0,0 +1,174 @@
+import { defaultResponseAdapter } from '../../utils/table/tableUtils.js'
+
+const ABNORMAL_PAGE_TITLE = '寮傚父绠$悊'
+
+const ABNORMAL_CARD_DEFINITIONS = [
+  {
+    key: 'checkDiff',
+    title: '鐩樼偣宸紓',
+    route: '/manager/checkDiff',
+    icon: 'ri:file-warning-line',
+    iconBoxClass: 'bg-[rgba(245,108,108,0.12)]',
+    iconClass: 'text-[var(--el-color-danger)]',
+    emptyHint: '鏆傛棤宸紓鍗�'
+  },
+  {
+    key: 'qlyInspect',
+    title: '璐ㄦ璁板綍',
+    route: '/manager/qlyInspect',
+    icon: 'ri:shield-check-line',
+    iconBoxClass: 'bg-[rgba(64,158,255,0.12)]',
+    iconClass: 'text-[var(--el-color-primary)]',
+    emptyHint: '鏆傛棤璐ㄦ璁板綍'
+  },
+  {
+    key: 'freeze',
+    title: '鍐荤粨搴撳瓨',
+    route: '/manager/freeze',
+    icon: 'ri:snowy-line',
+    iconBoxClass: 'bg-[rgba(103,194,58,0.12)]',
+    iconClass: 'text-[var(--el-color-success)]',
+    emptyHint: '鏆傛棤鍐荤粨搴撳瓨'
+  },
+  {
+    key: 'locRevise',
+    title: '搴撲綅璋冩暣',
+    route: '/manager/locRevise',
+    icon: 'ri:drag-move-2-line',
+    iconBoxClass: 'bg-[rgba(230,162,60,0.12)]',
+    iconClass: 'text-[var(--el-color-warning)]',
+    emptyHint: '鏆傛棤搴撲綅璋冩暣'
+  }
+]
+
+function normalizeSummaryResponse(response) {
+  const normalized = defaultResponseAdapter(response)
+  return {
+    total: Number(normalized.total || 0),
+    records: Array.isArray(normalized.records) ? normalized.records : []
+  }
+}
+
+function createAbnormalOverviewState() {
+  return Object.fromEntries(
+    ABNORMAL_CARD_DEFINITIONS.map((item) => [
+      item.key,
+      {
+        total: 0,
+        records: []
+      }
+    ])
+  )
+}
+
+function resolveCheckDiffActivity(record) {
+  return {
+    key: `checkDiff-${record.id || record.orderCode || record.updateTime || ''}`,
+    route: '/manager/checkDiff',
+    source: '鐩樼偣宸紓',
+    title: record.orderCode || '鏈懡鍚嶅樊寮傚崟',
+    summary: `${record.exceStatus$ || '--'} 路 ${record.areaName || '鏈垎閰嶅簱鍖�'}`,
+    time: record.updateTime$ || record.createTime$ || '--',
+    sortTime: record.updateTime || record.createTime || ''
+  }
+}
+
+function resolveQlyInspectActivity(record) {
+  return {
+    key: `qlyInspect-${record.id || record.code || record.updateTime || ''}`,
+    route: '/manager/qlyInspect',
+    source: '璐ㄦ璁板綍',
+    title: record.code || '鏈懡鍚嶈川妫�鍗�',
+    summary: `${record.isptStatus$ || '--'} 路 ${record.asnCode || '鏈叧鑱旈�氱煡鍗�'}`,
+    time: record.updateTime$ || record.createTime$ || '--',
+    sortTime: record.updateTime || record.createTime || ''
+  }
+}
+
+function resolveFreezeActivity(record) {
+  return {
+    key: `freeze-${record.id || record.locCode || record.updateTime || ''}`,
+    route: '/manager/freeze',
+    source: '鍐荤粨搴撳瓨',
+    title: record.locCode || '鏈懡鍚嶅簱浣�',
+    summary: `${record.maktx || '--'} 路 鍋滄粸 ${record.deadTime || 0} 澶ー,
+    time: record.updateTime$ || record.createTime$ || '--',
+    sortTime: record.updateTime || record.createTime || ''
+  }
+}
+
+function resolveLocReviseActivity(record) {
+  return {
+    key: `locRevise-${record.id || record.code || record.updateTime || ''}`,
+    route: '/manager/locRevise',
+    source: '搴撲綅璋冩暣',
+    title: record.code || '鏈懡鍚嶈皟鏁村崟',
+    summary: `${record.exceStatus$ || '--'} 路 ${record.areaName || '鏈垎閰嶅簱鍖�'}`,
+    time: record.updateTime$ || record.createTime$ || '--',
+    sortTime: record.updateTime || record.createTime || ''
+  }
+}
+
+function buildAbnormalOverviewCards(overviewState) {
+  return ABNORMAL_CARD_DEFINITIONS.map((item) => {
+    const state = overviewState[item.key] || { total: 0, records: [] }
+    const latestRecord = Array.isArray(state.records) ? state.records[0] : null
+    return {
+      ...item,
+      total: Number(state.total || 0),
+      subtitle: latestRecord?.updateTime$ || latestRecord?.createTime$ || item.emptyHint,
+      badgeText: latestRecord ? '鏈�杩戞洿鏂�' : '褰撳墠鐘舵��'
+    }
+  })
+}
+
+function buildAbnormalEntryItems(overviewState) {
+  return ABNORMAL_CARD_DEFINITIONS.map((item) => {
+    const state = overviewState[item.key] || { total: 0, records: [] }
+    const latestRecord = Array.isArray(state.records) ? state.records[0] : null
+    return {
+      key: item.key,
+      title: item.title,
+      route: item.route,
+      icon: item.icon,
+      count: Number(state.total || 0),
+      description:
+        latestRecord?.orderCode ||
+        latestRecord?.code ||
+        latestRecord?.locCode ||
+        latestRecord?.maktx ||
+        item.emptyHint
+    }
+  })
+}
+
+function buildAbnormalActivityItems(overviewState) {
+  const activities = []
+  const sourceResolvers = {
+    checkDiff: resolveCheckDiffActivity,
+    qlyInspect: resolveQlyInspectActivity,
+    freeze: resolveFreezeActivity,
+    locRevise: resolveLocReviseActivity
+  }
+
+  Object.entries(sourceResolvers).forEach(([key, resolver]) => {
+    const records = overviewState[key]?.records || []
+    records.slice(0, 3).forEach((record) => {
+      activities.push(resolver(record))
+    })
+  })
+
+  return activities
+    .sort((left, right) => String(right.sortTime).localeCompare(String(left.sortTime)))
+    .slice(0, 8)
+}
+
+export {
+  ABNORMAL_CARD_DEFINITIONS,
+  ABNORMAL_PAGE_TITLE,
+  buildAbnormalActivityItems,
+  buildAbnormalEntryItems,
+  buildAbnormalOverviewCards,
+  createAbnormalOverviewState,
+  normalizeSummaryResponse
+}
diff --git a/rsf-design/src/views/abnormal/index.vue b/rsf-design/src/views/abnormal/index.vue
new file mode 100644
index 0000000..c6bdf38
--- /dev/null
+++ b/rsf-design/src/views/abnormal/index.vue
@@ -0,0 +1,153 @@
+<template>
+  <div class="art-full-height flex flex-col gap-6">
+    <section class="grid gap-6 md:grid-cols-2 xl:grid-cols-4" v-loading="sectionLoading.summary">
+      <div
+        v-for="item in overviewCards"
+        :key="item.key"
+        class="art-card flex cursor-pointer items-start justify-between rounded-3xl px-7 py-6"
+        @click="navigateTo(item.route)"
+      >
+        <div class="min-w-0 pr-6">
+          <p class="text-sm font-medium text-g-700">{{ item.title }}</p>
+          <ArtCountTo class="mt-3 block text-[2.3rem] font-semibold leading-none text-g-900" :target="item.total" :duration="1200" />
+          <div class="mt-4 flex items-center gap-2 text-sm">
+            <span class="text-g-500">{{ item.badgeText }}</span>
+            <span class="text-g-600">{{ item.subtitle }}</span>
+          </div>
+        </div>
+        <div class="flex size-13 shrink-0 items-center justify-center rounded-2xl" :class="item.iconBoxClass">
+          <ArtSvgIcon :icon="item.icon" class="text-2xl" :class="item.iconClass" />
+        </div>
+      </div>
+    </section>
+
+    <section class="grid gap-6 xl:grid-cols-[0.92fr_1.08fr]">
+      <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.summary">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>寮傚父鍏ュ彛</h4>
+            <p>鑱氬悎鐜版湁寮傚父鐩稿叧鐪熷疄涓氬姟椤�</p>
+          </div>
+        </div>
+
+        <div class="mt-3 grid gap-3">
+          <button
+            v-for="item in entryItems"
+            :key="item.key"
+            class="flex items-center justify-between rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-4 py-4 text-left transition hover:border-[var(--el-color-primary-light-5)]"
+            type="button"
+            @click="navigateTo(item.route)"
+          >
+            <div class="flex min-w-0 items-center gap-3">
+              <div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-[var(--el-color-primary-light-9)]">
+                <ArtSvgIcon :icon="item.icon" class="text-xl text-[var(--el-color-primary)]" />
+              </div>
+              <div class="min-w-0">
+                <p class="truncate text-sm font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
+                <p class="mt-1 truncate text-xs text-g-500">{{ item.description }}</p>
+              </div>
+            </div>
+            <div class="pl-3 text-right">
+              <p class="text-2xl font-semibold text-[var(--art-gray-900)]">{{ item.count }}</p>
+              <p class="mt-1 text-xs text-g-500">鏉¤褰�</p>
+            </div>
+          </button>
+        </div>
+      </div>
+
+      <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.summary">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>鏈�杩戝紓甯稿姩鎬�</h4>
+            <p>鏉ヨ嚜鐩樼偣宸紓銆佽川妫�銆佸喕缁撳簱瀛樸�佸簱浣嶈皟鏁�</p>
+          </div>
+        </div>
+
+        <div class="mt-3 h-[calc(100%-3.75rem)] overflow-hidden">
+          <ElScrollbar>
+            <div
+              v-for="item in activityItems"
+              :key="item.key"
+              class="flex items-start gap-4 border-b border-g-300 py-4 last:border-b-0"
+            >
+              <div class="flex flex-col items-center pt-1">
+                <span class="size-3 rounded-full bg-[var(--el-color-danger)]"></span>
+                <span class="mt-2 min-h-10 w-px bg-[var(--art-border-color)]"></span>
+              </div>
+              <div class="min-w-0 flex-1">
+                <div class="flex items-center gap-2">
+                  <p class="truncate text-base font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
+                  <ElTag size="small" effect="light" type="danger">{{ item.source }}</ElTag>
+                </div>
+                <p class="mt-2 text-sm text-g-600">{{ item.summary }}</p>
+                <p class="mt-2 text-xs text-g-500">{{ item.time }}</p>
+              </div>
+              <ElButton text type="primary" @click="navigateTo(item.route)">鏌ョ湅</ElButton>
+            </div>
+
+            <ElEmpty v-if="!activityItems.length" description="鏆傛棤寮傚父鍔ㄦ��" :image-size="88" />
+          </ElScrollbar>
+        </div>
+      </div>
+    </section>
+  </div>
+</template>
+
+<script setup>
+  import { useRouter } from 'vue-router'
+  import { withRequestGuard } from '@/utils/sys/requestGuard'
+  import { fetchCheckDiffPage } from '@/api/check-diff'
+  import { fetchQlyInspectPage } from '@/api/qly-inspect'
+  import { fetchFreezePage } from '@/api/freeze'
+  import { fetchLocRevisePage } from '@/api/loc-revise'
+  import {
+    ABNORMAL_PAGE_TITLE,
+    buildAbnormalActivityItems,
+    buildAbnormalEntryItems,
+    buildAbnormalOverviewCards,
+    createAbnormalOverviewState,
+    normalizeSummaryResponse
+  } from './abnormalPage.helpers'
+
+  defineOptions({ name: 'AbnormalManage' })
+
+  const router = useRouter()
+  const sectionLoading = reactive({
+    summary: false
+  })
+  const overviewState = ref(createAbnormalOverviewState())
+
+  const overviewCards = computed(() => buildAbnormalOverviewCards(overviewState.value))
+  const entryItems = computed(() => buildAbnormalEntryItems(overviewState.value))
+  const activityItems = computed(() => buildAbnormalActivityItems(overviewState.value))
+
+  onMounted(() => {
+    document.title = `${ABNORMAL_PAGE_TITLE} - RSF Design`
+    void loadOverview()
+  })
+
+  async function loadOverview() {
+    sectionLoading.summary = true
+
+    const [checkDiffResponse, qlyInspectResponse, freezeResponse, locReviseResponse] = await Promise.all([
+      withRequestGuard(fetchCheckDiffPage({ current: 1, pageSize: 5 }), { records: [], total: 0 }),
+      withRequestGuard(fetchQlyInspectPage({ current: 1, pageSize: 5 }), { records: [], total: 0 }),
+      withRequestGuard(fetchFreezePage({ current: 1, pageSize: 5 }), { records: [], total: 0 }),
+      withRequestGuard(fetchLocRevisePage({ current: 1, pageSize: 5 }), { records: [], total: 0 })
+    ])
+
+    overviewState.value = {
+      checkDiff: normalizeSummaryResponse(checkDiffResponse),
+      qlyInspect: normalizeSummaryResponse(qlyInspectResponse),
+      freeze: normalizeSummaryResponse(freezeResponse),
+      locRevise: normalizeSummaryResponse(locReviseResponse)
+    }
+
+    sectionLoading.summary = false
+  }
+
+  function navigateTo(path) {
+    if (!path) return
+    router.push(path)
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-container/basContainerPage.helpers.js b/rsf-design/src/views/basic-info/bas-container/basContainerPage.helpers.js
new file mode 100644
index 0000000..8a56e0d
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/basContainerPage.helpers.js
@@ -0,0 +1,342 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const BAS_CONTAINER_REPORT_TITLE = '瀹瑰櫒瑙勫垯鎶ヨ〃'
+export const BAS_CONTAINER_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+export function createBasContainerSearchState() {
+  return {
+    condition: '',
+    code: '',
+    containerType: '',
+    codeType: '',
+    areas: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createBasContainerFormState() {
+  return {
+    id: void 0,
+    code: '',
+    containerType: void 0,
+    codeType: '',
+    areas: [],
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getBasContainerStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getBasContainerPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getBasContainerStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildBasContainerSearchParams(params = {}) {
+  const result = {}
+
+  ;['condition', 'code', 'codeType', 'areas', 'memo', 'timeStart', 'timeEnd'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.containerType !== '' && params.containerType !== null && params.containerType !== undefined) {
+    result.containerType = Number(params.containerType)
+  }
+
+  if (params.status !== '' && params.status !== null && params.status !== undefined) {
+    result.status = Number(params.status)
+  }
+
+  return result
+}
+
+export function buildBasContainerPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildBasContainerSearchParams(params)
+  }
+}
+
+export function normalizeBasContainerAreas(areas = []) {
+  if (!Array.isArray(areas)) {
+    return []
+  }
+
+  return areas
+    .map((item, index) => normalizeBasContainerAreaRecord(item, index))
+    .filter(Boolean)
+    .sort((left, right) => {
+      const leftSort = normalizeNumber(left.sort, Number.MAX_SAFE_INTEGER)
+      const rightSort = normalizeNumber(right.sort, Number.MAX_SAFE_INTEGER)
+      return leftSort - rightSort
+    })
+    .map((item, index) => ({
+      id: item.id,
+      sort: index + 1
+    }))
+}
+
+export function buildBasContainerSavePayload(formData = {}) {
+  const areas = normalizeBasContainerAreas(formData.areas)
+  const payload = {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code),
+    ...(formData.containerType !== void 0 && formData.containerType !== null && formData.containerType !== ''
+      ? { containerType: Number(formData.containerType) }
+      : {}),
+    codeType: normalizeText(formData.codeType),
+    areas,
+    status: formData.status !== void 0 && formData.status !== null && formData.status !== ''
+      ? Number(formData.status)
+      : 1,
+    memo: normalizeText(formData.memo)
+  }
+
+  if (!payload.areas.length) {
+    delete payload.areas
+  }
+
+  return payload
+}
+
+export function buildBasContainerDialogModel(record = {}) {
+  return {
+    ...createBasContainerFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    containerType:
+      record.containerType !== void 0 && record.containerType !== null && record.containerType !== ''
+        ? Number(record.containerType)
+        : record.containerType === 0
+          ? 0
+          : void 0,
+    codeType: normalizeText(record.codeType || ''),
+    areas: normalizeBasContainerAreas(record.areas),
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeBasContainerDetailRecord(record = {}, resolveAreaLabel) {
+  const areas = normalizeBasContainerAreas(record.areas)
+  const statusMeta = getBasContainerStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    areas,
+    containerTypeText: normalizeText(record.containerType$ || record.containerTypeText || ''),
+    areasText: buildBasContainerAreasText(areas, record.areas, resolveAreaLabel),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    areasIds: areas.map((item) => item.id)
+  }
+}
+
+export function normalizeBasContainerListRow(record = {}, resolveAreaLabel) {
+  return normalizeBasContainerDetailRecord(record, resolveAreaLabel)
+}
+
+export function buildBasContainerPrintRows(records = [], resolveAreaLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeBasContainerListRow(record, resolveAreaLabel))
+}
+
+export function buildBasContainerReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = BAS_CONTAINER_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: BAS_CONTAINER_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...BAS_CONTAINER_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function buildBasContainerContainerTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveBasContainerAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+
+      const value = item.id ?? item.areaId ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+
+      return {
+        value: Number(value),
+        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+function normalizeBasContainerAreaRecord(item, index) {
+  if (item === null || item === undefined) {
+    return null
+  }
+
+  if (typeof item === 'object') {
+    const id = normalizeNumber(item.id ?? item.areaId ?? item.value, void 0)
+    if (id === void 0) {
+      return null
+    }
+    return {
+      id,
+      sort: normalizeNumber(item.sort, index + 1),
+      name: normalizeText(item.name || item.areaName || item.label || ''),
+      code: normalizeText(item.code || item.areaCode || ''),
+      memo: normalizeText(item.memo || '')
+    }
+  }
+
+  const id = normalizeNumber(item, void 0)
+  if (id === void 0) {
+    return null
+  }
+  return {
+    id,
+    sort: index + 1,
+    name: '',
+    code: '',
+    memo: ''
+  }
+}
+
+function buildBasContainerAreasText(areas = [], fallbackAreas = [], resolveAreaLabel) {
+  const fallbackLabelMap = new Map(
+    (Array.isArray(fallbackAreas) ? fallbackAreas : [])
+      .map((item) => {
+        if (!item || typeof item !== 'object') {
+          return null
+        }
+        const id = normalizeNumber(item.id ?? item.areaId ?? item.value, void 0)
+        if (id === void 0) {
+          return null
+        }
+        return [id, normalizeText(item.name || item.areaName || item.code || item.areaCode || '')]
+      })
+      .filter(Boolean)
+  )
+
+  const orderedLabels = (Array.isArray(areas) ? areas : [])
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return ''
+      }
+      if (typeof resolveAreaLabel === 'function') {
+        const resolvedLabel = normalizeText(resolveAreaLabel(item.id))
+        if (resolvedLabel) {
+          return resolvedLabel
+        }
+      }
+      const fallbackLabel = fallbackLabelMap.get(item.id)
+      if (fallbackLabel) {
+        return fallbackLabel
+      }
+      return normalizeText(item.name || item.areaName || item.code || item.areaCode || item.id)
+    })
+    .filter(Boolean)
+
+  if (orderedLabels.length) {
+    return orderedLabels.join('銆�')
+  }
+
+  const fallbackLabels = (Array.isArray(fallbackAreas) ? fallbackAreas : [])
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return ''
+      }
+      return normalizeText(item.name || item.areaName || item.code || item.areaCode || item.id)
+    })
+    .filter(Boolean)
+
+  const labels = fallbackLabels
+  return labels.length ? labels.join('銆�') : ''
+}
diff --git a/rsf-design/src/views/basic-info/bas-container/basContainerTable.columns.js b/rsf-design/src/views/basic-info/bas-container/basContainerTable.columns.js
new file mode 100644
index 0000000..662276a
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/basContainerTable.columns.js
@@ -0,0 +1,136 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import { getBasContainerStatusMeta } from './basContainerPage.helpers'
+
+export function createBasContainerTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  resolveAreaLabel,
+  canEdit = true,
+  canDelete = true
+}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'containerTypeText',
+      label: '瀹瑰櫒绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.containerTypeText || row.containerType$ || '--'
+    },
+    {
+      prop: 'code',
+      label: '鍞竴缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'codeType',
+      label: '鏉$爜绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.codeType || '--'
+    },
+    {
+      prop: 'areasText',
+      label: '鍙叆搴撳尯',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => {
+        if (typeof resolveAreaLabel === 'function' && Array.isArray(row.areas) && row.areas.length) {
+          const labels = row.areas
+            .map((item) => resolveAreaLabel(item.id || item.areaId || item.value))
+            .filter(Boolean)
+          if (labels.length) {
+            return labels.join('銆�')
+          }
+        }
+        return row.areasText || '--'
+      }
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getBasContainerStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || row.createBy$ || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || row.createTime$ || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/bas-container/index.vue b/rsf-design/src/views/basic-info/bas-container/index.vue
new file mode 100644
index 0000000..16f81b6
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/index.vue
@@ -0,0 +1,400 @@
+<template>
+  <div class="bas-container-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板瀹瑰櫒瑙勫垯</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <BasContainerDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :container-data="currentContainerData"
+        :area-options="areaOptions"
+        :container-type-options="containerTypeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <BasContainerDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+        :resolve-area-label="resolveAreaLabel"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  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 {
+    fetchBasContainerPage,
+    fetchDeleteBasContainer,
+    fetchExportBasContainerReport,
+    fetchGetBasContainerDetail,
+    fetchGetBasContainerMany,
+    fetchSaveBasContainer,
+    fetchUpdateBasContainer,
+    fetchWarehouseAreasList
+  } from '@/api/bas-container'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import BasContainerDialog from './modules/bas-container-dialog.vue'
+  import BasContainerDetailDrawer from './modules/bas-container-detail-drawer.vue'
+  import { createBasContainerTableColumns } from './basContainerTable.columns'
+  import {
+    BAS_CONTAINER_REPORT_STYLE,
+    BAS_CONTAINER_REPORT_TITLE,
+    buildBasContainerDialogModel,
+    buildBasContainerPageQueryParams,
+    buildBasContainerPrintRows,
+    buildBasContainerReportMeta,
+    buildBasContainerSavePayload,
+    buildBasContainerSearchParams,
+    buildBasContainerContainerTypeOptions,
+    createBasContainerSearchState,
+    getBasContainerPaginationKey,
+    normalizeBasContainerDetailRecord,
+    normalizeBasContainerListRow,
+    resolveBasContainerAreaOptions
+  } from './basContainerPage.helpers'
+
+  defineOptions({ name: 'BasContainer' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createBasContainerSearchState())
+  const areaOptions = ref([])
+  const containerTypeOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = BAS_CONTAINER_REPORT_TITLE
+  const reportQueryParams = computed(() => buildBasContainerSearchParams(searchForm.value))
+  const areaLabelMap = computed(
+    () =>
+      new Map(
+        areaOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  function resolveAreaLabel(id) {
+    return areaLabelMap.value.get(String(id)) || ''
+  }
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ敮涓�缂栫爜/鏉$爜绫诲瀷/澶囨敞'
+      }
+    },
+    {
+      label: '鍞竴缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ敮涓�缂栫爜'
+      }
+    },
+    {
+      label: '瀹瑰櫒绫诲瀷',
+      key: 'containerType',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: containerTypeOptions.value
+      }
+    },
+    {
+      label: '鏉$爜绫诲瀷',
+      key: 'codeType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮佺被鍨�'
+      }
+    },
+    {
+      label: '鍙叆搴撳尯',
+      key: 'areas',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ彲鍏ュ簱鍖�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetBasContainerDetail(row.id), {}, {
+        timeoutMessage: '瀹瑰櫒瑙勫垯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeBasContainerDetailRecord(detail, resolveAreaLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇瀹瑰櫒瑙勫垯璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetBasContainerDetail(row.id), {}, {
+        timeoutMessage: '瀹瑰櫒瑙勫垯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇瀹瑰櫒瑙勫垯璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchBasContainerPage,
+        apiParams: buildBasContainerPageQueryParams(searchForm.value),
+        paginationKey: getBasContainerPaginationKey(),
+        columnsFactory: () =>
+          createBasContainerTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            resolveAreaLabel,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeBasContainerListRow(item, resolveAreaLabel))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentContainerData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildBasContainerDialogModel(),
+    buildEditModel: (record) => buildBasContainerDialogModel(record),
+    buildSavePayload: (formData) => buildBasContainerSavePayload(formData),
+    saveRequest: fetchSaveBasContainer,
+    updateRequest: fetchUpdateBasContainer,
+    deleteRequest: fetchDeleteBasContainer,
+    entityName: '瀹瑰櫒瑙勫垯',
+    resolveRecordLabel: (record) => record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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_CONTAINER_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetBasContainerMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchBasContainerPage({
+        ...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-container.xlsx',
+    requestExport: (payload) =>
+      fetchExportBasContainerReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildBasContainerPrintRows(records, resolveAreaLabel),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildBasContainerReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || BAS_CONTAINER_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildBasContainerSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createBasContainerSearchState())
+    resetSearchParams()
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '鍙叆搴撳尯鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveBasContainerAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadContainerTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_container_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '瀹瑰櫒绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    containerTypeOptions.value = buildBasContainerContainerTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadAreaOptions(), loadContainerTypeOptions()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue
new file mode 100644
index 0000000..2ce6b2e
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-areas-editor.vue
@@ -0,0 +1,177 @@
+<template>
+  <div class="space-y-3">
+    <div class="flex flex-wrap items-center gap-2">
+      <ElSelect
+        v-model="selectedAreaId"
+        class="min-w-0 flex-1"
+        clearable
+        filterable
+        placeholder="璇烽�夋嫨鍙叆搴撳尯"
+      >
+        <ElOption
+          v-for="option in availableAreaOptions"
+          :key="option.value"
+          :label="option.label"
+          :value="option.value"
+        />
+      </ElSelect>
+      <ElButton :disabled="selectedAreaId === '' || selectedAreaId === void 0" @click="handleAddArea">
+        娣诲姞
+      </ElButton>
+    </div>
+
+    <ElEmpty v-if="!selectedAreas.length" description="鏆傛棤鍙叆搴撳尯" />
+
+    <div v-else class="space-y-2">
+      <div
+        v-for="(item, index) in selectedAreas"
+        :key="item.id"
+        class="flex flex-wrap items-center gap-2 rounded-lg border border-[var(--art-border-color)] px-3 py-2"
+      >
+        <div class="flex w-10 shrink-0 items-center justify-center text-sm text-[var(--art-text-secondary)]">
+          {{ index + 1 }}
+        </div>
+        <div class="min-w-0 flex-1">
+          <div class="truncate font-medium text-[var(--art-text-primary)]">
+            {{ resolveAreaLabel(item) }}
+          </div>
+          <div class="text-xs text-[var(--art-text-secondary)]">
+            ID: {{ item.id }}
+          </div>
+        </div>
+        <div class="flex items-center gap-2">
+          <ElInputNumber
+            :model-value="item.sort"
+            :min="1"
+            :controls-position="'right'"
+            class="!w-24"
+            @update:model-value="handleSortChange(item.id, $event)"
+          />
+          <ElButton text :disabled="index === 0" @click="handleMoveUp(index)">涓婄Щ</ElButton>
+          <ElButton text :disabled="index === selectedAreas.length - 1" @click="handleMoveDown(index)">
+            涓嬬Щ
+          </ElButton>
+          <ElButton text type="danger" @click="handleRemove(item.id)">鍒犻櫎</ElButton>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  const props = defineProps({
+    areaOptions: {
+      type: Array,
+      default: () => []
+    }
+  })
+
+  const modelValue = defineModel({ type: Array, default: () => [] })
+  const selectedAreaId = ref('')
+
+  const selectedAreas = computed(() => normalizeSelectedAreas(modelValue.value))
+  const areaOptionMap = computed(() =>
+    new Map(
+      (Array.isArray(props.areaOptions) ? props.areaOptions : [])
+        .map((item) => normalizeAreaOption(item))
+        .filter(Boolean)
+        .map((item) => [String(item.value), item.label])
+    )
+  )
+  const availableAreaOptions = computed(() => {
+    const selectedIds = new Set(selectedAreas.value.map((item) => String(item.id)))
+    return (Array.isArray(props.areaOptions) ? props.areaOptions : [])
+      .map((item) => normalizeAreaOption(item))
+      .filter((item) => item && !selectedIds.has(String(item.value)))
+  })
+
+  function normalizeAreaOption(option) {
+    if (!option || typeof option !== 'object') {
+      return null
+    }
+    const value = option.value ?? option.id ?? option.areaId
+    if (value === void 0 || value === null || value === '') {
+      return null
+    }
+    return {
+      value: Number(value),
+      label: String(option.label || option.name || option.areaName || option.code || option.areaCode || value)
+    }
+  }
+
+  function normalizeSelectedAreas(source = []) {
+    if (!Array.isArray(source)) {
+      return []
+    }
+    return source
+      .map((item, index) => {
+        if (item === null || item === void 0) {
+          return null
+        }
+        if (typeof item === 'object') {
+          const id = Number(item.id ?? item.areaId ?? item.value)
+          if (Number.isNaN(id)) return null
+          return {
+            id,
+            sort: Number(item.sort ?? index + 1) || index + 1
+          }
+        }
+        const id = Number(item)
+        if (Number.isNaN(id)) return null
+        return {
+          id,
+          sort: index + 1
+        }
+      })
+      .filter(Boolean)
+      .sort((left, right) => Number(left.sort) - Number(right.sort))
+  }
+
+  function syncModel(rows) {
+    modelValue.value = rows.map((item, index) => ({
+      id: item.id,
+      sort: index + 1
+    }))
+  }
+
+  function resolveAreaLabel(item) {
+    return areaOptionMap.value.get(String(item.id)) || `搴撳尯 ${item.id}`
+  }
+
+  function handleAddArea() {
+    const nextId = Number(selectedAreaId.value)
+    if (!nextId || selectedAreas.value.some((item) => Number(item.id) === nextId)) {
+      return
+    }
+    const nextRows = [...selectedAreas.value, { id: nextId, sort: selectedAreas.value.length + 1 }]
+    syncModel(nextRows)
+    selectedAreaId.value = ''
+  }
+
+  function handleSortChange(id, value) {
+    const nextSort = Number(value) || 1
+    const nextRows = selectedAreas.value.map((item) =>
+      Number(item.id) === Number(id) ? { ...item, sort: nextSort } : { ...item }
+    )
+    nextRows.sort((left, right) => Number(left.sort) - Number(right.sort))
+    syncModel(nextRows)
+  }
+
+  function handleMoveUp(index) {
+    if (index <= 0) return
+    const nextRows = [...selectedAreas.value]
+    ;[nextRows[index - 1], nextRows[index]] = [nextRows[index], nextRows[index - 1]]
+    syncModel(nextRows)
+  }
+
+  function handleMoveDown(index) {
+    if (index >= selectedAreas.value.length - 1) return
+    const nextRows = [...selectedAreas.value]
+    ;[nextRows[index + 1], nextRows[index]] = [nextRows[index], nextRows[index + 1]]
+    syncModel(nextRows)
+  }
+
+  function handleRemove(id) {
+    syncModel(selectedAreas.value.filter((item) => Number(item.id) !== Number(id)))
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-container/modules/bas-container-detail-drawer.vue b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-detail-drawer.vue
new file mode 100644
index 0000000..1ad9d29
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-detail-drawer.vue
@@ -0,0 +1,83 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="瀹瑰櫒瑙勫垯璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="瀹瑰櫒绫诲瀷">{{ detail.containerTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍞竴缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉$爜绫诲瀷">{{ detail.codeType || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="鍙叆搴撳尯" :column="1" border>
+          <ElDescriptionsItem v-if="!areas.length" label="鍒楄〃">鏆傛棤鍙叆搴撳尯</ElDescriptionsItem>
+          <ElDescriptionsItem v-for="item in areas" v-else :key="item.id" :label="`#${item.sort}`">
+            <div class="flex flex-col gap-1">
+              <div class="font-medium text-[var(--art-text-primary)]">
+                {{ resolveAreaLabel(item.id) || item.name || item.code || `搴撳尯 ${item.id}` }}
+              </div>
+              <div class="text-xs text-[var(--art-text-secondary)]">ID: {{ item.id }}</div>
+            </div>
+          </ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    resolveAreaLabel: { type: Function, default: null }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  const areas = computed(() => {
+    if (!Array.isArray(props.detail?.areas)) {
+      return []
+    }
+    return props.detail.areas
+  })
+
+  function resolveAreaLabel(id) {
+    if (typeof props.resolveAreaLabel === 'function') {
+      return String(props.resolveAreaLabel(id) || '').trim()
+    }
+    return ''
+  }
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-container/modules/bas-container-dialog.vue b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-dialog.vue
new file mode 100644
index 0000000..9537098
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-container/modules/bas-container-dialog.vue
@@ -0,0 +1,171 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    >
+      <template #areas>
+        <BasContainerAreasEditor v-model="form.areas" :area-options="areaOptions" />
+      </template>
+    </ArtForm>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildBasContainerDialogModel, createBasContainerFormState, getBasContainerStatusOptions } from '../basContainerPage.helpers'
+  import BasContainerAreasEditor from './bas-container-areas-editor.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    containerData: { type: Object, default: () => ({}) },
+    areaOptions: { type: Array, default: () => [] },
+    containerTypeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createBasContainerFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫瀹瑰櫒瑙勫垯' : '鏂板瀹瑰櫒瑙勫垯'))
+
+  const rules = computed(() => ({
+    containerType: [{ required: true, message: '璇烽�夋嫨瀹瑰櫒绫诲瀷', trigger: 'change' }],
+    code: [{ required: true, message: '璇疯緭鍏ュ敮涓�缂栫爜', trigger: 'blur' }],
+    codeType: [{ required: true, message: '璇疯緭鍏ユ潯鐮佺被鍨�', trigger: 'blur' }],
+    areas: [{ type: 'array', required: true, message: '璇疯嚦灏戦�夋嫨涓�涓彲鍏ュ簱鍖�', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '瀹瑰櫒绫诲瀷',
+      key: 'containerType',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨瀹瑰櫒绫诲瀷',
+        clearable: true,
+        options: props.containerTypeOptions || []
+      }
+    },
+    {
+      label: '鍞竴缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ敮涓�缂栫爜',
+        clearable: true
+      }
+    },
+    {
+      label: '鏉$爜绫诲瀷',
+      key: 'codeType',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ潯鐮佺被鍨�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍙叆搴撳尯',
+      key: 'areas',
+      type: 'input',
+      span: 24
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getBasContainerStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildBasContainerDialogModel(props.containerData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createBasContainerFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.containerData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
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
new file mode 100644
index 0000000..658dddb
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaPage.helpers.js
@@ -0,0 +1,474 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+const TYPE_OPTIONS = [
+  { label: '鏅鸿兘绔欑偣', value: 0 },
+  { label: '鏅�氱珯鐐�', value: 1 }
+]
+
+const BINARY_OPTIONS = [
+  { label: '鍚�', value: 0 },
+  { label: '鏄�', value: 1 }
+]
+
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1 },
+  { label: '鍐荤粨', value: 0 }
+]
+
+export const BAS_STATION_AREA_REPORT_TITLE = '绔欑偣鍖哄煙鎶ヨ〃'
+export const BAS_STATION_AREA_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeBooleanText(value) {
+  if (value === 1 || value === '1' || value === true || value === '鏄�') {
+    return '鏄�'
+  }
+  if (value === 0 || value === '0' || value === false || value === '鍚�') {
+    return '鍚�'
+  }
+  return normalizeText(value) || '--'
+}
+
+function normalizeIdArray(values = []) {
+  if (Array.isArray(values)) {
+    return values
+      .map((item) => normalizeNumber(item, void 0))
+      .filter((item) => item !== void 0 && item !== null)
+  }
+
+  if (typeof values === 'string' && values.trim()) {
+    return values
+      .split(',')
+      .map((item) => normalizeNumber(item, void 0))
+      .filter((item) => item !== void 0 && item !== null)
+  }
+
+  return []
+}
+
+function normalizeStringArray(values = []) {
+  if (Array.isArray(values)) {
+    return values.map((item) => normalizeText(item)).filter(Boolean)
+  }
+
+  if (typeof values === 'string' && values.trim()) {
+    return values
+      .split(',')
+      .map((item) => normalizeText(item))
+      .filter(Boolean)
+  }
+
+  return []
+}
+
+function buildJoinedText(values = [], resolver) {
+  const labels = values
+    .map((value) => {
+      if (typeof resolver === 'function') {
+        return normalizeText(resolver(value))
+      }
+      return normalizeText(value)
+    })
+    .filter(Boolean)
+
+  return labels.length ? labels.join('銆�') : '--'
+}
+
+function getStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function createBasStationAreaSearchState() {
+  return {
+    condition: '',
+    stationAreaName: '',
+    stationAreaId: '',
+    type: '',
+    inAble: '',
+    outAble: '',
+    useStatus: '',
+    area: '',
+    isCrossZone: '',
+    crossZoneArea: '',
+    isWcs: '',
+    wcsData: '',
+    containerType: '',
+    barcode: '',
+    autoTransfer: '',
+    stationAlias: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function createBasStationAreaFormState() {
+  return {
+    id: void 0,
+    stationAreaName: '',
+    stationAreaId: '',
+    type: void 0,
+    inAble: 0,
+    outAble: 0,
+    useStatus: '',
+    area: void 0,
+    isCrossZone: 0,
+    crossZoneArea: [],
+    isWcs: 0,
+    wcsData: '',
+    containerType: [],
+    barcode: '',
+    autoTransfer: 0,
+    stationAlias: [],
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getBasStationAreaPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getBasStationAreaTypeOptions() {
+  return TYPE_OPTIONS
+}
+
+export function getBasStationAreaBinaryOptions() {
+  return BINARY_OPTIONS
+}
+
+export function getBasStationAreaStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function resolveBasStationAreaWarehouseAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.areaId ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveBasStationAreaStationOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.stationId ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.stationName || item.stationId || item.name || `绔欑偣 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveBasStationAreaContainerTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveBasStationAreaUseStatusOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: normalizeText(item.value ?? value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildBasStationAreaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    stationAreaName: normalizeText(params.stationAreaName),
+    stationAreaId: normalizeText(params.stationAreaId),
+    type:
+      params.type !== undefined && params.type !== null && params.type !== ''
+        ? Number(params.type)
+        : void 0,
+    inAble:
+      params.inAble !== undefined && params.inAble !== null && params.inAble !== ''
+        ? Number(params.inAble)
+        : void 0,
+    outAble:
+      params.outAble !== undefined && params.outAble !== null && params.outAble !== ''
+        ? Number(params.outAble)
+        : void 0,
+    useStatus: normalizeText(params.useStatus),
+    area:
+      params.area !== undefined && params.area !== null && params.area !== ''
+        ? Number(params.area)
+        : void 0,
+    isCrossZone:
+      params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
+        ? Number(params.isCrossZone)
+        : void 0,
+    crossZoneArea: normalizeText(params.crossZoneArea),
+    isWcs:
+      params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
+        ? Number(params.isWcs)
+        : void 0,
+    wcsData: normalizeText(params.wcsData),
+    containerType: normalizeText(params.containerType),
+    barcode: normalizeText(params.barcode),
+    autoTransfer:
+      params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+        ? Number(params.autoTransfer)
+        : void 0,
+    stationAlias: normalizeText(params.stationAlias),
+    memo: normalizeText(params.memo),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildBasStationAreaPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildBasStationAreaSearchParams(params)
+  }
+}
+
+function normalizeIdValue(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? void 0 : numberValue
+}
+
+function resolveOptionText(ids = [], resolver, fallback = []) {
+  const fallbackList = normalizeStringArray(fallback)
+  const resolvedList = normalizeIdArray(ids).map((id, index) => {
+    if (typeof resolver === 'function') {
+      const text = normalizeText(resolver(id))
+      if (text) {
+        return text
+      }
+    }
+    return fallbackList[index] || normalizeText(id)
+  })
+  return buildJoinedText(resolvedList)
+}
+
+export function buildBasStationAreaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    stationAreaName: normalizeText(formData.stationAreaName) || '',
+    stationAreaId: normalizeText(formData.stationAreaId) || '',
+    ...(formData.type !== void 0 && formData.type !== null && formData.type !== ''
+      ? { type: Number(formData.type) }
+      : {}),
+    ...(formData.inAble !== void 0 && formData.inAble !== null && formData.inAble !== ''
+      ? { inAble: Number(formData.inAble) }
+      : {}),
+    ...(formData.outAble !== void 0 && formData.outAble !== null && formData.outAble !== ''
+      ? { outAble: Number(formData.outAble) }
+      : {}),
+    useStatus: normalizeText(formData.useStatus) || '',
+    ...(formData.area !== void 0 && formData.area !== null && formData.area !== ''
+      ? { area: Number(formData.area) }
+      : {}),
+    ...(formData.isCrossZone !== void 0 && formData.isCrossZone !== null && formData.isCrossZone !== ''
+      ? { isCrossZone: Number(formData.isCrossZone) }
+      : {}),
+    ...(Array.isArray(formData.crossZoneArea) && formData.crossZoneArea.length
+      ? { crossZoneArea: normalizeIdArray(formData.crossZoneArea) }
+      : {}),
+    ...(formData.isWcs !== void 0 && formData.isWcs !== null && formData.isWcs !== ''
+      ? { isWcs: Number(formData.isWcs) }
+      : {}),
+    wcsData: normalizeText(formData.wcsData) || '',
+    ...(Array.isArray(formData.containerType) && formData.containerType.length
+      ? { containerType: normalizeIdArray(formData.containerType) }
+      : {}),
+    barcode: normalizeText(formData.barcode) || '',
+    ...(formData.autoTransfer !== void 0 && formData.autoTransfer !== null && formData.autoTransfer !== ''
+      ? { autoTransfer: Number(formData.autoTransfer) }
+      : {}),
+    ...(Array.isArray(formData.stationAlias) && formData.stationAlias.length
+      ? { stationAlias: normalizeIdArray(formData.stationAlias).map((item) => String(item)) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildBasStationAreaDialogModel(record = {}, resolvers = {}) {
+  return {
+    ...createBasStationAreaFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    stationAreaName: normalizeText(record.stationAreaName || ''),
+    stationAreaId: normalizeText(record.stationAreaId || ''),
+    type: normalizeIdValue(record.type),
+    inAble: record.inAble !== void 0 && record.inAble !== null ? Number(record.inAble) : 0,
+    outAble: record.outAble !== void 0 && record.outAble !== null ? Number(record.outAble) : 0,
+    useStatus: normalizeText(record.useStatus || ''),
+    area: normalizeIdValue(record.area),
+    isCrossZone: record.isCrossZone !== void 0 && record.isCrossZone !== null ? Number(record.isCrossZone) : 0,
+    crossZoneArea: normalizeIdArray(record.crossZoneArea),
+    isWcs: record.isWcs !== void 0 && record.isWcs !== null ? Number(record.isWcs) : 0,
+    wcsData: normalizeText(record.wcsData || ''),
+    containerType: normalizeIdArray(record.containerType ?? record.containerTypes),
+    barcode: normalizeText(record.barcode || ''),
+    autoTransfer: record.autoTransfer !== void 0 && record.autoTransfer !== null ? Number(record.autoTransfer) : 0,
+    stationAlias: normalizeIdArray(record.stationAlias),
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeBasStationAreaDetailRecord(record = {}, resolvers = {}) {
+  const statusMeta = getStatusMeta(record.statusBool ?? record.status)
+  const typeValue = record.type$ ?? record.type
+  const areaId = record.area ?? record.areaId ?? record.area$
+  const crossZoneAreaIds = normalizeIdArray(record.crossZoneArea)
+  const containerTypeIds = normalizeIdArray(record.containerType ?? record.containerTypes)
+  const stationAliasIds = normalizeStringArray(record.stationAlias)
+  const stationAliasNames = normalizeStringArray(record.stationAliasStaNo)
+
+  return {
+    ...record,
+    id: normalizeIdValue(record.id),
+    stationAreaName: normalizeText(record.stationAreaName) || '--',
+    stationAreaId: normalizeText(record.stationAreaId) || '--',
+    type: normalizeIdValue(record.type),
+    typeText: normalizeText(
+      record.type$ || record.typeText || resolvers.resolveTypeLabel?.(typeValue) || typeValue
+    ) || '--',
+    inAble: normalizeIdValue(record.inAble),
+    inAbleText: normalizeBooleanText(record.inAble),
+    outAble: normalizeIdValue(record.outAble),
+    outAbleText: normalizeBooleanText(record.outAble),
+    useStatus: normalizeText(record.useStatus),
+    useStatusText: normalizeText(record.useStatus$ || record.useStatusText || resolvers.resolveUseStatusLabel?.(record.useStatus) || record.useStatus) || '--',
+    area: normalizeIdValue(areaId),
+    areaText: normalizeText(record.area$ || record.areaText || resolvers.resolveAreaLabel?.(areaId) || '') || '--',
+    isCrossZone: normalizeIdValue(record.isCrossZone),
+    isCrossZoneText: normalizeBooleanText(record.isCrossZone),
+    crossZoneArea: crossZoneAreaIds,
+    crossZoneAreaText:
+      resolveOptionText(crossZoneAreaIds, resolvers.resolveCrossZoneAreaLabel, record.crossZoneAreaText || []) || '--',
+    isWcs: normalizeIdValue(record.isWcs),
+    isWcsText: normalizeBooleanText(record.isWcs),
+    wcsData: normalizeText(record.wcsData) || '--',
+    containerType: containerTypeIds,
+    containerTypeText:
+      resolveOptionText(containerTypeIds, resolvers.resolveContainerTypeLabel, record.containerTypesText || []) || '--',
+    barcode: normalizeText(record.barcode) || '--',
+    autoTransfer: normalizeIdValue(record.autoTransfer),
+    autoTransferText: normalizeBooleanText(record.autoTransfer),
+    stationAlias: stationAliasIds,
+    stationAliasText:
+      resolveOptionText(
+        stationAliasIds,
+        resolvers.resolveStationAliasLabel,
+        stationAliasNames.length ? stationAliasNames : record.stationAliasText || []
+      ) || '--',
+    status: normalizeIdValue(record.status),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo) || '--',
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeBasStationAreaListRow(record = {}, resolvers = {}) {
+  return normalizeBasStationAreaDetailRecord(record, resolvers)
+}
diff --git a/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
new file mode 100644
index 0000000..e5284e9
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station-area/basStationAreaTable.columns.js
@@ -0,0 +1,176 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getBasStationAreaStatusOptions } from './basStationAreaPage.helpers'
+
+export function createBasStationAreaTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'stationAreaId',
+      label: '绔欑偣鍖哄煙缂栧彿',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationAreaId || '--'
+    },
+    {
+      prop: 'stationAreaName',
+      label: '绔欑偣鍖哄煙鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationAreaName || '--'
+    },
+    {
+      prop: 'typeText',
+      label: '绔欑偣绫诲瀷',
+      width: 120,
+      align: 'center',
+      formatter: (row) => row.typeText || '--'
+    },
+    {
+      prop: 'areaText',
+      label: '鎵�灞炲簱鍖�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaText || '--'
+    },
+    {
+      prop: 'crossZoneAreaText',
+      label: '鍙法鍖哄尯鍩�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.crossZoneAreaText || '--'
+    },
+    {
+      prop: 'containerTypeText',
+      label: '瀹瑰櫒绫诲瀷',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.containerTypeText || '--'
+    },
+    {
+      prop: 'stationAliasText',
+      label: '绔欑偣鍒悕',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationAliasText || '--'
+    },
+    {
+      prop: 'inAbleText',
+      label: '鍙叆',
+      width: 84,
+      align: 'center',
+      formatter: (row) => row.inAbleText || '--'
+    },
+    {
+      prop: 'outAbleText',
+      label: '鍙嚭',
+      width: 84,
+      align: 'center',
+      formatter: (row) => row.outAbleText || '--'
+    },
+    {
+      prop: 'isCrossZoneText',
+      label: '璺ㄥ尯',
+      width: 84,
+      align: 'center',
+      formatter: (row) => row.isCrossZoneText || '--'
+    },
+    {
+      prop: 'isWcsText',
+      label: 'WCS',
+      width: 84,
+      align: 'center',
+      formatter: (row) => row.isWcsText || '--'
+    },
+    {
+      prop: 'autoTransferText',
+      label: '鑷姩璋冩嫧',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.autoTransferText || '--'
+    },
+    {
+      prop: 'useStatusText',
+      label: '浣跨敤鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.useStatusText || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鏉$爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const status = getBasStationAreaStatusOptions().find((item) => Number(item.value) === Number(row.status))
+        const text = status?.label || row.statusText || '--'
+        const type = Number(row.status) === 1 ? 'success' : Number(row.status) === 0 ? 'danger' : 'info'
+        return h(ElTag, { type, effect: 'light' }, () => text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
+
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
new file mode 100644
index 0000000..7d9c219
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station-area/index.vue
@@ -0,0 +1,459 @@
+<template>
+  <div class="bas-station-area-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板绔欑偣鍖哄煙</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <BasStationAreaDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :bas-station-area-data="currentBasStationAreaData"
+        :area-options="areaOptions"
+        :cross-zone-area-options="crossZoneAreaOptions"
+        :container-type-options="containerTypeOptions"
+        :station-options="stationOptions"
+        :use-status-options="useStatusOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <BasStationAreaDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import { fetchBasStationPage } from '@/api/bas-station'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import {
+    fetchBasStationAreaDetail,
+    fetchBasStationAreaPage,
+    fetchDeleteBasStationArea,
+    fetchSaveBasStationArea,
+    fetchUpdateBasStationArea
+  } from '@/api/bas-station-area'
+  import BasStationAreaDialog from './modules/bas-station-area-dialog.vue'
+  import BasStationAreaDetailDrawer from './modules/bas-station-area-detail-drawer.vue'
+  import { createBasStationAreaTableColumns } from './basStationAreaTable.columns'
+  import {
+    buildBasStationAreaDialogModel,
+    buildBasStationAreaPageQueryParams,
+    buildBasStationAreaSavePayload,
+    buildBasStationAreaSearchParams,
+    createBasStationAreaSearchState,
+    getBasStationAreaBinaryOptions,
+    getBasStationAreaPaginationKey,
+    getBasStationAreaStatusOptions,
+    getBasStationAreaTypeOptions,
+    normalizeBasStationAreaDetailRecord,
+    normalizeBasStationAreaListRow,
+    resolveBasStationAreaContainerTypeOptions,
+    resolveBasStationAreaStationOptions,
+    resolveBasStationAreaUseStatusOptions,
+    resolveBasStationAreaWarehouseAreaOptions
+  } from './basStationAreaPage.helpers'
+
+  defineOptions({ name: 'BasStationArea' })
+
+  const { hasAuth } = useAuth()
+
+  const searchForm = ref(createBasStationAreaSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const areaOptions = ref([])
+  const crossZoneAreaOptions = ref([])
+  const containerTypeOptions = ref([])
+  const stationOptions = ref([])
+  const useStatusOptions = ref([])
+  let handleDeleteAction = null
+
+  const areaLabelMap = computed(
+    () =>
+      new Map(
+        areaOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+  const containerTypeLabelMap = computed(
+    () =>
+      new Map(
+        containerTypeOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+  const typeLabelMap = computed(
+    () =>
+      new Map(
+        getBasStationAreaTypeOptions()
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+  const stationLabelMap = computed(
+    () =>
+      new Map(
+        stationOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+  const useStatusLabelMap = computed(
+    () =>
+      new Map(
+        useStatusOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  const resolveAreaLabel = (id) => areaLabelMap.value.get(String(id)) || ''
+  const resolveCrossZoneAreaLabel = (id) => areaLabelMap.value.get(String(id)) || ''
+  const resolveContainerTypeLabel = (id) => containerTypeLabelMap.value.get(String(id)) || ''
+  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 searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熷悕绉�/缂栧彿/澶囨敞'
+      }
+    },
+    {
+      label: '绔欑偣鍖哄煙鍚嶇О',
+      key: 'stationAreaName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熷悕绉�'
+      }
+    },
+    {
+      label: '绔欑偣鍖哄煙缂栧彿',
+      key: 'stationAreaId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熺紪鍙�'
+      }
+    },
+    {
+      label: '绔欑偣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaTypeOptions()
+      }
+    },
+    {
+      label: '鎵�灞炲簱鍖�',
+      key: 'area',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaOptions.value
+      }
+    },
+    {
+      label: '浣跨敤鐘舵��',
+      key: 'useStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: useStatusOptions.value
+      }
+    },
+    {
+      label: '鍙叆',
+      key: 'inAble',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鍙嚭',
+      key: 'outAble',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鏄惁璺ㄥ尯',
+      key: 'isCrossZone',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鏄惁WCS',
+      key: 'isWcs',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鑷姩璋冩嫧',
+      key: 'autoTransfer',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationAreaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchBasStationAreaDetail(row.id), {}, {
+        timeoutMessage: '绔欑偣鍖哄煙璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeBasStationAreaDetailRecord(detail, {
+        resolveAreaLabel,
+        resolveCrossZoneAreaLabel,
+        resolveContainerTypeLabel,
+        resolveTypeLabel,
+        resolveStationAliasLabel,
+        resolveUseStatusLabel
+      })
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇绔欑偣鍖哄煙璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchBasStationAreaDetail(row.id), {}, {
+        timeoutMessage: '绔欑偣鍖哄煙璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇绔欑偣鍖哄煙璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchBasStationAreaPage,
+        apiParams: buildBasStationAreaPageQueryParams(searchForm.value),
+        paginationKey: getBasStationAreaPaginationKey(),
+        columnsFactory: () =>
+          createBasStationAreaTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) =>
+            normalizeBasStationAreaListRow(item, {
+              resolveAreaLabel,
+              resolveCrossZoneAreaLabel,
+              resolveContainerTypeLabel,
+              resolveTypeLabel,
+              resolveStationAliasLabel,
+              resolveUseStatusLabel
+            })
+          )
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentBasStationAreaData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildBasStationAreaDialogModel(),
+    buildEditModel: (record) => buildBasStationAreaDialogModel(record),
+    buildSavePayload: (formData) => buildBasStationAreaSavePayload(formData),
+    saveRequest: fetchSaveBasStationArea,
+    updateRequest: fetchUpdateBasStationArea,
+    deleteRequest: fetchDeleteBasStationArea,
+    entityName: '绔欑偣鍖哄煙',
+    resolveRecordLabel: (record) => record?.stationAreaName || record?.stationAreaId || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildBasStationAreaSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createBasStationAreaSearchState())
+    resetSearchParams()
+  }
+
+  async function loadAreaOptions() {
+    const response = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    const options = resolveBasStationAreaWarehouseAreaOptions(defaultResponseAdapter(response).records)
+    areaOptions.value = options
+    crossZoneAreaOptions.value = options
+  }
+
+  async function loadStationOptions() {
+    const response = await guardRequestWithMessage(
+      fetchBasStationPage({
+        current: 1,
+        pageSize: 500
+      }, {
+        showErrorMessage: false
+      }),
+      { records: [] },
+      {
+        timeoutMessage: '绔欑偣鍒悕閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    stationOptions.value = resolveBasStationAreaStationOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadContainerTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_container_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '瀹瑰櫒绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    containerTypeOptions.value = resolveBasStationAreaContainerTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadUseStatusOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_sta_use_stas',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '浣跨敤鐘舵�侀�夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    useStatusOptions.value = resolveBasStationAreaUseStatusOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.allSettled([
+      loadAreaOptions(),
+      loadStationOptions(),
+      loadContainerTypeOptions(),
+      loadUseStatusOptions()
+    ])
+    await getData()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue
new file mode 100644
index 0000000..ebd2c7c
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-detail-drawer.vue
@@ -0,0 +1,69 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="绔欑偣鍖哄煙璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="绔欑偣鍖哄煙鍚嶇О">{{ detail.stationAreaName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鍖哄煙缂栧彿">{{ detail.stationAreaId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵�灞炲簱鍖�">{{ detail.areaText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙法鍖哄簱鍖�" :span="2">{{ detail.crossZoneAreaText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹瑰櫒绫诲瀷" :span="2">{{ detail.containerTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鍒悕" :span="2">{{ detail.stationAliasText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙叆">{{ detail.inAbleText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙嚭">{{ detail.outAbleText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏄惁璺ㄥ尯">{{ detail.isCrossZoneText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏄惁WCS">{{ detail.isWcsText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑷姩璋冩嫧">{{ detail.autoTransferText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浣跨敤鐘舵��">{{ detail.useStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉$爜">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="WCS鏁版嵁" :span="2">{{ detail.wcsData || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
+
diff --git a/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
new file mode 100644
index 0000000..698219f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station-area/modules/bas-station-area-dialog.vue
@@ -0,0 +1,307 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="1080px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildBasStationAreaDialogModel,
+    createBasStationAreaFormState,
+    getBasStationAreaBinaryOptions,
+    getBasStationAreaStatusOptions,
+    getBasStationAreaTypeOptions
+  } from '../basStationAreaPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    basStationAreaData: { type: Object, default: () => ({}) },
+    areaOptions: { type: Array, default: () => [] },
+    crossZoneAreaOptions: { type: Array, default: () => [] },
+    containerTypeOptions: { type: Array, default: () => [] },
+    stationOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createBasStationAreaFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫绔欑偣鍖哄煙' : '鏂板绔欑偣鍖哄煙'))
+
+  const rules = computed(() => ({
+    stationAreaName: [{ required: true, message: '璇疯緭鍏ョ珯鐐瑰尯鍩熷悕绉�', trigger: 'blur' }],
+    stationAreaId: [{ required: true, message: '璇疯緭鍏ョ珯鐐瑰尯鍩熺紪鍙�', trigger: 'blur' }],
+    type: [{ required: true, message: '璇烽�夋嫨绔欑偣绫诲瀷', trigger: 'change' }],
+    area: [{ required: true, message: '璇烽�夋嫨鎵�灞炲簱鍖�', trigger: 'change' }],
+    containerType: [{ type: 'array', required: true, message: '璇烽�夋嫨瀹瑰櫒绫诲瀷', trigger: 'change' }],
+    stationAlias: [{ type: 'array', required: true, message: '璇烽�夋嫨绔欑偣鍒悕', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '绔欑偣鍖哄煙鍚嶇О',
+      key: 'stationAreaName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熷悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '绔欑偣鍖哄煙缂栧彿',
+      key: 'stationAreaId',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ珯鐐瑰尯鍩熺紪鍙�',
+        clearable: true
+      }
+    },
+    {
+      label: '绔欑偣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨绔欑偣绫诲瀷',
+        clearable: true,
+        options: getBasStationAreaTypeOptions()
+      }
+    },
+    {
+      label: '鎵�灞炲簱鍖�',
+      key: 'area',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鎵�灞炲簱鍖�',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions || []
+      }
+    },
+    {
+      label: '鍙法鍖哄簱鍖�',
+      key: 'crossZoneArea',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨鍙法鍖哄簱鍖�',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.crossZoneAreaOptions || []
+      }
+    },
+    {
+      label: '瀹瑰櫒绫诲瀷',
+      key: 'containerType',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨瀹瑰櫒绫诲瀷',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.containerTypeOptions || []
+      }
+    },
+    {
+      label: '绔欑偣鍒悕',
+      key: 'stationAlias',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨绔欑偣鍒悕',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.stationOptions || []
+      }
+    },
+    {
+      label: '鍙叆',
+      key: 'inAble',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鍙叆',
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鍙嚭',
+      key: 'outAble',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鍙嚭',
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鏄惁璺ㄥ尯',
+      key: 'isCrossZone',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁璺ㄥ尯',
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鏄惁WCS',
+      key: 'isWcs',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁WCS',
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '鑷姩璋冩嫧',
+      key: 'autoTransfer',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鑷姩璋冩嫧',
+        clearable: true,
+        options: getBasStationAreaBinaryOptions()
+      }
+    },
+    {
+      label: '浣跨敤鐘舵��',
+      key: 'useStatus',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浣跨敤鐘舵��',
+        clearable: true,
+        filterable: true,
+        options: props.useStatusOptions || []
+      }
+    },
+    {
+      label: 'WCS鏁版嵁',
+      key: 'wcsData',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏CS鏁版嵁',
+        clearable: true
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ潯鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getBasStationAreaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createBasStationAreaFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(form, buildBasStationAreaDialogModel(props.basStationAreaData))
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.basStationAreaData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
+
diff --git a/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js b/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js
new file mode 100644
index 0000000..6750a27
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/basStationPage.helpers.js
@@ -0,0 +1,531 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+const TYPE_META = {
+  0: { text: '鏅鸿兘绔欑偣', type: 'primary' },
+  1: { text: '鏅�氱珯鐐�', type: 'warning' }
+}
+
+const BOOLEAN_META = {
+  1: { text: '鏄�', type: 'success' },
+  0: { text: '鍚�', type: 'info' }
+}
+
+export const BAS_STATION_REPORT_TITLE = '绔欑偣绠$悊鎶ヨ〃'
+export const BAS_STATION_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeIdArray(values = []) {
+  if (!Array.isArray(values)) {
+    return []
+  }
+
+  return values
+    .map((item, index) => {
+      if (item === null || item === undefined || item === '') {
+        return null
+      }
+      if (typeof item === 'object') {
+        const id = normalizeNumber(item.id ?? item.areaId ?? item.value, void 0)
+        if (id === void 0) {
+          return null
+        }
+        return {
+          id,
+          sort: normalizeNumber(item.sort, index + 1)
+        }
+      }
+      const id = normalizeNumber(item, void 0)
+      if (id === void 0) {
+        return null
+      }
+      return {
+        id,
+        sort: index + 1
+      }
+    })
+    .filter(Boolean)
+    .sort((left, right) => {
+      const leftSort = normalizeNumber(left.sort, Number.MAX_SAFE_INTEGER)
+      const rightSort = normalizeNumber(right.sort, Number.MAX_SAFE_INTEGER)
+      return leftSort - rightSort
+    })
+    .map((item, index) => ({
+      id: item.id,
+      sort: index + 1
+    }))
+}
+
+function normalizePlainIdArray(values = []) {
+  if (!Array.isArray(values)) {
+    return []
+  }
+  return values
+    .map((item) => normalizeNumber(item, void 0))
+    .filter((item) => item !== void 0 && item !== null)
+}
+
+export function createBasStationSearchState() {
+  return {
+    condition: '',
+    stationName: '',
+    stationId: '',
+    type: '',
+    useStatus: '',
+    area: '',
+    isCrossZone: '',
+    isWcs: '',
+    barcode: '',
+    autoTransfer: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createBasStationFormState() {
+  return {
+    id: void 0,
+    stationName: '',
+    stationId: '',
+    type: 0,
+    area: void 0,
+    useStatus: '',
+    isCrossZone: 0,
+    areaIds: [],
+    isWcs: 0,
+    wcsData: '',
+    containerTypes: [],
+    barcode: '',
+    autoTransfer: 0,
+    inAble: 0,
+    outAble: 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getBasStationPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getBasStationStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getBasStationTypeOptions() {
+  return [
+    { label: '鏅鸿兘绔欑偣', value: 0 },
+    { label: '鏅�氱珯鐐�', value: 1 }
+  ]
+}
+
+export function getBasStationBooleanOptions() {
+  return [
+    { label: '鍚�', value: 0 },
+    { label: '鏄�', value: 1 }
+  ]
+}
+
+export function getBasStationUseStatusMeta(value) {
+  if (!value) {
+    return { text: '鏈煡', type: 'info' }
+  }
+  return {
+    text: normalizeText(value),
+    type: 'primary'
+  }
+}
+
+export function getBasStationStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function getBasStationTypeMeta(type) {
+  if (type === true || Number(type) === 0) {
+    return TYPE_META[0]
+  }
+  if (Number(type) === 1) {
+    return TYPE_META[1]
+  }
+  return { text: '鏈煡', type: 'info' }
+}
+
+export function getBasStationBooleanMeta(value) {
+  if (value === true || Number(value) === 1) {
+    return BOOLEAN_META[1]
+  }
+  if (value === false || Number(value) === 0) {
+    return BOOLEAN_META[0]
+  }
+  return { text: '鏈煡', type: 'info' }
+}
+
+export function buildBasStationSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    stationName: normalizeText(params.stationName),
+    stationId: normalizeText(params.stationId),
+    type:
+      params.type !== undefined && params.type !== null && params.type !== ''
+        ? Number(params.type)
+        : void 0,
+    useStatus: normalizeText(params.useStatus),
+    area:
+      params.area !== undefined && params.area !== null && params.area !== ''
+        ? Number(params.area)
+        : void 0,
+    isCrossZone:
+      params.isCrossZone !== undefined && params.isCrossZone !== null && params.isCrossZone !== ''
+        ? Number(params.isCrossZone)
+        : void 0,
+    isWcs:
+      params.isWcs !== undefined && params.isWcs !== null && params.isWcs !== ''
+        ? Number(params.isWcs)
+        : void 0,
+    barcode: normalizeText(params.barcode),
+    autoTransfer:
+      params.autoTransfer !== undefined && params.autoTransfer !== null && params.autoTransfer !== ''
+        ? Number(params.autoTransfer)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildBasStationPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildBasStationSearchParams(params)
+  }
+}
+
+export function buildBasStationSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    stationName: normalizeText(formData.stationName) || '',
+    stationId: normalizeText(formData.stationId) || '',
+    ...(formData.type !== void 0 && formData.type !== null && formData.type !== ''
+      ? { type: Number(formData.type) }
+      : {}),
+    ...(formData.area !== void 0 && formData.area !== null && formData.area !== ''
+      ? { area: Number(formData.area) }
+      : {}),
+    useStatus: normalizeText(formData.useStatus) || '',
+    ...(formData.isCrossZone !== void 0 && formData.isCrossZone !== null && formData.isCrossZone !== ''
+      ? { isCrossZone: Number(formData.isCrossZone) }
+      : {}),
+    ...(normalizeIdArray(formData.areaIds).length ? { areaIds: normalizeIdArray(formData.areaIds).map((item) => item.id) } : {}),
+    ...(formData.isWcs !== void 0 && formData.isWcs !== null && formData.isWcs !== ''
+      ? { isWcs: Number(formData.isWcs) }
+      : {}),
+    wcsData: normalizeText(formData.wcsData) || '',
+    ...(normalizePlainIdArray(formData.containerTypes).length
+      ? { containerTypes: normalizePlainIdArray(formData.containerTypes) }
+      : {}),
+    barcode: normalizeText(formData.barcode) || '',
+    ...(formData.autoTransfer !== void 0 && formData.autoTransfer !== null && formData.autoTransfer !== ''
+      ? { autoTransfer: Number(formData.autoTransfer) }
+      : {}),
+    ...(formData.inAble !== void 0 && formData.inAble !== null && formData.inAble !== ''
+      ? { inAble: Number(formData.inAble) }
+      : {}),
+    ...(formData.outAble !== void 0 && formData.outAble !== null && formData.outAble !== ''
+      ? { outAble: Number(formData.outAble) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function createBasStationDialogModel(record = {}) {
+  return {
+    ...createBasStationFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    stationName: normalizeText(record.stationName || ''),
+    stationId: normalizeText(record.stationId || ''),
+    type:
+      record.type !== void 0 && record.type !== null && record.type !== ''
+        ? Number(record.type)
+        : 0,
+    area:
+      record.area !== void 0 && record.area !== null && record.area !== ''
+        ? Number(record.area)
+        : void 0,
+    useStatus: normalizeText(record.useStatus || record.useStatus$ || ''),
+    isCrossZone:
+      record.isCrossZone !== void 0 && record.isCrossZone !== null && record.isCrossZone !== ''
+        ? Number(record.isCrossZone)
+        : 0,
+    areaIds: normalizeIdArray(record.areaIds ?? record.crossZoneArea ?? []),
+    isWcs:
+      record.isWcs !== void 0 && record.isWcs !== null && record.isWcs !== ''
+        ? Number(record.isWcs)
+        : 0,
+    wcsData: normalizeText(record.wcsData || ''),
+    containerTypes: normalizePlainIdArray(record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? []),
+    barcode: normalizeText(record.barcode || ''),
+    autoTransfer:
+      record.autoTransfer !== void 0 && record.autoTransfer !== null && record.autoTransfer !== ''
+        ? Number(record.autoTransfer)
+        : 0,
+    inAble: record.inAble !== void 0 && record.inAble !== null && record.inAble !== '' ? Number(record.inAble) : 0,
+    outAble:
+      record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
+        ? Number(record.outAble)
+        : 0,
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export const buildBasStationDialogModel = createBasStationDialogModel
+
+export function resolveBasStationAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.areaId ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildBasStationContainerTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildBasStationUseStatusOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: normalizeText(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+function buildIdLabelText(records = [], resolveLabel, fallbackPrefix) {
+  if (!Array.isArray(records) || !records.length) {
+    return ''
+  }
+  const labels = records
+    .map((item) => {
+      if (item === null || item === undefined || item === '') {
+        return ''
+      }
+      const id = typeof item === 'object' ? item.id ?? item.areaId ?? item.value : item
+      if (typeof resolveLabel === 'function') {
+        const resolvedLabel = normalizeText(resolveLabel(id))
+        if (resolvedLabel) {
+          return resolvedLabel
+        }
+      }
+      if (typeof item === 'object') {
+        return normalizeText(item.name || item.areaName || item.label || item.code || item.areaCode || id)
+      }
+      return normalizeText(`${fallbackPrefix} ${id}`)
+    })
+    .filter(Boolean)
+  return labels.join('銆�')
+}
+
+function buildArrayText(records = [], resolveLabel, fallbackPrefix) {
+  if (!Array.isArray(records) || !records.length) {
+    return ''
+  }
+  return records
+    .map((item) => {
+      const id = typeof item === 'object' ? item.id ?? item.value : item
+      if (typeof resolveLabel === 'function') {
+        const resolved = normalizeText(resolveLabel(id))
+        if (resolved) {
+          return resolved
+        }
+      }
+      if (typeof item === 'object') {
+        return normalizeText(item.label || item.name || item.dictLabel || item.value || id)
+      }
+      return normalizeText(`${fallbackPrefix} ${id}`)
+    })
+    .filter(Boolean)
+    .join('銆�')
+}
+
+export function normalizeBasStationDetailRecord(record = {}, resolveAreaLabel, resolveContainerTypeLabel) {
+  const areaIds = normalizeIdArray(record.areaIds ?? record.crossZoneArea ?? [])
+  const containerTypes = normalizePlainIdArray(record.containerTypes ?? record.containerType ?? record.containerTypes$ ?? [])
+  const statusMeta = getBasStationStatusMeta(record.statusBool ?? record.status)
+  const typeMeta = getBasStationTypeMeta(record.type)
+  const useStatusMeta = getBasStationUseStatusMeta(record.useStatus$ || record.useStatus)
+
+  return {
+    ...record,
+    stationName: normalizeText(record.stationName || ''),
+    stationId: normalizeText(record.stationId || ''),
+    type: record.type !== void 0 && record.type !== null && record.type !== '' ? Number(record.type) : void 0,
+    typeText: typeMeta.text,
+    useStatus: normalizeText(record.useStatus || record.useStatus$ || ''),
+    useStatusText: useStatusMeta.text,
+    area: record.area !== void 0 && record.area !== null && record.area !== '' ? Number(record.area) : void 0,
+    areaText: normalizeText(resolveAreaLabel?.(record.area) || record.area$ || record.areaName || ''),
+    areaIds,
+    crossZoneAreaText: buildIdLabelText(areaIds, resolveAreaLabel, '搴撳尯'),
+    isCrossZone: record.isCrossZone !== void 0 && record.isCrossZone !== null && record.isCrossZone !== ''
+      ? Number(record.isCrossZone)
+      : void 0,
+    isCrossZoneText: getBasStationBooleanMeta(record.isCrossZone).text,
+    isWcs: record.isWcs !== void 0 && record.isWcs !== null && record.isWcs !== ''
+      ? Number(record.isWcs)
+      : void 0,
+    isWcsText: getBasStationBooleanMeta(record.isWcs).text,
+    wcsData: normalizeText(record.wcsData || ''),
+    containerTypes,
+    containerTypesText: buildArrayText(containerTypes, resolveContainerTypeLabel, '瀹瑰櫒绫诲瀷'),
+    barcode: normalizeText(record.barcode || ''),
+    autoTransfer: record.autoTransfer !== void 0 && record.autoTransfer !== null && record.autoTransfer !== ''
+      ? Number(record.autoTransfer)
+      : void 0,
+    autoTransferText: getBasStationBooleanMeta(record.autoTransfer).text,
+    inAble: record.inAble !== void 0 && record.inAble !== null && record.inAble !== ''
+      ? Number(record.inAble)
+      : void 0,
+    inAbleText: getBasStationBooleanMeta(record.inAble).text,
+    outAble: record.outAble !== void 0 && record.outAble !== null && record.outAble !== ''
+      ? Number(record.outAble)
+      : void 0,
+    outAbleText: getBasStationBooleanMeta(record.outAble).text,
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    stationAlias: Array.isArray(record.stationAlias) ? [...record.stationAlias] : record.stationAlias,
+    stationAliasText: Array.isArray(record.stationAlias)
+      ? record.stationAlias.map((item) => normalizeText(item)).filter(Boolean).join('銆�')
+      : normalizeText(record.stationAlias || record.stationAlias$ || ''),
+    productionLineCode: normalizeText(record.productionLineCode || ''),
+    productionLineName: normalizeText(record.productionLineName || ''),
+    memo: normalizeText(record.memo || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeBasStationListRow(record = {}, resolveAreaLabel, resolveContainerTypeLabel) {
+  return normalizeBasStationDetailRecord(record, resolveAreaLabel, resolveContainerTypeLabel)
+}
+
+export function buildBasStationPrintRows(records = [], resolveAreaLabel, resolveContainerTypeLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeBasStationListRow(record, resolveAreaLabel, resolveContainerTypeLabel))
+}
+
+export function buildBasStationReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = BAS_STATION_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: BAS_STATION_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...BAS_STATION_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js b/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js
new file mode 100644
index 0000000..1fabdbc
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/basStationTable.columns.js
@@ -0,0 +1,174 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import {
+  getBasStationUseStatusMeta,
+  getBasStationStatusMeta,
+  getBasStationTypeMeta
+} from './basStationPage.helpers'
+
+export function createBasStationTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'stationName',
+      label: '绔欑偣缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationName || '--'
+    },
+    {
+      prop: 'stationId',
+      label: '绔欑偣鍚嶇О',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationId || '--'
+    },
+    {
+      prop: 'typeText',
+      label: '绔欑偣绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) => {
+        const typeMeta = getBasStationTypeMeta(row.type)
+        return h(ElTag, { type: typeMeta.type, effect: 'light' }, () => typeMeta.text)
+      }
+    },
+    {
+      prop: 'useStatusText',
+      label: '浣跨敤鐘舵��',
+      width: 110,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getBasStationUseStatusMeta(row.useStatusText || row.useStatus || '')
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text || '--')
+      }
+    },
+    {
+      prop: 'areaText',
+      label: '鎵�灞炲簱鍖�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaText || row.area$ || '--'
+    },
+    {
+      prop: 'crossZoneAreaText',
+      label: '鍙法鍖哄簱鍖�',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.crossZoneAreaText || '--'
+    },
+    {
+      prop: 'containerTypesText',
+      label: '鍙叆瀹瑰櫒绫诲瀷',
+      minWidth: 200,
+      showOverflowTooltip: true,
+      formatter: (row) => row.containerTypesText || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鏉$爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'inAbleText',
+      label: '鍙叆',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.inAbleText || '--'
+    },
+    {
+      prop: 'outAbleText',
+      label: '鍙嚭',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.outAbleText || '--'
+    },
+    {
+      prop: 'isCrossZoneText',
+      label: '鏄惁璺ㄥ尯',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.isCrossZoneText || '--'
+    },
+    {
+      prop: 'isWcsText',
+      label: '鏄惁WCS',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.isWcsText || '--'
+    },
+    {
+      prop: 'autoTransferText',
+      label: '鑷姩璋冩嫧',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.autoTransferText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getBasStationStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/bas-station/index.vue b/rsf-design/src/views/basic-info/bas-station/index.vue
new file mode 100644
index 0000000..a5dd00a
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/index.vue
@@ -0,0 +1,472 @@
+<template>
+  <div class="bas-station-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板绔欑偣</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <BasStationDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :station-data="currentStationData"
+        :area-options="areaOptions"
+        :container-type-options="containerTypeOptions"
+        :use-status-options="useStatusOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <BasStationDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+        :resolve-area-label="resolveAreaLabel"
+        :resolve-container-type-label="resolveContainerTypeLabel"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import {
+    fetchBasStationPage,
+    fetchDeleteBasStation,
+    fetchExportBasStationReport,
+    fetchGetBasStationDetail,
+    fetchGetBasStationMany,
+    fetchSaveBasStation,
+    fetchUpdateBasStation
+  } from '@/api/bas-station'
+  import BasStationDialog from './modules/bas-station-dialog.vue'
+  import BasStationDetailDrawer from './modules/bas-station-detail-drawer.vue'
+  import { createBasStationTableColumns } from './basStationTable.columns'
+  import {
+    BAS_STATION_REPORT_STYLE,
+    BAS_STATION_REPORT_TITLE,
+    buildBasStationDialogModel,
+    buildBasStationPageQueryParams,
+    buildBasStationPrintRows,
+    buildBasStationReportMeta,
+    buildBasStationSavePayload,
+    buildBasStationSearchParams,
+    buildBasStationContainerTypeOptions,
+    buildBasStationUseStatusOptions,
+    createBasStationSearchState,
+    getBasStationPaginationKey,
+    normalizeBasStationDetailRecord,
+    normalizeBasStationListRow,
+    getBasStationTypeOptions,
+    getBasStationBooleanOptions,
+    resolveBasStationAreaOptions
+  } from './basStationPage.helpers'
+
+  defineOptions({ name: 'BasStation' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createBasStationSearchState())
+  const areaOptions = ref([])
+  const containerTypeOptions = ref([])
+  const useStatusOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = BAS_STATION_REPORT_TITLE
+  const reportQueryParams = computed(() => buildBasStationSearchParams(searchForm.value))
+
+  const areaLabelMap = computed(
+    () =>
+      new Map(
+        areaOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  const containerTypeLabelMap = computed(
+    () =>
+      new Map(
+        containerTypeOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  function resolveAreaLabel(id) {
+    return areaLabelMap.value.get(String(id)) || ''
+  }
+
+  function resolveContainerTypeLabel(value) {
+    return containerTypeLabelMap.value.get(String(value)) || ''
+  }
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐圭紪鐮�/绔欑偣鍚嶇О/澶囨敞'
+      }
+    },
+    {
+      label: '绔欑偣缂栫爜',
+      key: 'stationName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐圭紪鐮�'
+      }
+    },
+    {
+      label: '绔欑偣鍚嶇О',
+      key: 'stationId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰悕绉�'
+      }
+    },
+    {
+      label: '绔欑偣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationTypeOptions()
+      }
+    },
+    {
+      label: '浣跨敤鐘舵��',
+      key: 'useStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: useStatusOptions.value
+      }
+    },
+    {
+      label: '鎵�灞炲簱鍖�',
+      key: 'area',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaOptions.value
+      }
+    },
+    {
+      label: '鏄惁璺ㄥ尯',
+      key: 'isCrossZone',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鏄惁WCS',
+      key: 'isWcs',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮�'
+      }
+    },
+    {
+      label: '鑷姩璋冩嫧',
+      key: 'autoTransfer',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetBasStationDetail(row.id), {}, {
+        timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeBasStationDetailRecord(detail, resolveAreaLabel, resolveContainerTypeLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇绔欑偣璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetBasStationDetail(row.id), {}, {
+        timeoutMessage: '绔欑偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇绔欑偣璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchBasStationPage,
+        apiParams: buildBasStationPageQueryParams(searchForm.value),
+        paginationKey: getBasStationPaginationKey(),
+        columnsFactory: () =>
+          createBasStationTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeBasStationListRow(item, resolveAreaLabel, resolveContainerTypeLabel))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentStationData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildBasStationDialogModel(),
+    buildEditModel: (record) => buildBasStationDialogModel(record),
+    buildSavePayload: (formData) => buildBasStationSavePayload(formData),
+    saveRequest: fetchSaveBasStation,
+    updateRequest: fetchUpdateBasStation,
+    deleteRequest: fetchDeleteBasStation,
+    entityName: '绔欑偣',
+    resolveRecordLabel: (record) => record?.stationName || record?.stationId || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetBasStationMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchBasStationPage({
+        ...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.xlsx',
+      requestExport: (payload) =>
+        fetchExportBasStationReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildBasStationPrintRows(records, resolveAreaLabel, resolveContainerTypeLabel),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildBasStationReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || BAS_STATION_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildBasStationSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createBasStationSearchState())
+    resetSearchParams()
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveBasStationAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadContainerTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_container_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '瀹瑰櫒绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    containerTypeOptions.value = buildBasStationContainerTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadUseStatusOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 100,
+        dictTypeCode: 'sys_sta_use_stas',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '浣跨敤鐘舵�佸姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+    )
+    useStatusOptions.value = buildBasStationUseStatusOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadAreaOptions(), loadContainerTypeOptions(), loadUseStatusOptions()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-station/modules/bas-station-detail-drawer.vue b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-detail-drawer.vue
new file mode 100644
index 0000000..d864f30
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-detail-drawer.vue
@@ -0,0 +1,130 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="绔欑偣绠$悊璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="绔欑偣缂栫爜">{{ detail.stationName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鍚嶇О">{{ detail.stationId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣绫诲瀷">
+            <ElTag :type="detail.typeType || 'info'" effect="light">
+              {{ detail.typeText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浣跨敤鐘舵��">
+            <ElTag :type="detail.useStatusType || 'info'" effect="light">
+              {{ detail.useStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵�灞炲簱鍖�">{{ detail.areaText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉$爜">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙叆">{{ detail.inAbleText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙嚭">{{ detail.outAbleText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏄惁璺ㄥ尯">{{ detail.isCrossZoneText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏄惁WCS">{{ detail.isWcsText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑷姩璋冩嫧">{{ detail.autoTransferText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="涓氬姟閰嶇疆" :column="2" border>
+          <ElDescriptionsItem label="鍙法鍖哄簱鍖�" :span="2">
+            <div v-if="areaItems.length" class="flex flex-wrap gap-2">
+              <ElTag v-for="item in areaItems" :key="item.id" effect="plain">
+                #{{ item.sort }} {{ item.label }}
+              </ElTag>
+            </div>
+            <span v-else>--</span>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙叆瀹瑰櫒绫诲瀷" :span="2">
+            <div v-if="containerItems.length" class="flex flex-wrap gap-2">
+              <ElTag v-for="item in containerItems" :key="item.value" effect="plain">
+                {{ item.label }}
+              </ElTag>
+            </div>
+            <span v-else>--</span>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="WCS鏁版嵁" :span="2">{{ detail.wcsData || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鍒悕" :span="2">{{ detail.stationAliasText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱缂栫爜">{{ detail.productionLineCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱鍚嶇О">{{ detail.productionLineName || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    resolveAreaLabel: { type: Function, default: null },
+    resolveContainerTypeLabel: { type: Function, default: null }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  const areaItems = computed(() => {
+    if (!Array.isArray(props.detail?.areaIds)) {
+      return []
+    }
+    return props.detail.areaIds
+      .map((item, index) => {
+        const id = typeof item === 'object' ? item.id ?? item.areaId ?? item.value : item
+        const label = typeof props.resolveAreaLabel === 'function' ? props.resolveAreaLabel(id) : ''
+        return {
+          id,
+          sort: typeof item === 'object' && item.sort !== undefined ? item.sort : index + 1,
+          label: String(label || item.name || item.areaName || item.label || item.code || item.areaCode || `搴撳尯 ${id}`)
+        }
+      })
+      .filter((item) => item.id !== undefined && item.id !== null && item.id !== '')
+  })
+
+  const containerItems = computed(() => {
+    if (!Array.isArray(props.detail?.containerTypes)) {
+      return []
+    }
+    return props.detail.containerTypes
+      .map((item) => {
+        const value = typeof item === 'object' ? item.id ?? item.value : item
+        const label = typeof props.resolveContainerTypeLabel === 'function' ? props.resolveContainerTypeLabel(value) : ''
+        return {
+          value,
+          label: String(label || item.label || item.name || item.dictLabel || item.value || `瀹瑰櫒绫诲瀷 ${value}`)
+        }
+      })
+      .filter((item) => item.value !== undefined && item.value !== null && item.value !== '')
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/bas-station/modules/bas-station-dialog.vue b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-dialog.vue
new file mode 100644
index 0000000..b478cae
--- /dev/null
+++ b/rsf-design/src/views/basic-info/bas-station/modules/bas-station-dialog.vue
@@ -0,0 +1,292 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="980px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildBasStationDialogModel,
+    createBasStationFormState,
+    getBasStationBooleanOptions,
+    getBasStationStatusOptions,
+    getBasStationTypeOptions
+  } from '../basStationPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    stationData: { type: Object, default: () => ({}) },
+    areaOptions: { type: Array, default: () => [] },
+    containerTypeOptions: { type: Array, default: () => [] },
+    useStatusOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createBasStationFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫绔欑偣' : '鏂板绔欑偣'))
+
+  const rules = computed(() => ({
+    stationName: [{ required: true, message: '璇疯緭鍏ョ珯鐐圭紪鐮�', trigger: 'blur' }],
+    stationId: [{ required: true, message: '璇疯緭鍏ョ珯鐐瑰悕绉�', trigger: 'blur' }],
+    type: [{ required: true, message: '璇烽�夋嫨绔欑偣绫诲瀷', trigger: 'change' }],
+    area: [{ required: true, message: '璇烽�夋嫨鎵�灞炲簱鍖�', trigger: 'change' }],
+    useStatus: [{ required: true, message: '璇烽�夋嫨浣跨敤鐘舵��', trigger: 'change' }],
+    areaIds: [{ type: 'array', required: true, message: '璇烽�夋嫨鍙法鍖哄簱鍖�', trigger: 'change' }],
+    containerTypes: [{ type: 'array', required: true, message: '璇烽�夋嫨鍙叆瀹瑰櫒绫诲瀷', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '绔欑偣缂栫爜',
+      key: 'stationName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ珯鐐圭紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '绔欑偣鍚嶇О',
+      key: 'stationId',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ珯鐐瑰悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '绔欑偣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨绔欑偣绫诲瀷',
+        clearable: true,
+        options: getBasStationTypeOptions()
+      }
+    },
+    {
+      label: '鎵�灞炲簱鍖�',
+      key: 'area',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鎵�灞炲簱鍖�',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions || []
+      }
+    },
+    {
+      label: '浣跨敤鐘舵��',
+      key: 'useStatus',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浣跨敤鐘舵��',
+        clearable: true,
+        filterable: true,
+        options: props.useStatusOptions || []
+      }
+    },
+    {
+      label: '鍙法鍖哄簱鍖�',
+      key: 'areaIds',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨鍙法鍖哄簱鍖�',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.areaOptions || []
+      }
+    },
+    {
+      label: '鍙叆瀹瑰櫒绫诲瀷',
+      key: 'containerTypes',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨鍙叆瀹瑰櫒绫诲瀷',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.containerTypeOptions || []
+      }
+    },
+    {
+      label: '鏄惁璺ㄥ尯',
+      key: 'isCrossZone',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁璺ㄥ尯',
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鏄惁WCS',
+      key: 'isWcs',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁WCS',
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鑷姩璋冩嫧',
+      key: 'autoTransfer',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鑷姩璋冩嫧',
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鍙叆',
+      key: 'inAble',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鍙叆',
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鍙嚭',
+      key: 'outAble',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鍙嚭',
+        clearable: true,
+        options: getBasStationBooleanOptions()
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getBasStationStatusOptions()
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ潯鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: 'WCS鏁版嵁',
+      key: 'wcsData',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏CS鏁版嵁',
+        clearable: true
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildBasStationDialogModel(props.stationData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createBasStationFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.stationData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/companys/companysPage.helpers.js b/rsf-design/src/views/basic-info/companys/companysPage.helpers.js
new file mode 100644
index 0000000..e375f44
--- /dev/null
+++ b/rsf-design/src/views/basic-info/companys/companysPage.helpers.js
@@ -0,0 +1,252 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const COMPANYS_REPORT_TITLE = '寰�鏉ヤ紒涓氭姤琛�'
+export const COMPANYS_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeString(value, fallback = '') {
+  const text = normalizeText(value)
+  return text || fallback
+}
+
+export function createCompanysSearchState() {
+  return {
+    condition: '',
+    code: '',
+    name: '',
+    nameEn: '',
+    breifCode: '',
+    type: '',
+    contact: '',
+    tel: '',
+    email: '',
+    pcode: '',
+    province: '',
+    city: '',
+    address: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createCompanysFormState() {
+  return {
+    id: void 0,
+    code: '',
+    name: '',
+    nameEn: '',
+    breifCode: '',
+    type: '',
+    contact: '',
+    tel: '',
+    email: '',
+    pcode: '',
+    province: '',
+    city: '',
+    address: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getCompanysStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getCompanysPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getCompanysStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildCompanysTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: String(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || '')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildCompanysSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    nameEn: normalizeText(params.nameEn),
+    breifCode: normalizeText(params.breifCode),
+    type: normalizeText(params.type),
+    contact: normalizeText(params.contact),
+    tel: normalizeText(params.tel),
+    email: normalizeText(params.email),
+    pcode: normalizeText(params.pcode),
+    province: normalizeText(params.province),
+    city: normalizeText(params.city),
+    address: normalizeText(params.address),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildCompanysPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildCompanysSearchParams(params)
+  }
+}
+
+export function buildCompanysSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code),
+    name: normalizeText(formData.name),
+    nameEn: normalizeText(formData.nameEn),
+    breifCode: normalizeText(formData.breifCode),
+    type: normalizeString(formData.type),
+    contact: normalizeText(formData.contact),
+    tel: normalizeText(formData.tel),
+    email: normalizeText(formData.email),
+    pcode: normalizeText(formData.pcode),
+    province: normalizeText(formData.province),
+    city: normalizeText(formData.city),
+    address: normalizeText(formData.address),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo)
+  }
+}
+
+export function buildCompanysDialogModel(record = {}) {
+  return {
+    ...createCompanysFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    nameEn: normalizeText(record.nameEn || ''),
+    breifCode: normalizeText(record.breifCode || ''),
+    type: normalizeString(record.type || ''),
+    contact: normalizeText(record.contact || ''),
+    tel: normalizeText(record.tel || ''),
+    email: normalizeText(record.email || ''),
+    pcode: normalizeText(record.pcode || ''),
+    province: normalizeText(record.province || ''),
+    city: normalizeText(record.city || ''),
+    address: normalizeText(record.address || ''),
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeCompanysDetailRecord(record = {}, resolveTypeLabel) {
+  const statusMeta = getCompanysStatusMeta(record.statusBool ?? record.status)
+  const typeValue = record.type ?? ''
+  const typeText =
+    normalizeString(record.companys$ || record.type$ || record.typeText || '') ||
+    normalizeString(typeof resolveTypeLabel === 'function' ? resolveTypeLabel(typeValue) : '') ||
+    normalizeString(typeValue, '--')
+
+  return {
+    ...record,
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    nameEn: normalizeText(record.nameEn || ''),
+    breifCode: normalizeText(record.breifCode || ''),
+    type: normalizeString(typeValue),
+    typeText,
+    contact: normalizeText(record.contact || ''),
+    tel: normalizeText(record.tel || ''),
+    email: normalizeText(record.email || ''),
+    pcode: normalizeText(record.pcode || ''),
+    province: normalizeText(record.province || ''),
+    city: normalizeText(record.city || ''),
+    address: normalizeText(record.address || ''),
+    memo: normalizeText(record.memo || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeCompanysListRow(record = {}, resolveTypeLabel) {
+  return normalizeCompanysDetailRecord(record, resolveTypeLabel)
+}
+
+export function buildCompanysPrintRows(records = [], resolveTypeLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeCompanysListRow(record, resolveTypeLabel))
+}
+
+export function buildCompanysReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = COMPANYS_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: COMPANYS_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...COMPANYS_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/companys/companysTable.columns.js b/rsf-design/src/views/basic-info/companys/companysTable.columns.js
new file mode 100644
index 0000000..042186a
--- /dev/null
+++ b/rsf-design/src/views/basic-info/companys/companysTable.columns.js
@@ -0,0 +1,102 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getCompanysStatusMeta } from './companysPage.helpers'
+
+export function createCompanysTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true,
+  resolveTypeLabel
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'code', label: '浼佷笟缂栫爜', minWidth: 140, showOverflowTooltip: true, formatter: (row) => row.code || '--' },
+    { prop: 'name', label: '浼佷笟鍚嶇О', minWidth: 170, showOverflowTooltip: true, formatter: (row) => row.name || '--' },
+    { prop: 'nameEn', label: '鑻辨枃鍒悕', minWidth: 160, showOverflowTooltip: true, formatter: (row) => row.nameEn || '--' },
+    { prop: 'breifCode', label: '鍔╄鐮�', minWidth: 120, showOverflowTooltip: true, formatter: (row) => row.breifCode || '--' },
+    {
+      prop: 'typeText',
+      label: '浼佷笟绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        row.typeText || (typeof resolveTypeLabel === 'function' ? resolveTypeLabel(row.type) : '') || '--'
+    },
+    { prop: 'contact', label: '鑱旂郴浜�', minWidth: 120, showOverflowTooltip: true, formatter: (row) => row.contact || '--' },
+    { prop: 'tel', label: '鑱旂郴鐢佃瘽', minWidth: 140, showOverflowTooltip: true, formatter: (row) => row.tel || '--' },
+    { prop: 'email', label: '閭', minWidth: 180, showOverflowTooltip: true, formatter: (row) => row.email || '--' },
+    { prop: 'pcode', label: '閭紪', minWidth: 120, showOverflowTooltip: true, formatter: (row) => row.pcode || '--' },
+    { prop: 'province', label: '鐪佷唤', minWidth: 120, showOverflowTooltip: true, formatter: (row) => row.province || '--' },
+    { prop: 'city', label: '鍩庡競', minWidth: 120, showOverflowTooltip: true, formatter: (row) => row.city || '--' },
+    { prop: 'address', label: '鍦板潃', minWidth: 220, showOverflowTooltip: true, formatter: (row) => row.address || '--' },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getCompanysStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    { prop: 'memo', label: '澶囨敞', minWidth: 180, showOverflowTooltip: true, formatter: (row) => row.memo || '--' },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || row.createBy$ || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || row.createTime$ || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/companys/index.vue b/rsf-design/src/views/basic-info/companys/index.vue
new file mode 100644
index 0000000..abc0bf2
--- /dev/null
+++ b/rsf-design/src/views/basic-info/companys/index.vue
@@ -0,0 +1,355 @@
+<template>
+  <div class="companys-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板浼佷笟</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <CompanysDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :companys-data="currentCompanysData"
+        :type-options="typeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <CompanysDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    fetchCompanysPage,
+    fetchDeleteCompanys,
+    fetchExportCompanysReport,
+    fetchGetCompanysDetail,
+    fetchGetCompanysMany,
+    fetchSaveCompanys,
+    fetchUpdateCompanys
+  } from '@/api/companys'
+  import CompanysDialog from './modules/companys-dialog.vue'
+  import CompanysDetailDrawer from './modules/companys-detail-drawer.vue'
+  import { createCompanysTableColumns } from './companysTable.columns'
+  import {
+    COMPANYS_REPORT_STYLE,
+    COMPANYS_REPORT_TITLE,
+    buildCompanysDialogModel,
+    buildCompanysPageQueryParams,
+    buildCompanysSavePayload,
+    buildCompanysPrintRows,
+    buildCompanysReportMeta,
+    buildCompanysSearchParams,
+    buildCompanysTypeOptions,
+    createCompanysSearchState,
+    getCompanysPaginationKey,
+    normalizeCompanysListRow
+  } from './companysPage.helpers'
+
+  defineOptions({ name: 'Companys' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createCompanysSearchState())
+  const typeOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = COMPANYS_REPORT_TITLE
+  const reportQueryParams = computed(() => buildCompanysSearchParams(searchForm.value))
+  const typeLabelMap = computed(
+    () =>
+      new Map(
+        typeOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  function resolveTypeLabel(value) {
+    return typeLabelMap.value.get(String(value)) || ''
+  }
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ紒涓氬悕绉�/缂栫爜/鑱旂郴浜�/鐢佃瘽'
+      }
+    },
+    { label: '浼佷笟缂栫爜', key: 'code', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヤ紒涓氱紪鐮�' } },
+    { label: '浼佷笟鍚嶇О', key: 'name', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヤ紒涓氬悕绉�' } },
+    { label: '鑻辨枃鍒悕', key: 'nameEn', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ嫳鏂囧埆鍚�' } },
+    { label: '鍔╄鐮�', key: 'breifCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ姪璁扮爜' } },
+    {
+      label: '浼佷笟绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: typeOptions.value
+      }
+    },
+    { label: '鑱旂郴浜�', key: 'contact', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ仈绯讳汉' } },
+    { label: '鑱旂郴鐢佃瘽', key: 'tel', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ仈绯荤數璇�' } },
+    { label: '閭', key: 'email', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ラ偖绠�' } },
+    { label: '閭紪', key: 'pcode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ラ偖缂�' } },
+    { label: '鐪佷唤', key: 'province', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ渷浠�' } },
+    { label: '鍩庡競', key: 'city', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ煄甯�' } },
+    { label: '鍦板潃', key: 'address', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ湴鍧�' } },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetCompanysDetail(row.id), {}, {
+        timeoutMessage: '寰�鏉ヤ紒涓氳鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      detailData.value = normalizeCompanysListRow(detail, resolveTypeLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇寰�鏉ヤ紒涓氳鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetCompanysDetail(row.id), {}, {
+        timeoutMessage: '寰�鏉ヤ紒涓氳鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇寰�鏉ヤ紒涓氳鎯呭け璐�')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchCompanysPage,
+      apiParams: buildCompanysPageQueryParams(searchForm.value),
+      paginationKey: getCompanysPaginationKey(),
+      columnsFactory: () =>
+        createCompanysTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          resolveTypeLabel,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeCompanysListRow(item, resolveTypeLabel))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentCompanysData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildCompanysDialogModel(),
+    buildEditModel: (record) => buildCompanysDialogModel(record),
+    buildSavePayload: (formData) => buildCompanysSavePayload(formData),
+    saveRequest: fetchSaveCompanys,
+    updateRequest: fetchUpdateCompanys,
+    deleteRequest: fetchDeleteCompanys,
+    entityName: '寰�鏉ヤ紒涓�',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...COMPANYS_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetCompanysMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchCompanysPage({
+        ...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: 'companys.xlsx',
+    requestExport: (payload) =>
+      fetchExportCompanysReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildCompanysPrintRows(records, resolveTypeLabel),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildCompanysReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || COMPANYS_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildCompanysSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createCompanysSearchState())
+    resetSearchParams()
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_companys_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '浼佷笟绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    typeOptions.value = buildCompanysTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await loadTypeOptions()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/companys/modules/companys-detail-drawer.vue b/rsf-design/src/views/basic-info/companys/modules/companys-detail-drawer.vue
new file mode 100644
index 0000000..3c6bb18
--- /dev/null
+++ b/rsf-design/src/views/basic-info/companys/modules/companys-detail-drawer.vue
@@ -0,0 +1,65 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="寰�鏉ヤ紒涓氳鎯�"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="浼佷笟缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浼佷笟鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑻辨枃鍒悕">{{ detail.nameEn || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍔╄鐮�">{{ detail.breifCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浼佷笟绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑱旂郴浜�">{{ detail.contact || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑱旂郴鐢佃瘽">{{ detail.tel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閭">{{ detail.email || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閭紪">{{ detail.pcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐪佷唤">{{ detail.province || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍩庡競">{{ detail.city || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍦板潃" :span="2">{{ detail.address || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/companys/modules/companys-dialog.vue b/rsf-design/src/views/basic-info/companys/modules/companys-dialog.vue
new file mode 100644
index 0000000..02459c0
--- /dev/null
+++ b/rsf-design/src/views/basic-info/companys/modules/companys-dialog.vue
@@ -0,0 +1,247 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildCompanysDialogModel,
+    createCompanysFormState,
+    getCompanysStatusOptions
+  } from '../companysPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    companysData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createCompanysFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫寰�鏉ヤ紒涓�' : '鏂板寰�鏉ヤ紒涓�'))
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ヤ紒涓氬悕绉�', trigger: 'blur' }],
+    breifCode: [{ required: true, message: '璇疯緭鍏ュ姪璁扮爜', trigger: 'blur' }],
+    type: [{ required: true, message: '璇烽�夋嫨浼佷笟绫诲瀷', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '浼佷笟缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '鐣欑┖灏嗚嚜鍔ㄧ敓鎴�',
+        clearable: true,
+        disabled: isEdit.value
+      }
+    },
+    {
+      label: '浼佷笟鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヤ紒涓氬悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鑻辨枃鍒悕',
+      key: 'nameEn',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ嫳鏂囧埆鍚�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍔╄鐮�',
+      key: 'breifCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ姪璁扮爜',
+        clearable: true
+      }
+    },
+    {
+      label: '浼佷笟绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浼佷笟绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.typeOptions
+      }
+    },
+    {
+      label: '鑱旂郴浜�',
+      key: 'contact',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ仈绯讳汉',
+        clearable: true
+      }
+    },
+    {
+      label: '鑱旂郴鐢佃瘽',
+      key: 'tel',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+        clearable: true
+      }
+    },
+    {
+      label: '閭',
+      key: 'email',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ偖绠�',
+        clearable: true
+      }
+    },
+    {
+      label: '閭紪',
+      key: 'pcode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ偖缂�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐪佷唤',
+      key: 'province',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ渷浠�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍩庡競',
+      key: 'city',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ煄甯�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍦板潃',
+      key: 'address',
+      type: 'input',
+      span: 24,
+      props: {
+        placeholder: '璇疯緭鍏ュ湴鍧�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getCompanysStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildCompanysDialogModel(props.companysData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createCompanysFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.companysData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/contract/contractPage.helpers.js b/rsf-design/src/views/basic-info/contract/contractPage.helpers.js
new file mode 100644
index 0000000..77df180
--- /dev/null
+++ b/rsf-design/src/views/basic-info/contract/contractPage.helpers.js
@@ -0,0 +1,162 @@
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+export const CONTRACT_REPORT_TITLE = '鍚堝悓淇℃伅鎶ヨ〃'
+export const CONTRACT_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function createContractSearchState() {
+  return {
+    condition: '',
+    code: '',
+    name: '',
+    projectName: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createContractFormState() {
+  return {
+    id: void 0,
+    code: '',
+    name: '',
+    projectName: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getContractPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getContractStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getContractStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildContractSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'name', 'projectName', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.status !== '' && params.status !== null && params.status !== undefined) {
+    result.status = Number(params.status)
+  }
+
+  return result
+}
+
+export function buildContractPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildContractSearchParams(params)
+  }
+}
+
+export function buildContractSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code),
+    name: normalizeText(formData.name),
+    projectName: normalizeText(formData.projectName),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo)
+  }
+}
+
+export function buildContractDialogModel(record = {}) {
+  return {
+    ...createContractFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    projectName: normalizeText(record.projectName || ''),
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeContractListRow(record = {}) {
+  const statusMeta = getContractStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    code: normalizeText(record.code) || '--',
+    name: normalizeText(record.name) || '--',
+    projectName: normalizeText(record.projectName) || '--',
+    memo: normalizeText(record.memo) || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeContractDetailRecord(record = {}) {
+  return normalizeContractListRow(record)
+}
+
+export function buildContractPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeContractListRow(record))
+}
+
+export function buildContractReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = CONTRACT_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: CONTRACT_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...CONTRACT_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/contract/contractTable.columns.js b/rsf-design/src/views/basic-info/contract/contractTable.columns.js
new file mode 100644
index 0000000..3e2db3d
--- /dev/null
+++ b/rsf-design/src/views/basic-info/contract/contractTable.columns.js
@@ -0,0 +1,99 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+
+export function createContractTableColumns({ handleView, handleEdit, handleDelete, canEdit = true, canDelete = true }) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: '鍚堝悓缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'name',
+      label: '鍚堝悓鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'projectName',
+      label: '椤圭洰鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 200,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/contract/index.vue b/rsf-design/src/views/basic-info/contract/index.vue
new file mode 100644
index 0000000..cfc778e
--- /dev/null
+++ b/rsf-design/src/views/basic-info/contract/index.vue
@@ -0,0 +1,316 @@
+<template>
+  <div class="contract-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鍚堝悓淇℃伅</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <ContractDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :contract-data="currentContractData"
+        @submit="handleDialogSubmit"
+      />
+
+      <ContractDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchContractPage,
+    fetchDeleteContract,
+    fetchExportContractReport,
+    fetchGetContractDetail,
+    fetchGetContractMany,
+    fetchSaveContract,
+    fetchUpdateContract
+  } from '@/api/contract'
+  import ContractDialog from './modules/contract-dialog.vue'
+  import ContractDetailDrawer from './modules/contract-detail-drawer.vue'
+  import { createContractTableColumns } from './contractTable.columns'
+  import {
+    buildContractDialogModel,
+    buildContractPageQueryParams,
+    buildContractPrintRows,
+    buildContractReportMeta,
+    buildContractSavePayload,
+    buildContractSearchParams,
+    createContractFormState,
+    createContractSearchState,
+    CONTRACT_REPORT_STYLE,
+    CONTRACT_REPORT_TITLE,
+    getContractPaginationKey,
+    normalizeContractDetailRecord,
+    normalizeContractListRow
+  } from './contractPage.helpers'
+
+  defineOptions({ name: 'Contract' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createContractSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = CONTRACT_REPORT_TITLE
+  const reportQueryParams = computed(() => buildContractSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悎鍚岀紪鐮�/鍚嶇О/椤圭洰鍚嶇О/澶囨敞'
+      }
+    },
+    {
+      label: '鍚堝悓缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悎鍚岀紪鐮�'
+      }
+    },
+    {
+      label: '鍚堝悓鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悎鍚屽悕绉�'
+      }
+    },
+    {
+      label: '椤圭洰鍚嶇О',
+      key: 'projectName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetContractDetail(row.id), {}, {
+        timeoutMessage: '鍚堝悓淇℃伅璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeContractDetailRecord(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鍚堝悓淇℃伅璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetContractDetail(row.id), {}, {
+        timeoutMessage: '鍚堝悓淇℃伅璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鍚堝悓淇℃伅璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchContractPage,
+        apiParams: buildContractPageQueryParams(searchForm.value),
+        paginationKey: getContractPaginationKey(),
+        columnsFactory: () =>
+          createContractTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeContractListRow(item))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentContractData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildContractDialogModel(createContractFormState()),
+    buildEditModel: (record) => buildContractDialogModel(record),
+    buildSavePayload: (formData) => buildContractSavePayload(formData),
+    saveRequest: fetchSaveContract,
+    updateRequest: fetchUpdateContract,
+    deleteRequest: fetchDeleteContract,
+    entityName: '鍚堝悓淇℃伅',
+    resolveRecordLabel: (record) => record?.code || record?.name || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...CONTRACT_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetContractMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchContractPage({
+        ...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: 'contract.xlsx',
+      requestExport: (payload) =>
+        fetchExportContractReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildContractPrintRows(records),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildContractReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || CONTRACT_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildContractSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createContractSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/contract/modules/contract-detail-drawer.vue b/rsf-design/src/views/basic-info/contract/modules/contract-detail-drawer.vue
new file mode 100644
index 0000000..1df6729
--- /dev/null
+++ b/rsf-design/src/views/basic-info/contract/modules/contract-detail-drawer.vue
@@ -0,0 +1,55 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍚堝悓淇℃伅璇︽儏"
+    size="960px"
+    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">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚堝悓缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚堝悓鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="椤圭洰鍚嶇О">{{ detail.projectName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'ContractDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/contract/modules/contract-dialog.vue b/rsf-design/src/views/basic-info/contract/modules/contract-dialog.vue
new file mode 100644
index 0000000..40dc0b3
--- /dev/null
+++ b/rsf-design/src/views/basic-info/contract/modules/contract-dialog.vue
@@ -0,0 +1,156 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildContractDialogModel, createContractFormState, getContractStatusOptions } from '../contractPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    contractData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createContractFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鍚堝悓淇℃伅' : '鏂板鍚堝悓淇℃伅'))
+
+  const rules = computed(() => ({
+    code: [{ required: true, message: '璇疯緭鍏ュ悎鍚岀紪鐮�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ悎鍚屽悕绉�', trigger: 'blur' }],
+    projectName: [{ required: true, message: '璇疯緭鍏ラ」鐩悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '鍚堝悓缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ悎鍚岀紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍚堝悓鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ悎鍚屽悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '椤圭洰鍚嶇О',
+      key: 'projectName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ」鐩悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getContractStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildContractDialogModel(props.contractData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createContractFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.contractData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/device-bind/deviceBindPage.helpers.js b/rsf-design/src/views/basic-info/device-bind/deviceBindPage.helpers.js
new file mode 100644
index 0000000..2f1343e
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-bind/deviceBindPage.helpers.js
@@ -0,0 +1,314 @@
+const FLAG_META = {
+  1: { text: '鏄�', type: 'success', bool: true },
+  0: { text: '鍚�', type: 'danger', bool: false }
+}
+
+export const DEVICE_BIND_REPORT_TITLE = '璁惧缁戝畾鎶ヨ〃'
+export const DEVICE_BIND_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeFlagValue(value, fallback = '0') {
+  if (value === 1 || value === '1' || value === true) {
+    return '1'
+  }
+  if (value === 0 || value === '0' || value === false) {
+    return '0'
+  }
+  const text = normalizeText(value)
+  return text || fallback
+}
+
+function normalizeFlagText(value) {
+  if (value === 1 || value === '1' || value === true) {
+    return FLAG_META[1].text
+  }
+  if (value === 0 || value === '0' || value === false) {
+    return FLAG_META[0].text
+  }
+  return '--'
+}
+
+function splitStaList(value) {
+  return normalizeText(value)
+    .split(/[;,锛孿n\r]+/g)
+    .map((item) => item.trim())
+    .filter(Boolean)
+}
+
+export function createDeviceBindSearchState() {
+  return {
+    condition: '',
+    currentRow: null,
+    startRow: null,
+    endRow: null,
+    deviceQty: null,
+    startDeviceNo: null,
+    endDeviceNo: null,
+    typeId: '',
+    staList: '',
+    beSimilar: '',
+    emptySimilar: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createDeviceBindFormState() {
+  return {
+    id: void 0,
+    currentRow: void 0,
+    startRow: void 0,
+    endRow: void 0,
+    deviceQty: void 0,
+    startDeviceNo: void 0,
+    endDeviceNo: void 0,
+    staList: '',
+    typeId: void 0,
+    beSimilar: '0',
+    emptySimilar: '0',
+    memo: ''
+  }
+}
+
+export function getDeviceBindPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getDeviceBindFlagOptions() {
+  return [
+    { label: '鏄�', value: '1' },
+    { label: '鍚�', value: '0' }
+  ]
+}
+
+export function getDeviceBindFlagMeta(value) {
+  if (value === true || Number(value) === 1) {
+    return FLAG_META[1]
+  }
+  if (value === false || Number(value) === 0) {
+    return FLAG_META[0]
+  }
+  return { text: '--', type: 'info', bool: void 0 }
+}
+
+export function resolveDeviceBindTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.areaId ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.name || item.areaName || item.code || item.areaCode || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildDeviceBindSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    currentRow: normalizeNumber(params.currentRow),
+    startRow: normalizeNumber(params.startRow),
+    endRow: normalizeNumber(params.endRow),
+    deviceQty: normalizeNumber(params.deviceQty),
+    startDeviceNo: normalizeNumber(params.startDeviceNo),
+    endDeviceNo: normalizeNumber(params.endDeviceNo),
+    typeId: normalizeNumber(params.typeId),
+    staList: normalizeText(params.staList),
+    beSimilar: normalizeText(params.beSimilar),
+    emptySimilar: normalizeText(params.emptySimilar),
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildDeviceBindPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeviceBindSearchParams(params)
+  }
+}
+
+export function buildDeviceBindSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.currentRow !== void 0 && formData.currentRow !== null && formData.currentRow !== ''
+      ? { currentRow: Number(formData.currentRow) }
+      : {}),
+    ...(formData.startRow !== void 0 && formData.startRow !== null && formData.startRow !== ''
+      ? { startRow: Number(formData.startRow) }
+      : {}),
+    ...(formData.endRow !== void 0 && formData.endRow !== null && formData.endRow !== ''
+      ? { endRow: Number(formData.endRow) }
+      : {}),
+    ...(formData.deviceQty !== void 0 && formData.deviceQty !== null && formData.deviceQty !== ''
+      ? { deviceQty: Number(formData.deviceQty) }
+      : {}),
+    ...(formData.startDeviceNo !== void 0 && formData.startDeviceNo !== null && formData.startDeviceNo !== ''
+      ? { startDeviceNo: Number(formData.startDeviceNo) }
+      : {}),
+    ...(formData.endDeviceNo !== void 0 && formData.endDeviceNo !== null && formData.endDeviceNo !== ''
+      ? { endDeviceNo: Number(formData.endDeviceNo) }
+      : {}),
+    staList: normalizeText(formData.staList) || '',
+    ...(formData.typeId !== void 0 && formData.typeId !== null && formData.typeId !== ''
+      ? { typeId: Number(formData.typeId) }
+      : {}),
+    beSimilar: normalizeFlagValue(formData.beSimilar),
+    emptySimilar: normalizeFlagValue(formData.emptySimilar),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildDeviceBindDialogModel(record = {}) {
+  return {
+    ...createDeviceBindFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    currentRow:
+      record.currentRow !== void 0 && record.currentRow !== null && record.currentRow !== ''
+        ? Number(record.currentRow)
+        : void 0,
+    startRow:
+      record.startRow !== void 0 && record.startRow !== null && record.startRow !== ''
+        ? Number(record.startRow)
+        : void 0,
+    endRow:
+      record.endRow !== void 0 && record.endRow !== null && record.endRow !== ''
+        ? Number(record.endRow)
+        : void 0,
+    deviceQty:
+      record.deviceQty !== void 0 && record.deviceQty !== null && record.deviceQty !== ''
+        ? Number(record.deviceQty)
+        : void 0,
+    startDeviceNo:
+      record.startDeviceNo !== void 0 && record.startDeviceNo !== null && record.startDeviceNo !== ''
+        ? Number(record.startDeviceNo)
+        : void 0,
+    endDeviceNo:
+      record.endDeviceNo !== void 0 && record.endDeviceNo !== null && record.endDeviceNo !== ''
+        ? Number(record.endDeviceNo)
+        : void 0,
+    staList: normalizeText(record.staList || ''),
+    typeId:
+      record.typeId !== void 0 && record.typeId !== null && record.typeId !== ''
+        ? Number(record.typeId)
+        : void 0,
+    beSimilar: normalizeFlagValue(record.beSimilar, '0'),
+    emptySimilar: normalizeFlagValue(record.emptySimilar, '0'),
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeDeviceBindDetailRecord(record = {}, resolveTypeLabel) {
+  const beSimilarMeta = getDeviceBindFlagMeta(record.beSimilar)
+  const emptySimilarMeta = getDeviceBindFlagMeta(record.emptySimilar)
+
+  return {
+    ...record,
+    currentRow:
+      record.currentRow !== void 0 && record.currentRow !== null && record.currentRow !== ''
+        ? Number(record.currentRow)
+        : void 0,
+    startRow:
+      record.startRow !== void 0 && record.startRow !== null && record.startRow !== ''
+        ? Number(record.startRow)
+        : void 0,
+    endRow:
+      record.endRow !== void 0 && record.endRow !== null && record.endRow !== ''
+        ? Number(record.endRow)
+        : void 0,
+    deviceQty:
+      record.deviceQty !== void 0 && record.deviceQty !== null && record.deviceQty !== ''
+        ? Number(record.deviceQty)
+        : void 0,
+    startDeviceNo:
+      record.startDeviceNo !== void 0 && record.startDeviceNo !== null && record.startDeviceNo !== ''
+        ? Number(record.startDeviceNo)
+        : void 0,
+    endDeviceNo:
+      record.endDeviceNo !== void 0 && record.endDeviceNo !== null && record.endDeviceNo !== ''
+        ? Number(record.endDeviceNo)
+        : void 0,
+    staList: normalizeText(record.staList || ''),
+    staListItems: splitStaList(record.staList),
+    typeIdText:
+      normalizeText(record.typeId$ || record.typeIdText || '') ||
+      (typeof resolveTypeLabel === 'function' ? normalizeText(resolveTypeLabel(record.typeId)) : ''),
+    beSimilarText: beSimilarMeta.text,
+    beSimilarType: beSimilarMeta.type,
+    emptySimilarText: emptySimilarMeta.text,
+    emptySimilarType: emptySimilarMeta.type,
+    memo: normalizeText(record.memo || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeDeviceBindListRow(record = {}, resolveTypeLabel) {
+  return normalizeDeviceBindDetailRecord(record, resolveTypeLabel)
+}
+
+export function buildDeviceBindPrintRows(records = [], resolveTypeLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeDeviceBindListRow(record, resolveTypeLabel))
+}
+
+export function buildDeviceBindReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = DEVICE_BIND_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: DEVICE_BIND_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...DEVICE_BIND_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/device-bind/deviceBindTable.columns.js b/rsf-design/src/views/basic-info/device-bind/deviceBindTable.columns.js
new file mode 100644
index 0000000..9238ee6
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-bind/deviceBindTable.columns.js
@@ -0,0 +1,141 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getDeviceBindFlagMeta } from './deviceBindPage.helpers'
+
+export function createDeviceBindTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  resolveTypeLabel,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'currentRow',
+      label: '褰撳墠鎺掑彿',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.currentRow ?? '--'
+    },
+    {
+      prop: 'startRow',
+      label: '璧峰鎺掑彿',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.startRow ?? '--'
+    },
+    {
+      prop: 'endRow',
+      label: '缁堟鎺掑彿',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.endRow ?? '--'
+    },
+    {
+      prop: 'deviceQty',
+      label: '璁惧鏁伴噺',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.deviceQty ?? '--'
+    },
+    {
+      prop: 'startDeviceNo',
+      label: '璧峰璁惧鍙�',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.startDeviceNo ?? '--'
+    },
+    {
+      prop: 'endDeviceNo',
+      label: '缁堟璁惧鍙�',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.endDeviceNo ?? '--'
+    },
+    {
+      prop: 'staList',
+      label: '绔欑偣鍒楄〃',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.staList || '--'
+    },
+    {
+      prop: 'typeIdText',
+      label: '搴撳尯绫诲瀷',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        row.typeIdText || (typeof resolveTypeLabel === 'function' ? resolveTypeLabel(row.typeId) : '') || '--'
+    },
+    {
+      prop: 'beSimilar',
+      label: '鐗╂枡鐩镐技',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getDeviceBindFlagMeta(row.beSimilar)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'emptySimilar',
+      label: '绌烘澘闈犺繎',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getDeviceBindFlagMeta(row.emptySimilar)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/device-bind/index.vue b/rsf-design/src/views/basic-info/device-bind/index.vue
new file mode 100644
index 0000000..091c9b4
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-bind/index.vue
@@ -0,0 +1,434 @@
+<template>
+  <div class="device-bind-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板缁戝畾</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <DeviceBindDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :device-bind-data="currentDeviceBindData"
+        :type-options="typeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <DeviceBindDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import {
+    fetchDeleteDeviceBind,
+    fetchDeviceBindPage,
+    fetchExportDeviceBindReport,
+    fetchGetDeviceBindDetail,
+    fetchGetDeviceBindMany,
+    fetchSaveDeviceBind,
+    fetchUpdateDeviceBind
+  } from '@/api/device-bind'
+  import DeviceBindDialog from './modules/device-bind-dialog.vue'
+  import DeviceBindDetailDrawer from './modules/device-bind-detail-drawer.vue'
+  import { createDeviceBindTableColumns } from './deviceBindTable.columns'
+  import {
+    buildDeviceBindDialogModel,
+    buildDeviceBindPageQueryParams,
+    buildDeviceBindPrintRows,
+    buildDeviceBindReportMeta,
+    buildDeviceBindSavePayload,
+    buildDeviceBindSearchParams,
+    createDeviceBindSearchState,
+    getDeviceBindPaginationKey,
+    normalizeDeviceBindListRow,
+    resolveDeviceBindTypeOptions,
+    DEVICE_BIND_REPORT_STYLE,
+    DEVICE_BIND_REPORT_TITLE
+  } from './deviceBindPage.helpers'
+
+  defineOptions({ name: 'DeviceBind' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createDeviceBindSearchState())
+  const typeOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = DEVICE_BIND_REPORT_TITLE
+  const reportQueryParams = computed(() => buildDeviceBindSearchParams(searchForm.value))
+  const typeLabelMap = computed(
+    () =>
+      new Map(
+        typeOptions.value
+          .map((item) => [String(item.value), item.label])
+          .filter(([value, label]) => value && label)
+      )
+  )
+
+  function resolveTypeLabel(id) {
+    return typeLabelMap.value.get(String(id)) || ''
+  }
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ帓鍙�/璁惧鍙�/绔欑偣鍒楄〃/澶囨敞'
+      }
+    },
+    {
+      label: '褰撳墠鎺掑彿',
+      key: 'currentRow',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ綋鍓嶆帓鍙�'
+      }
+    },
+    {
+      label: '璧峰鎺掑彿',
+      key: 'startRow',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ捣濮嬫帓鍙�'
+      }
+    },
+    {
+      label: '缁堟鎺掑彿',
+      key: 'endRow',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ粓姝㈡帓鍙�'
+      }
+    },
+    {
+      label: '璁惧鏁伴噺',
+      key: 'deviceQty',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ澶囨暟閲�'
+      }
+    },
+    {
+      label: '璧峰璁惧鍙�',
+      key: 'startDeviceNo',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ捣濮嬭澶囧彿'
+      }
+    },
+    {
+      label: '缁堟璁惧鍙�',
+      key: 'endDeviceNo',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ粓姝㈣澶囧彿'
+      }
+    },
+    {
+      label: '搴撳尯绫诲瀷',
+      key: 'typeId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: typeOptions.value
+      }
+    },
+    {
+      label: '绔欑偣鍒楄〃',
+      key: 'staList',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰垪琛�'
+      }
+    },
+    {
+      label: '鐗╂枡鐩镐技',
+      key: 'beSimilar',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鏄�', value: '1' },
+          { label: '鍚�', value: '0' }
+        ]
+      }
+    },
+    {
+      label: '绌烘澘闈犺繎',
+      key: 'emptySimilar',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鏄�', value: '1' },
+          { label: '鍚�', value: '0' }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetDeviceBindDetail(row.id), {}, {
+        timeoutMessage: '璁惧缁戝畾璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeDeviceBindListRow(detail, resolveTypeLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇璁惧缁戝畾璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetDeviceBindDetail(row.id), {}, {
+        timeoutMessage: '璁惧缁戝畾璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇璁惧缁戝畾璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchDeviceBindPage,
+        apiParams: buildDeviceBindPageQueryParams(searchForm.value),
+        paginationKey: getDeviceBindPaginationKey(),
+        columnsFactory: () =>
+          createDeviceBindTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            resolveTypeLabel,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeDeviceBindListRow(item, resolveTypeLabel))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentDeviceBindData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildDeviceBindDialogModel(),
+    buildEditModel: (record) => buildDeviceBindDialogModel(record),
+    buildSavePayload: (formData) => buildDeviceBindSavePayload(formData),
+    saveRequest: fetchSaveDeviceBind,
+    updateRequest: fetchUpdateDeviceBind,
+    deleteRequest: fetchDeleteDeviceBind,
+    entityName: '璁惧缁戝畾',
+    resolveRecordLabel: (record) => record?.currentRow ?? record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...DEVICE_BIND_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetDeviceBindMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchDeviceBindPage({
+        ...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: 'device-bind.xlsx',
+      requestExport: (payload) =>
+        fetchExportDeviceBindReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildDeviceBindPrintRows(records, resolveTypeLabel),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildDeviceBindReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || DEVICE_BIND_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildDeviceBindSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createDeviceBindSearchState())
+    resetSearchParams()
+  }
+
+  async function loadTypeOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    typeOptions.value = resolveDeviceBindTypeOptions(defaultResponseAdapter(records).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadTypeOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/device-bind/modules/device-bind-detail-drawer.vue b/rsf-design/src/views/basic-info/device-bind/modules/device-bind-detail-drawer.vue
new file mode 100644
index 0000000..8a067e0
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-bind/modules/device-bind-detail-drawer.vue
@@ -0,0 +1,79 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璁惧缁戝畾璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="褰撳墠鎺掑彿">{{ detail.currentRow ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璧峰鎺掑彿">{{ detail.startRow ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁堟鎺掑彿">{{ detail.endRow ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁惧鏁伴噺">{{ detail.deviceQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璧峰璁惧鍙�">{{ detail.startDeviceNo ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁堟璁惧鍙�">{{ detail.endDeviceNo ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯绫诲瀷">{{ detail.typeIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鍒楄〃" :span="2">
+            <div v-if="staListItems.length" class="flex flex-wrap gap-2">
+              <ElTag v-for="item in staListItems" :key="item" effect="plain">{{ item }}</ElTag>
+            </div>
+            <span v-else>--</span>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鐩镐技">
+            <ElTag :type="detail.beSimilarType || 'info'" effect="light">
+              {{ detail.beSimilarText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="绌烘澘闈犺繎">
+            <ElTag :type="detail.emptySimilarType || 'info'" effect="light">
+              {{ detail.emptySimilarText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  const staListItems = computed(() => {
+    if (Array.isArray(props.detail?.staListItems) && props.detail.staListItems.length) {
+      return props.detail.staListItems
+    }
+    const text = String(props.detail?.staList || '').trim()
+    return text ? text.split(/[;,锛孿n\r]+/g).map((item) => item.trim()).filter(Boolean) : []
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/device-bind/modules/device-bind-dialog.vue b/rsf-design/src/views/basic-info/device-bind/modules/device-bind-dialog.vue
new file mode 100644
index 0000000..20ea4c1
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-bind/modules/device-bind-dialog.vue
@@ -0,0 +1,235 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="920px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildDeviceBindDialogModel,
+    createDeviceBindFormState,
+    getDeviceBindFlagOptions
+  } from '../deviceBindPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    deviceBindData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createDeviceBindFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫璁惧缁戝畾' : '鏂板璁惧缁戝畾'))
+
+  const rules = computed(() => ({
+    currentRow: [{ required: true, message: '璇疯緭鍏ュ綋鍓嶆帓鍙�', trigger: 'blur' }],
+    startRow: [{ required: true, message: '璇疯緭鍏ヨ捣濮嬫帓鍙�', trigger: 'blur' }],
+    endRow: [{ required: true, message: '璇疯緭鍏ョ粓姝㈡帓鍙�', trigger: 'blur' }],
+    deviceQty: [{ required: true, message: '璇疯緭鍏ヨ澶囨暟閲�', trigger: 'blur' }],
+    startDeviceNo: [{ required: true, message: '璇疯緭鍏ヨ捣濮嬭澶囧彿', trigger: 'blur' }],
+    endDeviceNo: [{ required: true, message: '璇疯緭鍏ョ粓姝㈣澶囧彿', trigger: 'blur' }],
+    staList: [{ required: true, message: '璇疯緭鍏ョ珯鐐瑰垪琛�', trigger: 'blur' }],
+    typeId: [{ required: true, message: '璇烽�夋嫨搴撳尯绫诲瀷', trigger: 'change' }],
+    beSimilar: [{ required: true, message: '璇烽�夋嫨鐗╂枡鐩镐技', trigger: 'change' }],
+    emptySimilar: [{ required: true, message: '璇烽�夋嫨绌烘澘闈犺繎', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '褰撳墠鎺掑彿',
+      key: 'currentRow',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ綋鍓嶆帓鍙�'
+      }
+    },
+    {
+      label: '璧峰鎺掑彿',
+      key: 'startRow',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ヨ捣濮嬫帓鍙�'
+      }
+    },
+    {
+      label: '缁堟鎺掑彿',
+      key: 'endRow',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ョ粓姝㈡帓鍙�'
+      }
+    },
+    {
+      label: '璁惧鏁伴噺',
+      key: 'deviceQty',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ヨ澶囨暟閲�'
+      }
+    },
+    {
+      label: '璧峰璁惧鍙�',
+      key: 'startDeviceNo',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ヨ捣濮嬭澶囧彿'
+      }
+    },
+    {
+      label: '缁堟璁惧鍙�',
+      key: 'endDeviceNo',
+      type: 'number',
+      props: {
+        controlsPosition: 'right',
+        min: 0,
+        placeholder: '璇疯緭鍏ョ粓姝㈣澶囧彿'
+      }
+    },
+    {
+      label: '绔欑偣鍒楄〃',
+      key: 'staList',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰垪琛紝澶氫釜绔欑偣鍙敤鍒嗗彿鍒嗛殧',
+        clearable: true
+      }
+    },
+    {
+      label: '搴撳尯绫诲瀷',
+      key: 'typeId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.typeOptions || []
+      }
+    },
+    {
+      label: '鐗╂枡鐩镐技',
+      key: 'beSimilar',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐗╂枡鐩镐技',
+        clearable: true,
+        options: getDeviceBindFlagOptions()
+      }
+    },
+    {
+      label: '绌烘澘闈犺繎',
+      key: 'emptySimilar',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨绌烘澘闈犺繎',
+        clearable: true,
+        options: getDeviceBindFlagOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildDeviceBindDialogModel(props.deviceBindData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createDeviceBindFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.deviceBindData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/device-site/deviceSitePage.helpers.js b/rsf-design/src/views/basic-info/device-site/deviceSitePage.helpers.js
new file mode 100644
index 0000000..820bf18
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/deviceSitePage.helpers.js
@@ -0,0 +1,442 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const DEVICE_SITE_REPORT_TITLE = '璺緞绠$悊鎶ヨ〃'
+export const DEVICE_SITE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeArrayValue(value) {
+  if (Array.isArray(value)) {
+    return value.map((item) => normalizeText(item)).filter(Boolean)
+  }
+  const text = normalizeText(value)
+  if (!text) {
+    return []
+  }
+  return text
+    .split(',')
+    .map((item) => item.trim())
+    .filter(Boolean)
+}
+
+function normalizeSelectOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: normalizeText(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || item.value || `閫夐」 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function createDeviceSiteSearchState() {
+  return {
+    condition: '',
+    type: '',
+    site: '',
+    name: '',
+    target: '',
+    label: '',
+    device: '',
+    deviceCode: '',
+    deviceSite: '',
+    channel: '',
+    areaIdStart: '',
+    areaIdEnd: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createDeviceSiteFormState() {
+  return {
+    id: void 0,
+    type: [],
+    site: '',
+    name: '',
+    target: '',
+    label: '',
+    device: '',
+    deviceCode: '',
+    deviceSite: '',
+    channel: void 0,
+    areaIdStart: void 0,
+    areaIdEnd: void 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function createDeviceSiteInitState() {
+  return {
+    flagInit: 0,
+    deviceType: '',
+    typeIds: [],
+    site: '',
+    deviceCode: '',
+    deviceSites: '',
+    target: '',
+    rows: [],
+    channel: '',
+    areaIdStart: void 0,
+    areaIdEnd: void 0,
+    name: '',
+    wcsCode: '',
+    label: ''
+  }
+}
+
+export function getDeviceSitePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getDeviceSiteStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getDeviceSiteStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildDeviceSiteSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    type: normalizeArrayValue(params.type).join(','),
+    site: normalizeText(params.site),
+    name: normalizeText(params.name),
+    target: normalizeText(params.target),
+    label: normalizeText(params.label),
+    device: normalizeText(params.device),
+    deviceCode: normalizeText(params.deviceCode),
+    deviceSite: normalizeText(params.deviceSite),
+    channel:
+      params.channel !== undefined && params.channel !== null && params.channel !== ''
+        ? Number(params.channel)
+        : void 0,
+    areaIdStart:
+      params.areaIdStart !== undefined && params.areaIdStart !== null && params.areaIdStart !== ''
+        ? Number(params.areaIdStart)
+        : void 0,
+    areaIdEnd:
+      params.areaIdEnd !== undefined && params.areaIdEnd !== null && params.areaIdEnd !== ''
+        ? Number(params.areaIdEnd)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildDeviceSitePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeviceSiteSearchParams(params)
+  }
+}
+
+export function buildDeviceSiteSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    type: normalizeArrayValue(formData.type).join(','),
+    site: normalizeText(formData.site) || '',
+    name: normalizeText(formData.name) || '',
+    target: normalizeText(formData.target) || '',
+    label: normalizeText(formData.label) || '',
+    device: normalizeText(formData.device) || '',
+    deviceCode: normalizeText(formData.deviceCode) || '',
+    deviceSite: normalizeText(formData.deviceSite) || '',
+    ...(formData.channel !== void 0 && formData.channel !== null && formData.channel !== ''
+      ? { channel: Number(formData.channel) }
+      : {}),
+    ...(formData.areaIdStart !== void 0 && formData.areaIdStart !== null && formData.areaIdStart !== ''
+      ? { areaIdStart: Number(formData.areaIdStart) }
+      : {}),
+    ...(formData.areaIdEnd !== void 0 && formData.areaIdEnd !== null && formData.areaIdEnd !== ''
+      ? { areaIdEnd: Number(formData.areaIdEnd) }
+      : {}),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildDeviceSiteInitPayload(formData = {}) {
+  return {
+    flagInit:
+      formData.flagInit !== void 0 && formData.flagInit !== null && formData.flagInit !== ''
+        ? Number(formData.flagInit)
+        : 0,
+    deviceType: normalizeText(formData.deviceType) || '',
+    typeIds: Array.isArray(formData.typeIds) ? formData.typeIds.map((item) => normalizeText(item)).filter(Boolean) : [],
+    site: normalizeText(formData.site) || '',
+    deviceCode: normalizeText(formData.deviceCode) || '',
+    deviceSites: normalizeText(formData.deviceSites) || '',
+    target: normalizeText(formData.target) || '',
+    rows: Array.isArray(formData.rows)
+      ? formData.rows
+          .map((row) => ({
+            deviceSite: normalizeText(row?.deviceSite) || '',
+            site: normalizeText(row?.site) || '',
+            target: normalizeText(row?.target) || ''
+          }))
+          .filter((row) => row.deviceSite && row.site && row.target)
+      : [],
+    channel: normalizeText(formData.channel) || '',
+    ...(formData.areaIdStart !== void 0 && formData.areaIdStart !== null && formData.areaIdStart !== ''
+      ? { areaIdStart: Number(formData.areaIdStart) }
+      : {}),
+    ...(formData.areaIdEnd !== void 0 && formData.areaIdEnd !== null && formData.areaIdEnd !== ''
+      ? { areaIdEnd: Number(formData.areaIdEnd) }
+      : {}),
+    name: normalizeText(formData.name) || '',
+    wcsCode: normalizeText(formData.wcsCode) || '',
+    label: normalizeText(formData.label) || ''
+  }
+}
+
+export function buildDeviceSiteDialogModel(record = {}) {
+  return {
+    ...createDeviceSiteFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    type: normalizeArrayValue(record.type ?? record.type$ ?? []),
+    site: normalizeText(record.site || ''),
+    name: normalizeText(record.name || ''),
+    target: normalizeText(record.target || ''),
+    label: normalizeText(record.label || ''),
+    device: normalizeText(record.device || ''),
+    deviceCode: normalizeText(record.deviceCode || ''),
+    deviceSite: normalizeText(record.deviceSite || ''),
+    channel:
+      record.channel !== void 0 && record.channel !== null && record.channel !== ''
+        ? Number(record.channel)
+        : void 0,
+    areaIdStart:
+      record.areaIdStart !== void 0 && record.areaIdStart !== null && record.areaIdStart !== ''
+        ? Number(record.areaIdStart)
+        : void 0,
+    areaIdEnd:
+      record.areaIdEnd !== void 0 && record.areaIdEnd !== null && record.areaIdEnd !== ''
+        ? Number(record.areaIdEnd)
+        : void 0,
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function buildDeviceSiteInitModel(record = {}) {
+  return {
+    ...createDeviceSiteInitState(),
+    flagInit:
+      record.flagInit !== void 0 && record.flagInit !== null && record.flagInit !== ''
+        ? Number(record.flagInit)
+        : 0,
+    deviceType: normalizeText(record.deviceType || ''),
+    typeIds: normalizeArrayValue(record.typeIds ?? record.type ?? []),
+    site: normalizeText(record.site || ''),
+    deviceCode: normalizeText(record.deviceCode || ''),
+    deviceSites: normalizeText(record.deviceSites || ''),
+    target: normalizeText(record.target || ''),
+    rows: Array.isArray(record.rows)
+      ? record.rows.map((row) => ({
+          deviceSite: normalizeText(row?.deviceSite || row?.deviceSiteName || ''),
+          site: normalizeText(row?.site || row?.siteName || ''),
+          target: normalizeText(row?.target || '')
+        }))
+      : [],
+    channel: normalizeText(record.channel || ''),
+    areaIdStart:
+      record.areaIdStart !== void 0 && record.areaIdStart !== null && record.areaIdStart !== ''
+        ? Number(record.areaIdStart)
+        : void 0,
+    areaIdEnd:
+      record.areaIdEnd !== void 0 && record.areaIdEnd !== null && record.areaIdEnd !== ''
+        ? Number(record.areaIdEnd)
+        : void 0,
+    name: normalizeText(record.name || ''),
+    wcsCode: normalizeText(record.wcsCode || ''),
+    label: normalizeText(record.label || '')
+  }
+}
+
+export function normalizeDeviceSiteDetailRecord(record = {}, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel) {
+  const statusMeta = getDeviceSiteStatusMeta(record.statusBool ?? record.status)
+  const typeText = normalizeText(record.type$ || record.typeText || '')
+  const deviceText = normalizeText(record.device$ || record.deviceText || '')
+  const typeItems = normalizeArrayValue(record.type ?? record.type$ ?? '')
+
+  return {
+    ...record,
+    type: typeItems,
+    typeItems,
+    typeText:
+      typeText ||
+      typeItems
+        .map((item) => {
+          if (typeof resolveTypeLabel === 'function') {
+            const resolved = normalizeText(resolveTypeLabel(item))
+            if (resolved) {
+              return resolved
+            }
+          }
+          return item
+        })
+        .filter(Boolean)
+        .join(','),
+    site: normalizeText(record.site || ''),
+    name: normalizeText(record.name || ''),
+    target: normalizeText(record.target || ''),
+    label: normalizeText(record.label || ''),
+    device: normalizeText(record.device || ''),
+    deviceText:
+      deviceText ||
+      (typeof resolveDeviceLabel === 'function' ? normalizeText(resolveDeviceLabel(record.device)) : ''),
+    deviceCode: normalizeText(record.deviceCode || ''),
+    deviceSite: normalizeText(record.deviceSite || ''),
+    channel: record.channel !== void 0 && record.channel !== null && record.channel !== ''
+      ? Number(record.channel)
+      : void 0,
+    areaIdStartText:
+      normalizeText(record.areaIdStart$ || '') ||
+      (typeof resolveAreaLabel === 'function' ? normalizeText(resolveAreaLabel(record.areaIdStart)) : '') ||
+      normalizeText(record.areaIdStart),
+    areaIdEndText:
+      normalizeText(record.areaIdEnd$ || '') ||
+      (typeof resolveAreaLabel === 'function' ? normalizeText(resolveAreaLabel(record.areaIdEnd)) : '') ||
+      normalizeText(record.areaIdEnd),
+    areaIdStart: record.areaIdStart !== void 0 && record.areaIdStart !== null && record.areaIdStart !== ''
+      ? Number(record.areaIdStart)
+      : void 0,
+    areaIdEnd: record.areaIdEnd !== void 0 && record.areaIdEnd !== null && record.areaIdEnd !== ''
+      ? Number(record.areaIdEnd)
+      : void 0,
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeDeviceSiteListRow(record = {}, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel) {
+  return normalizeDeviceSiteDetailRecord(record, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel)
+}
+
+export function buildDeviceSitePrintRows(records = [], resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeDeviceSiteListRow(record, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel))
+}
+
+export function buildDeviceSiteReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = DEVICE_SITE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: DEVICE_SITE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...DEVICE_SITE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function resolveDeviceSiteTypeOptions(records = []) {
+  return normalizeSelectOptions(records)
+}
+
+export function resolveDeviceSiteDeviceOptions(records = []) {
+  return normalizeSelectOptions(records)
+}
+
+export function resolveDeviceSiteAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.value ?? item.id ?? item.areaId ?? item.dictValue
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      const numberValue = Number(value)
+      if (Number.isNaN(numberValue)) {
+        return null
+      }
+      return {
+        value: numberValue,
+        label: normalizeText(item.label || item.name || item.areaName || item.dictLabel || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
diff --git a/rsf-design/src/views/basic-info/device-site/deviceSiteTable.columns.js b/rsf-design/src/views/basic-info/device-site/deviceSiteTable.columns.js
new file mode 100644
index 0000000..939a805
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/deviceSiteTable.columns.js
@@ -0,0 +1,150 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getDeviceSiteStatusMeta } from './deviceSitePage.helpers'
+
+export function createDeviceSiteTableColumns({
+  handleView,
+  handleInit,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (handleInit) {
+    operations.push({ key: 'init', label: '鍒濆鍖�', icon: 'ri:route-line' })
+  }
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'typeText',
+      label: '绔欑偣绫诲瀷',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeText || row.type$ || '--'
+    },
+    {
+      prop: 'site',
+      label: '浣滀笟绔欑偣',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.site || '--'
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.name || '--'
+    },
+    {
+      prop: 'target',
+      label: '鐩爣绔欑偣',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.target || '--'
+    },
+    {
+      prop: 'label',
+      label: '绔欑偣鏍囩',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.label || '--'
+    },
+    {
+      prop: 'deviceText',
+      label: '璁惧绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.deviceText || row.device$ || row.device || '--'
+    },
+    {
+      prop: 'deviceCode',
+      label: '璁惧缂栧彿',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.deviceCode || '--'
+    },
+    {
+      prop: 'deviceSite',
+      label: '璁惧绔欑偣',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.deviceSite || '--'
+    },
+    {
+      prop: 'channel',
+      label: '宸烽亾',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.channel ?? '--'
+    },
+    {
+      prop: 'areaIdStart',
+      label: '婧愬簱鍖�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.areaIdStart ?? '--'
+    },
+    {
+      prop: 'areaIdEnd',
+      label: '鐩爣搴撳尯',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.areaIdEnd ?? '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getDeviceSiteStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 180,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'init') handleInit?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/device-site/index.vue b/rsf-design/src/views/basic-info/device-site/index.vue
new file mode 100644
index 0000000..d6736ae
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/index.vue
@@ -0,0 +1,439 @@
+<template>
+  <div class="device-site-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板璺緞</ElButton>
+            <ElButton v-auth="'add'" @click="openInitDialog()" v-ripple>璺緞鍒濆鍖�</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <DeviceSiteDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :device-site-data="currentDeviceSiteData"
+        :type-options="typeOptions"
+        :device-options="deviceOptions"
+        :area-options="areaOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <DeviceSiteDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+
+      <DeviceSiteInitDialog
+        v-model:visible="initDialogVisible"
+        :initial-data="currentInitData"
+        :type-options="typeOptions"
+        :device-options="deviceOptions"
+        :area-options="areaOptions"
+        :station-options="stationOptions"
+        @submit="handleInitSubmit"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchBasStationPage } from '@/api/bas-station'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    fetchDeleteDeviceSite,
+    fetchDeviceSitePage,
+    fetchExportDeviceSiteReport,
+    fetchGetDeviceSiteDetail,
+    fetchGetDeviceSiteMany,
+    fetchInitDeviceSite,
+    fetchSaveDeviceSite,
+    fetchUpdateDeviceSite
+  } from '@/api/device-site'
+  import DeviceSiteDialog from './modules/device-site-dialog.vue'
+  import DeviceSiteDetailDrawer from './modules/device-site-detail-drawer.vue'
+  import DeviceSiteInitDialog from './modules/device-site-init-dialog.vue'
+  import { createDeviceSiteTableColumns } from './deviceSiteTable.columns'
+  import {
+    DEVICE_SITE_REPORT_STYLE,
+    DEVICE_SITE_REPORT_TITLE,
+    buildDeviceSiteDialogModel,
+    buildDeviceSiteInitModel,
+    buildDeviceSiteInitPayload,
+    buildDeviceSitePageQueryParams,
+    buildDeviceSitePrintRows,
+    buildDeviceSiteReportMeta,
+    buildDeviceSiteSavePayload,
+    buildDeviceSiteSearchParams,
+    createDeviceSiteSearchState,
+    getDeviceSitePaginationKey,
+    normalizeDeviceSiteListRow,
+    resolveDeviceSiteAreaOptions,
+    resolveDeviceSiteDeviceOptions,
+    resolveDeviceSiteTypeOptions
+  } from './deviceSitePage.helpers'
+
+  defineOptions({ name: 'DeviceSite' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createDeviceSiteSearchState())
+  const typeOptions = ref([])
+  const deviceOptions = ref([])
+  const areaOptions = ref([])
+  const stationOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const initDialogVisible = ref(false)
+  const currentInitData = ref(buildDeviceSiteInitModel())
+  let handleDeleteAction = null
+
+  const reportTitle = DEVICE_SITE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildDeviceSiteSearchParams(searchForm.value))
+  const typeLabelMap = computed(
+    () =>
+      new Map(typeOptions.value.map((item) => [String(item.value), item.label]).filter(([value, label]) => value && label))
+  )
+  const deviceLabelMap = computed(
+    () =>
+      new Map(deviceOptions.value.map((item) => [String(item.value), item.label]).filter(([value, label]) => value && label))
+  )
+  const areaLabelMap = computed(
+    () =>
+      new Map(areaOptions.value.map((item) => [String(item.value), item.label]).filter(([value, label]) => value && label))
+  )
+
+  function resolveTypeLabel(value) {
+    return typeLabelMap.value.get(String(value)) || ''
+  }
+
+  function resolveDeviceLabel(value) {
+    return deviceLabelMap.value.get(String(value)) || ''
+  }
+
+  function resolveAreaLabel(value) {
+    return areaLabelMap.value.get(String(value)) || ''
+  }
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ珯鐐�/鍚嶇О/鐩爣/鏍囩' } },
+    {
+      label: '绔欑偣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: { clearable: true, filterable: true, multiple: true, collapseTags: true, options: typeOptions.value }
+    },
+    { label: '浣滀笟绔欑偣', key: 'site', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヤ綔涓氱珯鐐�' } },
+    { label: '鍚嶇О', key: 'name', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ悕绉�' } },
+    { label: '鐩爣绔欑偣', key: 'target', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洰鏍囩珯鐐�' } },
+    { label: '绔欑偣鏍囩', key: 'label', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ珯鐐规爣绛�' } },
+    { label: '璁惧绫诲瀷', key: 'device', type: 'select', props: { clearable: true, filterable: true, options: deviceOptions.value } },
+    { label: '璁惧缂栧彿', key: 'deviceCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ澶囩紪鍙�' } },
+    { label: '璁惧绔欑偣', key: 'deviceSite', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ澶囩珯鐐�' } },
+    { label: '宸烽亾', key: 'channel', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ贩閬�' } },
+    { label: '婧愬簱鍖�', key: 'areaIdStart', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ユ簮搴撳尯' } },
+    { label: '鐩爣搴撳尯', key: 'areaIdEnd', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ョ洰鏍囧簱鍖�' } },
+    { label: '鐘舵��', key: 'status', type: 'select', props: { clearable: true, options: [{ label: '姝e父', value: 1 }, { label: '鍐荤粨', value: 0 }] } },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  function createInitRecordFromRow(row) {
+    return buildDeviceSiteInitModel({
+      deviceType: row.device || '',
+      typeIds: row.type || row.typeText || '',
+      channel: row.channel,
+      areaIdStart: row.areaIdStart,
+      areaIdEnd: row.areaIdEnd,
+      name: row.name,
+      label: row.label,
+      rows: [{ deviceSiteName: row.deviceSite || '', siteName: row.site || '', target: row.target || '' }]
+    })
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetDeviceSiteDetail(row.id), {}, {
+        timeoutMessage: '璺緞璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeDeviceSiteListRow(detail, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇璺緞璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetDeviceSiteDetail(row.id), {}, {
+        timeoutMessage: '璺緞璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇璺緞璇︽儏澶辫触')
+    }
+  }
+
+  function openInitDialog(record = null) {
+    currentInitData.value = record ? createInitRecordFromRow(record) : buildDeviceSiteInitModel()
+    initDialogVisible.value = true
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchDeviceSitePage,
+      apiParams: buildDeviceSitePageQueryParams(searchForm.value),
+      paginationKey: getDeviceSitePaginationKey(),
+      columnsFactory: () =>
+        createDeviceSiteTableColumns({
+          handleView: openDetail,
+          handleInit: hasAuth('add') ? openInitDialog : null,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeDeviceSiteListRow(item, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentDeviceSiteData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildDeviceSiteDialogModel(),
+    buildEditModel: (record) => buildDeviceSiteDialogModel(record),
+    buildSavePayload: (formData) => buildDeviceSiteSavePayload(formData),
+    saveRequest: fetchSaveDeviceSite,
+    updateRequest: fetchUpdateDeviceSite,
+    deleteRequest: fetchDeleteDeviceSite,
+    entityName: '璺緞',
+    resolveRecordLabel: (record) => record?.name || record?.site || record?.deviceSite || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...DEVICE_SITE_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetDeviceSiteMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchDeviceSitePage({
+        ...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: 'device-site.xlsx',
+      requestExport: (payload) =>
+        fetchExportDeviceSiteReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildDeviceSitePrintRows(records, resolveTypeLabel, resolveDeviceLabel, resolveAreaLabel),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildDeviceSiteReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || DEVICE_SITE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildDeviceSiteSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createDeviceSiteSearchState())
+    resetSearchParams()
+  }
+
+  async function handleInitSubmit(formData) {
+    try {
+      await fetchInitDeviceSite(buildDeviceSiteInitPayload(formData))
+      ElMessage.success('鍒濆鍖栨垚鍔�')
+      initDialogVisible.value = false
+      currentInitData.value = buildDeviceSiteInitModel()
+      await refreshData()
+    } catch (error) {
+      ElMessage.error(error?.message || '鍒濆鍖栧け璐�')
+    }
+  }
+
+  function buildSelectOptions(records = [], labelKeys = ['label', 'name', 'stationName']) {
+    if (!Array.isArray(records)) {
+      return []
+    }
+    return records
+      .map((item) => {
+        if (!item || typeof item !== 'object') {
+          return null
+        }
+        const value = item.value ?? item.id ?? item.dictValue
+        if (value === void 0 || value === null || value === '') {
+          return null
+        }
+        const label = labelKeys.map((key) => item[key]).find((value2) => value2 !== void 0 && value2 !== null && String(value2).trim() !== '')
+        return { value: String(value), label: String(label || value).trim() }
+      })
+      .filter(Boolean)
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({ current: 1, pageSize: 200, dictTypeCode: 'sys_task_type', status: 1 }),
+      { records: [] },
+      { timeoutMessage: '绔欑偣绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    typeOptions.value = resolveDeviceSiteTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadDeviceOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({ current: 1, pageSize: 200, dictTypeCode: 'sys_device_type', status: 1 }),
+      { records: [] },
+      { timeoutMessage: '璁惧绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    deviceOptions.value = resolveDeviceSiteDeviceOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadAreaOptions() {
+    const response = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveDeviceSiteAreaOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadStationOptions() {
+    const response = await guardRequestWithMessage(
+      fetchBasStationPage({
+        current: 1,
+        pageSize: 500
+      }, {
+        showErrorMessage: false
+      }),
+      { records: [] },
+      {
+        timeoutMessage: '绔欑偣鍒楄〃鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    stationOptions.value = buildSelectOptions(defaultResponseAdapter(response).records, ['stationName', 'name'])
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadTypeOptions(), loadDeviceOptions(), loadAreaOptions(), loadStationOptions()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/device-site/modules/device-site-detail-drawer.vue b/rsf-design/src/views/basic-info/device-site/modules/device-site-detail-drawer.vue
new file mode 100644
index 0000000..25fc469
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/modules/device-site-detail-drawer.vue
@@ -0,0 +1,62 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璺緞璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="绔欑偣绫诲瀷">{{ detail.typeText || detail.type?.join(',') || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浣滀笟绔欑偣">{{ detail.site || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣绔欑偣">{{ detail.target || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绔欑偣鏍囩">{{ detail.label || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁惧绫诲瀷">{{ detail.deviceText || detail.device || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁惧缂栧彿">{{ detail.deviceCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁惧绔欑偣">{{ detail.deviceSite || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸烽亾">{{ detail.channel ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愬簱鍖�">{{ detail.areaIdStartText || (detail.areaIdStart ?? '--') }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣搴撳尯">{{ detail.areaIdEndText || (detail.areaIdEnd ?? '--') }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/device-site/modules/device-site-dialog.vue b/rsf-design/src/views/basic-info/device-site/modules/device-site-dialog.vue
new file mode 100644
index 0000000..522fb58
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/modules/device-site-dialog.vue
@@ -0,0 +1,137 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="980px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildDeviceSiteDialogModel,
+    createDeviceSiteFormState,
+    getDeviceSiteStatusOptions
+  } from '../deviceSitePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    deviceSiteData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] },
+    deviceOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createDeviceSiteFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫璺緞' : '鏂板璺緞'))
+
+  const rules = computed(() => ({
+    type: [{ type: 'array', required: true, message: '璇烽�夋嫨绔欑偣绫诲瀷', trigger: 'change' }],
+    site: [{ required: true, message: '璇疯緭鍏ヤ綔涓氱珯鐐�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],
+    target: [{ required: true, message: '璇疯緭鍏ョ洰鏍囩珯鐐�', trigger: 'blur' }],
+    label: [{ required: true, message: '璇疯緭鍏ョ珯鐐规爣绛�', trigger: 'blur' }],
+    device: [{ required: true, message: '璇烽�夋嫨璁惧绫诲瀷', trigger: 'change' }],
+    deviceCode: [{ required: true, message: '璇疯緭鍏ヨ澶囩紪鍙�', trigger: 'blur' }],
+    deviceSite: [{ required: true, message: '璇疯緭鍏ヨ澶囩珯鐐�', trigger: 'blur' }],
+    channel: [{ required: true, message: '璇疯緭鍏ュ贩閬�', trigger: 'blur' }],
+    areaIdStart: [{ required: true, message: '璇烽�夋嫨婧愬簱鍖�', trigger: 'change' }],
+    areaIdEnd: [{ required: true, message: '璇烽�夋嫨鐩爣搴撳尯', trigger: 'change' }],
+    status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    { label: '绔欑偣绫诲瀷', key: 'type', type: 'select', span: 24, props: { placeholder: '璇烽�夋嫨绔欑偣绫诲瀷', clearable: true, multiple: true, collapseTags: true, filterable: true, options: props.typeOptions || [] } },
+    { label: '浣滀笟绔欑偣', key: 'site', type: 'input', props: { placeholder: '璇疯緭鍏ヤ綔涓氱珯鐐�', clearable: true } },
+    { label: '鍚嶇О', key: 'name', type: 'input', props: { placeholder: '璇疯緭鍏ュ悕绉�', clearable: true } },
+    { label: '鐩爣绔欑偣', key: 'target', type: 'input', props: { placeholder: '璇疯緭鍏ョ洰鏍囩珯鐐�', clearable: true } },
+    { label: '绔欑偣鏍囩', key: 'label', type: 'input', props: { placeholder: '璇疯緭鍏ョ珯鐐规爣绛�', clearable: true } },
+    { label: '璁惧绫诲瀷', key: 'device', type: 'select', props: { placeholder: '璇烽�夋嫨璁惧绫诲瀷', clearable: true, filterable: true, options: props.deviceOptions || [] } },
+    { label: '璁惧缂栧彿', key: 'deviceCode', type: 'input', props: { placeholder: '璇疯緭鍏ヨ澶囩紪鍙�', clearable: true } },
+    { label: '璁惧绔欑偣', key: 'deviceSite', type: 'input', props: { placeholder: '璇疯緭鍏ヨ澶囩珯鐐�', clearable: true } },
+    { label: '宸烽亾', key: 'channel', type: 'input', props: { placeholder: '璇疯緭鍏ュ贩閬�', clearable: true } },
+    { label: '婧愬簱鍖�', key: 'areaIdStart', type: 'select', props: { placeholder: '璇烽�夋嫨婧愬簱鍖�', clearable: true, filterable: true, options: props.areaOptions || [] } },
+    { label: '鐩爣搴撳尯', key: 'areaIdEnd', type: 'select', props: { placeholder: '璇烽�夋嫨鐩爣搴撳尯', clearable: true, filterable: true, options: props.areaOptions || [] } },
+    { label: '鐘舵��', key: 'status', type: 'select', props: { placeholder: '璇烽�夋嫨鐘舵��', clearable: true, options: getDeviceSiteStatusOptions() } },
+    { label: '澶囨敞', key: 'memo', type: 'input', span: 24, props: { type: 'textarea', rows: 3, placeholder: '璇疯緭鍏ュ娉�', clearable: true } }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildDeviceSiteDialogModel(props.deviceSiteData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createDeviceSiteFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.deviceSiteData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/device-site/modules/device-site-init-dialog.vue b/rsf-design/src/views/basic-info/device-site/modules/device-site-init-dialog.vue
new file mode 100644
index 0000000..0e9de17
--- /dev/null
+++ b/rsf-design/src/views/basic-info/device-site/modules/device-site-init-dialog.vue
@@ -0,0 +1,212 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="1120px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <div class="deviceSite-init-row mt-4">
+      <div class="mb-3 flex items-center justify-between">
+        <div class="text-sm font-medium text-[var(--art-gray-900)]">璺緞琛屽垪琛�</div>
+        <ElButton size="small" @click="addRow">鏂板涓�琛�</ElButton>
+      </div>
+      <ElTable :data="form.rows" border>
+        <ElTableColumn label="璁惧绔欑偣" min-width="220">
+          <template #default="{ row }">
+            <ElSelect v-model="row.deviceSite" class="w-full" filterable clearable placeholder="璇烽�夋嫨璁惧绔欑偣">
+              <ElOption v-for="item in stationOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </ElSelect>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn label="浣滀笟绔欑偣" min-width="220">
+          <template #default="{ row }">
+            <ElSelect v-model="row.site" class="w-full" filterable clearable placeholder="璇烽�夋嫨浣滀笟绔欑偣">
+              <ElOption v-for="item in stationOptions" :key="item.value" :label="item.label" :value="item.value" />
+            </ElSelect>
+          </template>
+        </ElTableColumn>
+        <ElTableColumn label="鐩爣绔欑偣" min-width="220">
+          <template #default="{ row }">
+            <ElInput v-model="row.target" clearable placeholder="璇疯緭鍏ョ洰鏍囩珯鐐�" />
+          </template>
+        </ElTableColumn>
+        <ElTableColumn label="鎿嶄綔" width="100" align="center">
+          <template #default="{ $index }">
+            <ElButton link type="danger" :disabled="form.rows.length <= 1" @click="removeRow($index)">鍒犻櫎</ElButton>
+          </template>
+        </ElTableColumn>
+      </ElTable>
+      <div class="mt-2 text-xs text-[var(--art-gray-500)]">
+        姣忚琛ㄧず涓�缁勮澶囩珯鐐广�佷綔涓氱珯鐐广�佺洰鏍囩珯鐐广��
+      </div>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildDeviceSiteInitModel, createDeviceSiteInitState } from '../deviceSitePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    initialData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] },
+    deviceOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    stationOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createDeviceSiteInitState())
+
+  const dialogTitle = computed(() => '璺緞鍒濆鍖�')
+
+  const rules = computed(() => ({
+    typeIds: [{ type: 'array', required: true, message: '璇烽�夋嫨浣滀笟绫诲瀷', trigger: 'change' }],
+    deviceType: [{ required: true, message: '璇烽�夋嫨璁惧绫诲瀷', trigger: 'change' }],
+    channel: [{ required: true, message: '璇疯緭鍏ュ贩閬�', trigger: 'blur' }],
+    areaIdStart: [{ required: true, message: '璇烽�夋嫨婧愬簱鍖�', trigger: 'change' }],
+    areaIdEnd: [{ required: true, message: '璇烽�夋嫨鐩爣搴撳尯', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    { label: '鏄惁鍒濆鍖�', key: 'flagInit', type: 'select', props: { clearable: false, options: [{ label: '鍚�', value: 0 }, { label: '鏄�', value: 1 }] } },
+    { label: '璁惧绫诲瀷', key: 'deviceType', type: 'select', props: { placeholder: '璇烽�夋嫨璁惧绫诲瀷', clearable: true, filterable: true, options: props.deviceOptions || [] } },
+    { label: '浣滀笟绫诲瀷', key: 'typeIds', type: 'select', span: 24, props: { placeholder: '璇烽�夋嫨浣滀笟绫诲瀷', clearable: true, multiple: true, collapseTags: true, filterable: true, options: props.typeOptions || [] } },
+    { label: '宸烽亾', key: 'channel', type: 'input', props: { placeholder: '璇疯緭鍏ュ贩閬擄紝澶氫釜璇风敤鑻辨枃閫楀彿鍒嗛殧', clearable: true } },
+    { label: '婧愬簱鍖�', key: 'areaIdStart', type: 'select', props: { placeholder: '璇烽�夋嫨婧愬簱鍖�', clearable: true, filterable: true, options: props.areaOptions || [] } },
+    { label: '鐩爣搴撳尯', key: 'areaIdEnd', type: 'select', props: { placeholder: '璇烽�夋嫨鐩爣搴撳尯', clearable: true, filterable: true, options: props.areaOptions || [] } },
+    { label: '鍚嶇О', key: 'name', type: 'input', props: { placeholder: '璇疯緭鍏ュ悕绉�', clearable: true } },
+    { label: '绔欑偣鏍囩', key: 'label', type: 'input', props: { placeholder: '璇疯緭鍏ョ珯鐐规爣绛�', clearable: true } }
+  ])
+
+  function resolveStationValue(rawValue) {
+    const text = String(rawValue ?? '').trim()
+    if (!text) {
+      return ''
+    }
+    const exact = props.stationOptions.find((item) => String(item.value) === text)
+    if (exact) {
+      return exact.value
+    }
+    const matched = props.stationOptions.find((item) => item.label === text)
+    return matched ? matched.value : text
+  }
+
+  function normalizeRows(rows = []) {
+    return Array.isArray(rows)
+      ? rows
+          .map((row) => ({
+            deviceSite: resolveStationValue(row?.deviceSite || row?.deviceSiteName || ''),
+            site: resolveStationValue(row?.site || row?.siteName || ''),
+            target: String(row?.target || '').trim()
+          }))
+          .filter((row) => row.deviceSite || row.site || row.target)
+      : []
+  }
+
+  function loadFormData() {
+    const resolved = buildDeviceSiteInitModel(props.initialData)
+    Object.assign(form, {
+      ...resolved,
+      rows: normalizeRows(resolved.rows.length ? resolved.rows : [{ deviceSite: '', site: '', target: '' }])
+    })
+  }
+
+  function resetForm() {
+    Object.assign(form, createDeviceSiteInitState())
+    form.rows = [{ deviceSite: '', site: '', target: '' }]
+    formRef.value?.clearValidate?.()
+  }
+
+  function addRow() {
+    form.rows.push({ deviceSite: '', site: '', target: '' })
+  }
+
+  function removeRow(index) {
+    if (form.rows.length <= 1) {
+      return
+    }
+    form.rows.splice(index, 1)
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      const validRows = normalizeRows(form.rows)
+      if (!validRows.length) {
+        ElMessage.error('璇疯嚦灏戝~鍐欎竴琛屽畬鏁寸殑璺緞琛�')
+        return
+      }
+      emit('submit', { ...form, rows: validRows })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    async (visible) => {
+      if (visible) {
+        loadFormData()
+        await nextTick()
+        formRef.value?.clearValidate?.()
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.initialData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+
+  watch(
+    () => props.stationOptions,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat-rela/index.vue b/rsf-design/src/views/basic-info/loc-area-mat-rela/index.vue
new file mode 100644
index 0000000..7194e06
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat-rela/index.vue
@@ -0,0 +1,471 @@
+<template>
+  <div class="loc-area-mat-rela-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳尯鐗╂枡鍏崇郴</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocAreaMatRelaDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :rela-data="currentRelaData"
+        :area-mat-options="areaMatOptions"
+        :area-options="areaOptions"
+        :group-options="groupOptions"
+        :matnr-options="matnrOptions"
+        :loc-type-options="locTypeOptions"
+        :loc-options="locOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocAreaMatRelaDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchWarehouseAreasList } from '@/api/loc'
+  import { fetchLocAreaMatList } from '@/api/loc-area-mat'
+  import { fetchLocPage, fetchLocTypeList } from '@/api/loc'
+  import { fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat'
+  import {
+    fetchDeleteLocAreaMatRela,
+    fetchExportLocAreaMatRelaReport,
+    fetchGetLocAreaMatRelaDetail,
+    fetchGetLocAreaMatRelaMany,
+    fetchLocAreaMatRelaPage,
+    fetchSaveLocAreaMatRela,
+    fetchUpdateLocAreaMatRela
+  } from '@/api/loc-area-mat-rela'
+  import LocAreaMatRelaDialog from './modules/loc-area-mat-rela-dialog.vue'
+  import LocAreaMatRelaDetailDrawer from './modules/loc-area-mat-rela-detail-drawer.vue'
+  import { createLocAreaMatRelaTableColumns } from './locAreaMatRelaTable.columns'
+  import {
+    LOC_AREA_MAT_RELA_REPORT_STYLE,
+    LOC_AREA_MAT_RELA_REPORT_TITLE,
+    buildLocAreaMatRelaDialogModel,
+    buildLocAreaMatRelaPageQueryParams,
+    buildLocAreaMatRelaPrintRows,
+    buildLocAreaMatRelaReportMeta,
+    buildLocAreaMatRelaSavePayload,
+    buildLocAreaMatRelaSearchParams,
+    createLocAreaMatRelaLookupMaps,
+    createLocAreaMatRelaSearchState,
+    getLocAreaMatRelaPaginationKey,
+    getLocAreaMatRelaStatusOptions,
+    normalizeLocAreaMatRelaDetailRecord,
+    normalizeLocAreaMatRelaListRow,
+    resolveLocAreaMatRelaAreaMatOptions,
+    resolveLocAreaMatRelaAreaOptions,
+    resolveLocAreaMatRelaGroupOptions,
+    resolveLocAreaMatRelaLocOptions,
+    resolveLocAreaMatRelaLocTypeOptions,
+    resolveLocAreaMatRelaMatnrOptions
+  } from './locAreaMatRelaPage.helpers'
+
+  defineOptions({ name: 'LocAreaMatRela' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createLocAreaMatRelaSearchState())
+  const areaMatOptions = ref([])
+  const areaOptions = ref([])
+  const groupOptions = ref([])
+  const matnrOptions = ref([])
+  const locTypeOptions = ref([])
+  const locOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = LOC_AREA_MAT_RELA_REPORT_TITLE
+  const reportQueryParams = computed(() => buildLocAreaMatRelaSearchParams(searchForm.value))
+  const lookupMaps = computed(() =>
+    createLocAreaMatRelaLookupMaps({
+      areaMatOptions: areaMatOptions.value,
+      areaOptions: areaOptions.value,
+      matnrOptions: matnrOptions.value,
+      groupOptions: groupOptions.value,
+      locTypeOptions: locTypeOptions.value,
+      locOptions: locOptions.value
+    })
+  )
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙�/澶囨敞/鍏崇郴淇℃伅'
+      }
+    },
+    {
+      label: '涓诲崟',
+      key: 'areaMatId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaMatOptions.value
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaOptions.value
+      }
+    },
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙�'
+      }
+    },
+    {
+      label: '鐗╂枡',
+      key: 'matnrId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: matnrOptions.value
+      }
+    },
+    {
+      label: '鐗╂枡鍒嗙粍',
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: groupOptions.value,
+        props: {
+          label: 'displayLabel',
+          value: 'id',
+          children: 'children'
+        },
+        clearable: true,
+        checkStrictly: true,
+        defaultExpandAll: true,
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙粍'
+      }
+    },
+    {
+      label: '搴撲綅绫诲瀷',
+      key: 'locTypeId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: locTypeOptions.value
+      }
+    },
+    {
+      label: '搴撲綅',
+      key: 'locId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: locOptions.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocAreaMatRelaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaMatRelaDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯鐗╂枡鍏崇郴璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeLocAreaMatRelaDetailRecord(detail, lookupMaps.value)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鐗╂枡鍏崇郴璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaMatRelaDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯鐗╂枡鍏崇郴璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鐗╂枡鍏崇郴璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchLocAreaMatRelaPage,
+      apiParams: buildLocAreaMatRelaPageQueryParams(searchForm.value),
+      paginationKey: getLocAreaMatRelaPaginationKey(),
+      columnsFactory: () =>
+        createLocAreaMatRelaTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocAreaMatRelaListRow(item, lookupMaps.value))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentRelaData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildLocAreaMatRelaDialogModel(),
+    buildEditModel: (record) => buildLocAreaMatRelaDialogModel(record),
+    buildSavePayload: (formData) => buildLocAreaMatRelaSavePayload(formData),
+    saveRequest: fetchSaveLocAreaMatRela,
+    updateRequest: fetchUpdateLocAreaMatRela,
+    deleteRequest: fetchDeleteLocAreaMatRela,
+    entityName: '搴撳尯鐗╂枡鍏崇郴',
+    resolveRecordLabel: (record) => record?.areaMatIdText || record?.code || record?.relationTypeText || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response =
+      Array.isArray(payload?.ids) && payload.ids.length > 0
+        ? await fetchGetLocAreaMatRelaMany(payload.ids)
+        : await fetchLocAreaMatRelaPage({
+            ...reportQueryParams.value,
+            current: 1,
+            pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+          })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-area-mat-rela.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocAreaMatRelaReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocAreaMatRelaPrintRows(records, lookupMaps.value),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocAreaMatRelaReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || LOC_AREA_MAT_RELA_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocAreaMatRelaSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocAreaMatRelaSearchState())
+    resetSearchParams()
+  }
+
+  async function loadAreaMatOptions() {
+    const records = await guardRequestWithMessage(fetchLocAreaMatList(), [], {
+      timeoutMessage: '涓诲崟閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaMatOptions.value = resolveLocAreaMatRelaAreaMatOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveLocAreaMatRelaAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadGroupOptions() {
+    const records = await guardRequestWithMessage(fetchMatnrGroupTree({}), [], {
+      timeoutMessage: '鐗╂枡鍒嗙粍閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    groupOptions.value = resolveLocAreaMatRelaGroupOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadMatnrOptions() {
+    const response = await guardRequestWithMessage(
+      fetchMatnrPage({ current: 1, pageSize: 200 }),
+      { records: [] },
+      { timeoutMessage: '鐗╂枡閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    matnrOptions.value = resolveLocAreaMatRelaMatnrOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadLocTypeOptions() {
+    const records = await guardRequestWithMessage(fetchLocTypeList(), [], {
+      timeoutMessage: '搴撲綅绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    locTypeOptions.value = resolveLocAreaMatRelaLocTypeOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadLocOptions() {
+    const response = await guardRequestWithMessage(
+      fetchLocPage({ current: 1, pageSize: 200 }),
+      { records: [] },
+      { timeoutMessage: '搴撲綅閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    locOptions.value = resolveLocAreaMatRelaLocOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([
+      loadAreaMatOptions(),
+      loadAreaOptions(),
+      loadGroupOptions(),
+      loadMatnrOptions(),
+      loadLocTypeOptions(),
+      loadLocOptions(),
+      getData()
+    ])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaPage.helpers.js b/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaPage.helpers.js
new file mode 100644
index 0000000..7a26c7f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaPage.helpers.js
@@ -0,0 +1,389 @@
+import {
+  normalizeLocAreaMatGroupTreeOptions,
+  resolveLocAreaMatAreaOptions,
+  resolveLocAreaMatLocOptions,
+  resolveLocAreaMatLocTypeOptions,
+  resolveLocAreaMatMatnrOptions
+} from '../loc-area-mat/locAreaMatPage.helpers.js'
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const LOC_AREA_MAT_RELA_REPORT_TITLE = '搴撳尯鐗╂枡鍏崇郴鎶ヨ〃'
+export const LOC_AREA_MAT_RELA_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeOptionId(item = {}) {
+  const value = item.id ?? item.value
+  if (value === void 0 || value === null || value === '') {
+    return void 0
+  }
+  return normalizeNumber(value)
+}
+
+function normalizeOptionText(item = {}, fallbackPrefix = '') {
+  return (
+    normalizeText(
+      item.displayLabel ||
+        item.name ||
+        item.code ||
+        item.label ||
+        item.codeText ||
+        `${fallbackPrefix}${item.id ?? item.value ?? ''}`
+    ) || '--'
+  )
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+function createLookupMap(options = [], childrenKey = 'children') {
+  const map = {}
+  const walk = (items = []) => {
+    items.forEach((item) => {
+      if (!item || typeof item !== 'object') return
+      const value = normalizeOptionId(item)
+      if (value !== void 0) {
+        map[String(value)] = normalizeOptionText(item)
+      }
+      const children = item[childrenKey]
+      if (Array.isArray(children) && children.length > 0) {
+        walk(children)
+      }
+    })
+  }
+  walk(Array.isArray(options) ? options : [])
+  return map
+}
+
+function resolveMapLabel(record, field, map, fallbackKeys = []) {
+  const rawValue = record?.[field]
+  const candidates = [
+    record?.[`${field}$`],
+    record?.[`${field}Text`],
+    ...fallbackKeys.map((key) => record?.[key])
+  ]
+  const mapped = rawValue !== void 0 && rawValue !== null && rawValue !== '' ? map[String(rawValue)] : void 0
+  return normalizeText(candidates.find((item) => normalizeText(item)) || mapped || '')
+}
+
+function resolveRelationTypeText(record = {}) {
+  const hasMatnr = record.matnrId !== void 0 || record.matnrId$ !== void 0 || record.matnrIdText !== void 0
+  const hasLoc = record.locId !== void 0 || record.locId$ !== void 0 || record.locIdText !== void 0
+  if (hasMatnr && hasLoc) return '娣峰悎鍏崇郴'
+  if (hasMatnr) return '鐗╂枡鍏崇郴'
+  if (hasLoc) return '搴撲綅鍏崇郴'
+  return '鏈垎绫�'
+}
+
+export function createLocAreaMatRelaSearchState() {
+  return {
+    condition: '',
+    areaMatId: '',
+    areaId: '',
+    code: '',
+    matnrId: '',
+    groupId: '',
+    locTypeId: '',
+    locId: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createLocAreaMatRelaFormState() {
+  return {
+    id: void 0,
+    areaMatId: '',
+    areaId: '',
+    code: '',
+    matnrId: '',
+    groupId: '',
+    locTypeId: '',
+    locId: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getLocAreaMatRelaStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getLocAreaMatRelaStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function getLocAreaMatRelaPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildLocAreaMatRelaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    areaMatId:
+      params.areaMatId !== undefined && params.areaMatId !== null && params.areaMatId !== ''
+        ? normalizeNumber(params.areaMatId)
+        : void 0,
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? normalizeNumber(params.areaId)
+        : void 0,
+    code: normalizeText(params.code),
+    matnrId:
+      params.matnrId !== undefined && params.matnrId !== null && params.matnrId !== ''
+        ? normalizeNumber(params.matnrId)
+        : void 0,
+    groupId:
+      params.groupId !== undefined && params.groupId !== null && params.groupId !== ''
+        ? normalizeNumber(params.groupId)
+        : void 0,
+    locTypeId:
+      params.locTypeId !== undefined && params.locTypeId !== null && params.locTypeId !== ''
+        ? normalizeNumber(params.locTypeId)
+        : void 0,
+    locId:
+      params.locId !== undefined && params.locId !== null && params.locId !== ''
+        ? normalizeNumber(params.locId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaMatRelaPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocAreaMatRelaSearchParams(params)
+  }
+}
+
+export function buildLocAreaMatRelaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(formData.areaMatId !== void 0 && formData.areaMatId !== null && formData.areaMatId !== ''
+      ? { areaMatId: normalizeNumber(formData.areaMatId) }
+      : {}),
+    ...(formData.areaId !== void 0 && formData.areaId !== null && formData.areaId !== ''
+      ? { areaId: normalizeNumber(formData.areaId) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    ...(formData.matnrId !== void 0 && formData.matnrId !== null && formData.matnrId !== ''
+      ? { matnrId: normalizeNumber(formData.matnrId) }
+      : {}),
+    ...(formData.groupId !== void 0 && formData.groupId !== null && formData.groupId !== ''
+      ? { groupId: normalizeNumber(formData.groupId) }
+      : {}),
+    ...(formData.locTypeId !== void 0 && formData.locTypeId !== null && formData.locTypeId !== ''
+      ? { locTypeId: normalizeNumber(formData.locTypeId) }
+      : {}),
+    ...(formData.locId !== void 0 && formData.locId !== null && formData.locId !== ''
+      ? { locId: normalizeNumber(formData.locId) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocAreaMatRelaDialogModel(record = {}) {
+  return {
+    ...createLocAreaMatRelaFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: normalizeNumber(record.id) } : {}),
+    areaMatId:
+      record.areaMatId !== undefined && record.areaMatId !== null && record.areaMatId !== ''
+        ? normalizeNumber(record.areaMatId)
+        : '',
+    areaId:
+      record.areaId !== undefined && record.areaId !== null && record.areaId !== ''
+        ? normalizeNumber(record.areaId)
+        : '',
+    code: normalizeText(record.code || ''),
+    matnrId:
+      record.matnrId !== undefined && record.matnrId !== null && record.matnrId !== ''
+        ? normalizeNumber(record.matnrId)
+        : '',
+    groupId:
+      record.groupId !== undefined && record.groupId !== null && record.groupId !== ''
+        ? normalizeNumber(record.groupId)
+        : '',
+    locTypeId:
+      record.locTypeId !== undefined && record.locTypeId !== null && record.locTypeId !== ''
+        ? normalizeNumber(record.locTypeId)
+        : '',
+    locId:
+      record.locId !== undefined && record.locId !== null && record.locId !== ''
+        ? normalizeNumber(record.locId)
+        : '',
+    status: record.status !== undefined && record.status !== null ? normalizeNumber(record.status, 1) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeLocAreaMatRelaListRow(record = {}, lookups = {}) {
+  const areaMatMap = lookups.areaMatMap || createLookupMap(lookups.areaMatOptions || [])
+  const areaMap = lookups.areaMap || createLookupMap(lookups.areaOptions || [])
+  const matnrMap = lookups.matnrMap || createLookupMap(lookups.matnrOptions || [])
+  const groupMap = lookups.groupMap || createLookupMap(lookups.groupOptions || [])
+  const locTypeMap = lookups.locTypeMap || createLookupMap(lookups.locTypeOptions || [])
+  const locMap = lookups.locMap || createLookupMap(lookups.locOptions || [])
+
+  const statusMeta = getLocAreaMatRelaStatusMeta(record.statusBool ?? record.status)
+
+  return {
+    ...record,
+    areaMatIdText: resolveMapLabel(record, 'areaMatId', areaMatMap, ['areaMatCode', 'areaMatName']),
+    areaIdText: resolveMapLabel(record, 'areaId', areaMap, ['areaName']),
+    code: normalizeText(record.code || ''),
+    matnrIdText: resolveMapLabel(record, 'matnrId', matnrMap, ['matnrCode', 'matnrName']),
+    groupIdText: resolveMapLabel(record, 'groupId', groupMap, ['groupName']),
+    locTypeIdText: resolveMapLabel(record, 'locTypeId', locTypeMap, ['locTypeName', 'typeName']),
+    locIdText: resolveMapLabel(record, 'locId', locMap, ['locCode', 'locName']),
+    relationTypeText: resolveRelationTypeText(record),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeLocAreaMatRelaDetailRecord(record = {}, lookups = {}) {
+  return normalizeLocAreaMatRelaListRow(record, lookups)
+}
+
+export function buildLocAreaMatRelaPrintRows(records = [], lookups = {}) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocAreaMatRelaListRow(record, lookups))
+}
+
+export function buildLocAreaMatRelaReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_AREA_MAT_RELA_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_AREA_MAT_RELA_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_AREA_MAT_RELA_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function createLocAreaMatRelaLookupMaps({
+  areaMatOptions = [],
+  areaOptions = [],
+  matnrOptions = [],
+  groupOptions = [],
+  locTypeOptions = [],
+  locOptions = []
+} = {}) {
+  return {
+    areaMatMap: createLookupMap(areaMatOptions),
+    areaMap: createLookupMap(areaOptions),
+    matnrMap: createLookupMap(matnrOptions),
+    groupMap: createLookupMap(groupOptions),
+    locTypeMap: createLookupMap(locTypeOptions),
+    locMap: createLookupMap(locOptions)
+  }
+}
+
+export function resolveLocAreaMatRelaAreaMatOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      const label =
+        [
+          normalizeText(item.code || item.areaMatCode || ''),
+          normalizeText(item.depict || item.areaMatName || ''),
+          normalizeText(item.warehouseName || item.warehouseId$ || ''),
+          normalizeText(item.areaName || item.areaId$ || '')
+        ]
+          .filter(Boolean)
+          .join(' 路 ') || `涓诲崟 ${value}`
+      return {
+        value,
+        label,
+        areaId:
+          item.areaId !== undefined && item.areaId !== null && item.areaId !== ''
+            ? normalizeNumber(item.areaId)
+            : void 0
+      }
+    })
+    .filter(Boolean)
+}
+
+export {
+  normalizeLocAreaMatGroupTreeOptions as resolveLocAreaMatRelaGroupOptions,
+  resolveLocAreaMatAreaOptions as resolveLocAreaMatRelaAreaOptions,
+  resolveLocAreaMatLocOptions as resolveLocAreaMatRelaLocOptions,
+  resolveLocAreaMatLocTypeOptions as resolveLocAreaMatRelaLocTypeOptions,
+  resolveLocAreaMatMatnrOptions as resolveLocAreaMatRelaMatnrOptions
+}
diff --git a/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaTable.columns.js b/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaTable.columns.js
new file mode 100644
index 0000000..f0f2348
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat-rela/locAreaMatRelaTable.columns.js
@@ -0,0 +1,138 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getLocAreaMatRelaStatusMeta } from './locAreaMatRelaPage.helpers'
+
+export function createLocAreaMatRelaTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'areaMatIdText',
+      label: '涓诲崟',
+      minWidth: 200,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaMatIdText || row.areaMatId || '--'
+    },
+    {
+      prop: 'areaIdText',
+      label: '搴撳尯',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaIdText || '--'
+    },
+    {
+      prop: 'code',
+      label: '缂栧彿',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'matnrIdText',
+      label: '鐗╂枡',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrIdText || '--'
+    },
+    {
+      prop: 'groupIdText',
+      label: '鐗╂枡鍒嗙粍',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.groupIdText || '--'
+    },
+    {
+      prop: 'locTypeIdText',
+      label: '搴撲綅绫诲瀷',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locTypeIdText || '--'
+    },
+    {
+      prop: 'locIdText',
+      label: '搴撲綅',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locIdText || '--'
+    },
+    {
+      prop: 'relationTypeText',
+      label: '鍏崇郴绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.relationTypeText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getLocAreaMatRelaStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-detail-drawer.vue b/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-detail-drawer.vue
new file mode 100644
index 0000000..43bc37f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-detail-drawer.vue
@@ -0,0 +1,60 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳尯鐗╂枡鍏崇郴璇︽儏"
+    size="1120px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍏崇郴淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="涓诲崟">{{ detail.areaMatIdText || detail.areaMatId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯">{{ detail.areaIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缂栧彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡">{{ detail.matnrIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍒嗙粍">{{ detail.groupIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅绫诲瀷">{{ detail.locTypeIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-dialog.vue b/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-dialog.vue
new file mode 100644
index 0000000..73cdad1
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat-rela/modules/loc-area-mat-rela-dialog.vue
@@ -0,0 +1,261 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="980px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaMatRelaDialogModel,
+    createLocAreaMatRelaFormState
+  } from '../locAreaMatRelaPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    relaData: { type: Object, default: () => ({}) },
+    areaMatOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    groupOptions: { type: Array, default: () => [] },
+    matnrOptions: { type: Array, default: () => [] },
+    locTypeOptions: { type: Array, default: () => [] },
+    locOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+
+  const formRef = ref()
+  const form = reactive(createLocAreaMatRelaFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳尯鐗╂枡鍏崇郴' : '鏂板搴撳尯鐗╂枡鍏崇郴'))
+
+  const selectedAreaId = computed(() =>
+    form.areaId !== void 0 && form.areaId !== null && form.areaId !== ''
+      ? Number(form.areaId)
+      : void 0
+  )
+  const selectedGroupId = computed(() =>
+    form.groupId !== void 0 && form.groupId !== null && form.groupId !== ''
+      ? Number(form.groupId)
+      : void 0
+  )
+
+  const filteredAreaMatOptions = computed(() => {
+    if (selectedAreaId.value === void 0) {
+      return props.areaMatOptions
+    }
+    return props.areaMatOptions.filter(
+      (item) => item.areaId === void 0 || Number(item.areaId) === selectedAreaId.value
+    )
+  })
+
+  const filteredMatnrOptions = computed(() => {
+    if (selectedGroupId.value === void 0) {
+      return props.matnrOptions
+    }
+    return props.matnrOptions.filter(
+      (item) => item.groupId === void 0 || Number(item.groupId) === selectedGroupId.value
+    )
+  })
+
+  const filteredLocOptions = computed(() => {
+    if (selectedAreaId.value === void 0) {
+      return props.locOptions
+    }
+    return props.locOptions.filter(
+      (item) => item.areaId === void 0 || Number(item.areaId) === selectedAreaId.value
+    )
+  })
+
+  const rules = computed(() => ({
+    areaMatId: [{ required: true, message: '璇烽�夋嫨涓诲崟', trigger: 'change' }],
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '涓诲崟',
+      key: 'areaMatId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨涓诲崟',
+        clearable: true,
+        filterable: true,
+        options: filteredAreaMatOptions.value
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions
+      }
+    },
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ紪鍙�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐗╂枡',
+      key: 'matnrId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+        clearable: true,
+        filterable: true,
+        options: filteredMatnrOptions.value
+      }
+    },
+    {
+      label: '鐗╂枡鍒嗙粍',
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: props.groupOptions,
+        props: {
+          label: 'displayLabel',
+          value: 'id',
+          children: 'children'
+        },
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙粍',
+        clearable: true,
+        checkStrictly: true,
+        defaultExpandAll: true
+      }
+    },
+    {
+      label: '搴撲綅绫诲瀷',
+      key: 'locTypeId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撲綅绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.locTypeOptions
+      }
+    },
+    {
+      label: '搴撲綅',
+      key: 'locId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+        clearable: true,
+        filterable: true,
+        options: filteredLocOptions.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaMatRelaDialogModel(props.relaData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaMatRelaFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.relaData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/index.vue b/rsf-design/src/views/basic-info/loc-area-mat/index.vue
new file mode 100644
index 0000000..d81b1d6
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/index.vue
@@ -0,0 +1,365 @@
+<template>
+  <div class="loc-area-mat-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳尯鐗╂枡</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocAreaMatDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :loc-area-mat-data="currentLocAreaMatData"
+        :warehouse-options="warehouseOptions"
+        :area-options="areaOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocAreaMatDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchWarehouseAreasList, fetchWarehouseList } from '@/api/loc'
+  import {
+    fetchDeleteLocAreaMat,
+    fetchExportLocAreaMatReport,
+    fetchGetLocAreaMatDetail,
+    fetchGetLocAreaMatMany,
+    fetchLocAreaMatPage,
+    fetchSaveLocAreaMat,
+    fetchUpdateLocAreaMat
+  } from '@/api/loc-area-mat'
+  import LocAreaMatDialog from './modules/loc-area-mat-dialog.vue'
+  import LocAreaMatDetailDrawer from './modules/loc-area-mat-detail-drawer.vue'
+  import { createLocAreaMatTableColumns } from './locAreaMatTable.columns'
+  import {
+    LOC_AREA_MAT_REPORT_STYLE,
+    LOC_AREA_MAT_REPORT_TITLE,
+    buildLocAreaMatDialogModel,
+    buildLocAreaMatPageQueryParams,
+    buildLocAreaMatPrintRows,
+    buildLocAreaMatReportMeta,
+    buildLocAreaMatSavePayload,
+    buildLocAreaMatSearchParams,
+    createLocAreaMatSearchState,
+    getLocAreaMatPaginationKey,
+    getLocAreaMatStatusOptions,
+    normalizeLocAreaMatListRow,
+    resolveLocAreaMatAreaOptions,
+    resolveLocAreaMatWarehouseOptions
+  } from './locAreaMatPage.helpers'
+
+  defineOptions({ name: 'LocAreaMat' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createLocAreaMatSearchState())
+  const warehouseOptions = ref([])
+  const areaOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = LOC_AREA_MAT_REPORT_TITLE
+  const reportQueryParams = computed(() => buildLocAreaMatSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ�昏緫缂栧彿/鎻忚堪/澶囨敞'
+      }
+    },
+    {
+      label: '閫昏緫缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ�昏緫缂栧彿'
+      }
+    },
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: warehouseOptions.value
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaOptions.value
+      }
+    },
+    {
+      label: '閫昏緫鎻忚堪',
+      key: 'depict',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ�昏緫鎻忚堪'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocAreaMatStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaMatDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯鐗╂枡璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeLocAreaMatListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鐗╂枡璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaMatDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯鐗╂枡璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鐗╂枡璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchLocAreaMatPage,
+      apiParams: buildLocAreaMatPageQueryParams(searchForm.value),
+      paginationKey: getLocAreaMatPaginationKey(),
+      columnsFactory: () =>
+        createLocAreaMatTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocAreaMatListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentLocAreaMatData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildLocAreaMatDialogModel(),
+    buildEditModel: (record) => buildLocAreaMatDialogModel(record),
+    buildSavePayload: (formData) => buildLocAreaMatSavePayload(formData),
+    saveRequest: fetchSaveLocAreaMat,
+    updateRequest: fetchUpdateLocAreaMat,
+    deleteRequest: fetchDeleteLocAreaMat,
+    entityName: '搴撳尯鐗╂枡',
+    resolveRecordLabel: (record) => record?.code || record?.depict || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response = Array.isArray(payload?.ids) && payload.ids.length > 0
+      ? await fetchGetLocAreaMatMany(payload.ids)
+      : await fetchLocAreaMatPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-area-mat.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocAreaMatReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocAreaMatPrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocAreaMatReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || LOC_AREA_MAT_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocAreaMatSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocAreaMatSearchState())
+    resetSearchParams()
+  }
+
+  async function loadWarehouseOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
+      timeoutMessage: '浠撳簱閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    warehouseOptions.value = resolveLocAreaMatWarehouseOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveLocAreaMatAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadWarehouseOptions(), loadAreaOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatPage.helpers.js b/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatPage.helpers.js
new file mode 100644
index 0000000..83ebf93
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatPage.helpers.js
@@ -0,0 +1,449 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const LOC_AREA_MAT_REPORT_TITLE = '搴撳尯鐗╂枡鎶ヨ〃'
+export const LOC_AREA_MAT_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+export function createLocAreaMatSearchState() {
+  return {
+    condition: '',
+    code: '',
+    warehouseId: '',
+    areaId: '',
+    depict: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createLocAreaMatFormState() {
+  return {
+    id: void 0,
+    code: '',
+    warehouseId: '',
+    areaId: '',
+    depict: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function createLocAreaMatRelationBindMatnrState(areaMatId = void 0) {
+  return {
+    areaMatId,
+    warehouseId: '',
+    areaId: '',
+    groupId: '',
+    matnrId: [],
+    typeId: []
+  }
+}
+
+export function createLocAreaMatRelationBindLocState(areaMatId = void 0) {
+  return {
+    areaMatId,
+    warehouseId: '',
+    areaId: '',
+    groupId: '',
+    locId: [],
+    typeId: []
+  }
+}
+
+export function buildLocAreaMatRelationBindLocState(areaMatId = void 0) {
+  return createLocAreaMatRelationBindLocState(areaMatId)
+}
+
+export function buildLocAreaMatRelationBindMatnrModel(record = {}) {
+  return {
+    ...createLocAreaMatRelationBindMatnrState(record.areaMatId),
+    ...(record.areaMatId !== void 0 && record.areaMatId !== null && record.areaMatId !== ''
+      ? { areaMatId: normalizeNumber(record.areaMatId) }
+      : {}),
+    warehouseId:
+      record.warehouseId !== undefined && record.warehouseId !== null && record.warehouseId !== ''
+        ? normalizeNumber(record.warehouseId)
+        : '',
+    areaId:
+      record.areaId !== undefined && record.areaId !== null && record.areaId !== ''
+        ? normalizeNumber(record.areaId)
+        : '',
+    groupId:
+      record.groupId !== undefined && record.groupId !== null && record.groupId !== ''
+        ? normalizeNumber(record.groupId)
+        : '',
+    matnrId: Array.isArray(record.matnrId) ? record.matnrId.map((item) => normalizeNumber(item)).filter(Boolean) : [],
+    typeId: Array.isArray(record.typeId) ? record.typeId.map((item) => normalizeNumber(item)).filter(Boolean) : []
+  }
+}
+
+export function buildLocAreaMatRelationBindLocModel(record = {}) {
+  return {
+    ...createLocAreaMatRelationBindLocState(record.areaMatId),
+    ...(record.areaMatId !== void 0 && record.areaMatId !== null && record.areaMatId !== ''
+      ? { areaMatId: normalizeNumber(record.areaMatId) }
+      : {}),
+    warehouseId:
+      record.warehouseId !== undefined && record.warehouseId !== null && record.warehouseId !== ''
+        ? normalizeNumber(record.warehouseId)
+        : '',
+    areaId:
+      record.areaId !== undefined && record.areaId !== null && record.areaId !== ''
+        ? normalizeNumber(record.areaId)
+        : '',
+    groupId:
+      record.groupId !== undefined && record.groupId !== null && record.groupId !== ''
+        ? normalizeNumber(record.groupId)
+        : '',
+    locId: Array.isArray(record.locId) ? record.locId.map((item) => normalizeNumber(item)).filter(Boolean) : [],
+    typeId: Array.isArray(record.typeId) ? record.typeId.map((item) => normalizeNumber(item)).filter(Boolean) : []
+  }
+}
+
+export function getLocAreaMatStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getLocAreaMatPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getLocAreaMatStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildLocAreaMatSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    warehouseId:
+      params.warehouseId !== undefined && params.warehouseId !== null && params.warehouseId !== ''
+        ? normalizeNumber(params.warehouseId)
+        : void 0,
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? normalizeNumber(params.areaId)
+        : void 0,
+    depict: normalizeText(params.depict),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaMatPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocAreaMatSearchParams(params)
+  }
+}
+
+export function buildLocAreaMatSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    warehouseId:
+      formData.warehouseId !== undefined && formData.warehouseId !== null && formData.warehouseId !== ''
+        ? normalizeNumber(formData.warehouseId)
+        : void 0,
+    areaId:
+      formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+        ? normalizeNumber(formData.areaId)
+        : void 0,
+    depict: normalizeText(formData.depict) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocAreaMatDialogModel(record = {}) {
+  return {
+    ...createLocAreaMatFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: normalizeNumber(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    warehouseId:
+      record.warehouseId !== undefined && record.warehouseId !== null && record.warehouseId !== ''
+        ? normalizeNumber(record.warehouseId)
+        : '',
+    areaId:
+      record.areaId !== undefined && record.areaId !== null && record.areaId !== ''
+        ? normalizeNumber(record.areaId)
+        : '',
+    depict: normalizeText(record.depict || ''),
+    status: record.status !== void 0 && record.status !== null ? normalizeNumber(record.status, 1) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeLocAreaMatListRow(record = {}) {
+  const statusMeta = getLocAreaMatStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    code: normalizeText(record.code || ''),
+    depict: normalizeText(record.depict || ''),
+    warehouseName: normalizeText(record.warehouseId$ || record.warehouseName || ''),
+    areaName: normalizeText(record.areaId$ || record.areaName || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    warehouseIdText: normalizeText(record.warehouseId$ || record.warehouseIdText || ''),
+    areaIdText: normalizeText(record.areaId$ || record.areaIdText || '')
+  }
+}
+
+export function normalizeLocAreaMatDetailRecord(record = {}) {
+  return normalizeLocAreaMatListRow(record)
+}
+
+export function normalizeLocAreaMatRelationRow(record = {}) {
+  const relationTypeText =
+    record.matnrId !== void 0 ||
+    record.matnrId$ !== void 0 ||
+    record.typeId !== void 0 ||
+    record.typeId$ !== void 0
+    ? '鐗╂枡缁戝畾'
+    : '搴撲綅缁戝畾'
+
+  return {
+    ...record,
+    warehouseIdText: normalizeText(record.warehouseId$ || record.warehouseIdText || ''),
+    areaIdText: normalizeText(record.areaId$ || record.areaIdText || ''),
+    matnrIdText: normalizeText(record.matnrId$ || record.matnrIdText || record.matnrCode || record.matnrName || ''),
+    groupIdText: normalizeText(record.groupId$ || record.groupIdText || record.groupName || ''),
+    locTypeIdText: normalizeText(record.locTypeId$ || record.locTypeIdText || record.typeName || record.typeText || ''),
+    locIdText: normalizeText(record.locId$ || record.locIdText || record.locCode || record.code || ''),
+    relationTypeText,
+    statusText: getLocAreaMatStatusMeta(record.statusBool ?? record.status).text,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function buildLocAreaMatPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocAreaMatListRow(record))
+}
+
+export function buildLocAreaMatReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_AREA_MAT_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_AREA_MAT_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_AREA_MAT_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+function normalizeOptionText(item = {}, fallbackPrefix = '') {
+  return normalizeText(item.name || item.code || item.label || item.codeText || `${fallbackPrefix}${item.id ?? item.value ?? ''}`) || '--'
+}
+
+function normalizeOptionId(item = {}) {
+  const value = item.id ?? item.value
+  if (value === void 0 || value === null || value === '') {
+    return void 0
+  }
+  return normalizeNumber(value)
+}
+
+export function resolveLocAreaMatWarehouseOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      return {
+        value,
+        label: normalizeOptionText(item, '浠撳簱 ')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveLocAreaMatAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      return {
+        value,
+        label: normalizeOptionText(item, '搴撳尯 '),
+        warehouseId:
+          item.warehouseId !== undefined && item.warehouseId !== null && item.warehouseId !== ''
+            ? normalizeNumber(item.warehouseId)
+            : void 0
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveLocAreaMatLocTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      return {
+        value,
+        label: normalizeOptionText(item, '绫诲瀷 ')
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveLocAreaMatMatnrOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      const label = normalizeText(item.name || item.code || item.maktx || item.barcode || `鐗╂枡 ${value}`)
+      return {
+        value,
+        label: label || `鐗╂枡 ${value}`,
+        groupId:
+          item.groupId !== undefined && item.groupId !== null && item.groupId !== ''
+            ? normalizeNumber(item.groupId)
+            : void 0
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveLocAreaMatLocOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      const value = normalizeOptionId(item)
+      if (value === void 0) return null
+      const code = normalizeText(item.code || item.locCode || '')
+      const label = [code, normalizeText(item.warehouseName || item.areaName || '')].filter(Boolean).join(' 路 ') || `搴撲綅 ${value}`
+      return {
+        value,
+        label,
+        warehouseId:
+          item.warehouseId !== undefined && item.warehouseId !== null && item.warehouseId !== ''
+            ? normalizeNumber(item.warehouseId)
+            : void 0,
+        areaId:
+          item.areaId !== undefined && item.areaId !== null && item.areaId !== ''
+            ? normalizeNumber(item.areaId)
+            : void 0
+      }
+    })
+    .filter(Boolean)
+}
+
+export function normalizeLocAreaMatGroupTreeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  const walk = (nodes = []) =>
+    nodes
+      .map((item) => {
+        if (!item || typeof item !== 'object') {
+          return null
+        }
+        const value = normalizeOptionId(item)
+        const children = walk(item.children || [])
+        if (value === void 0 && children.length === 0) {
+          return null
+        }
+        return {
+          id: value ?? 0,
+          name: normalizeText(item.name || '椤剁骇鍒嗙粍'),
+          code: normalizeText(item.code || ''),
+          displayLabel: [normalizeText(item.name || '椤剁骇鍒嗙粍'), normalizeText(item.code || '')].filter(Boolean).join(' 路 ') || '椤剁骇鍒嗙粍',
+          children
+        }
+      })
+      .filter(Boolean)
+
+  return [
+    {
+      id: 0,
+      name: '椤剁骇鍒嗙粍',
+      code: '',
+      displayLabel: '椤剁骇鍒嗙粍',
+      children: walk(records)
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatTable.columns.js b/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatTable.columns.js
new file mode 100644
index 0000000..60c78ab
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/locAreaMatTable.columns.js
@@ -0,0 +1,125 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getLocAreaMatStatusMeta } from './locAreaMatPage.helpers'
+
+export function createLocAreaMatTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: '閫昏緫缂栧彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'warehouseName',
+      label: '浠撳簱',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.warehouseName || row.warehouseIdText || '--'
+    },
+    {
+      prop: 'areaName',
+      label: '搴撳尯',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaName || row.areaIdText || '--'
+    },
+    {
+      prop: 'depict',
+      label: '閫昏緫鎻忚堪',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.depict || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getLocAreaMatStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
+
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-loc-dialog.vue b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-loc-dialog.vue
new file mode 100644
index 0000000..49f1e76
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-loc-dialog.vue
@@ -0,0 +1,199 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaMatRelationBindLocModel,
+    createLocAreaMatRelationBindLocState
+  } from '../locAreaMatPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    bindData: { type: Object, default: () => ({}) },
+    warehouseOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    groupOptions: { type: Array, default: () => [] },
+    locOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+
+  const formRef = ref()
+  const form = reactive(createLocAreaMatRelationBindLocState())
+
+  const dialogTitle = computed(() => '搴撲綅缁戝畾搴撳尯')
+  const selectedWarehouseId = computed(() =>
+    form.warehouseId !== undefined && form.warehouseId !== null && form.warehouseId !== ''
+      ? Number(form.warehouseId)
+      : void 0
+  )
+  const selectedAreaId = computed(() =>
+    form.areaId !== undefined && form.areaId !== null && form.areaId !== ''
+      ? Number(form.areaId)
+      : void 0
+  )
+
+  const filteredAreaOptions = computed(() => {
+    if (selectedWarehouseId.value === void 0) {
+      return props.areaOptions
+    }
+    return props.areaOptions.filter(
+      (item) => item.warehouseId === void 0 || Number(item.warehouseId) === selectedWarehouseId.value
+    )
+  })
+
+  const filteredLocOptions = computed(() => {
+    return props.locOptions.filter((item) => {
+      const warehouseMatch =
+        selectedWarehouseId.value === void 0 ||
+        item.warehouseId === void 0 ||
+        Number(item.warehouseId) === selectedWarehouseId.value
+      const areaMatch =
+        selectedAreaId.value === void 0 || item.areaId === void 0 || Number(item.areaId) === selectedAreaId.value
+      return warehouseMatch && areaMatch
+    })
+  })
+
+  const rules = computed(() => ({
+    warehouseId: [{ required: true, message: '璇烽�夋嫨浠撳簱', trigger: 'change' }],
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }],
+    groupId: [{ required: true, message: '璇烽�夋嫨鐗╂枡鍒嗙粍', trigger: 'change' }],
+    locId: [{ required: true, message: '璇烽�夋嫨搴撲綅', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+        clearable: true,
+        filterable: true,
+        options: props.warehouseOptions
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: filteredAreaOptions.value
+      }
+    },
+    {
+      label: '鐗╂枡鍒嗙粍',
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: props.groupOptions,
+        props: {
+          label: 'displayLabel',
+          value: 'id',
+          children: 'children'
+        },
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙粍',
+        clearable: false,
+        checkStrictly: true,
+        defaultExpandAll: true
+      }
+    },
+    {
+      label: '搴撲綅',
+      key: 'locId',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: filteredLocOptions.value
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaMatRelationBindLocState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaMatRelationBindLocModel(props.bindData))
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.bindData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-matnr-dialog.vue b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-matnr-dialog.vue
new file mode 100644
index 0000000..5310469
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-bind-matnr-dialog.vue
@@ -0,0 +1,212 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaMatRelationBindMatnrModel,
+    createLocAreaMatRelationBindMatnrState
+  } from '../locAreaMatPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    bindData: { type: Object, default: () => ({}) },
+    warehouseOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    groupOptions: { type: Array, default: () => [] },
+    matnrOptions: { type: Array, default: () => [] },
+    locTypeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+
+  const formRef = ref()
+  const form = reactive(createLocAreaMatRelationBindMatnrState())
+
+  const dialogTitle = computed(() => '鐗╂枡缁戝畾搴撳尯')
+  const selectedWarehouseId = computed(() =>
+    form.warehouseId !== undefined && form.warehouseId !== null && form.warehouseId !== ''
+      ? Number(form.warehouseId)
+      : void 0
+  )
+  const selectedGroupId = computed(() =>
+    form.groupId !== undefined && form.groupId !== null && form.groupId !== ''
+      ? Number(form.groupId)
+      : void 0
+  )
+
+  const filteredAreaOptions = computed(() => {
+    if (selectedWarehouseId.value === void 0) {
+      return props.areaOptions
+    }
+    return props.areaOptions.filter(
+      (item) => item.warehouseId === void 0 || Number(item.warehouseId) === selectedWarehouseId.value
+    )
+  })
+
+  const filteredMatnrOptions = computed(() => {
+    if (selectedGroupId.value === void 0) {
+      return props.matnrOptions
+    }
+    return props.matnrOptions.filter(
+      (item) => item.groupId === void 0 || Number(item.groupId) === selectedGroupId.value
+    )
+  })
+
+  const rules = computed(() => ({
+    warehouseId: [{ required: true, message: '璇烽�夋嫨浠撳簱', trigger: 'change' }],
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }],
+    groupId: [{ required: true, message: '璇烽�夋嫨鐗╂枡鍒嗙粍', trigger: 'change' }],
+    matnrId: [{ required: true, message: '璇烽�夋嫨鐗╂枡', trigger: 'change' }],
+    typeId: [{ required: true, message: '璇烽�夋嫨搴撲綅绫诲瀷', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+        clearable: true,
+        filterable: true,
+        options: props.warehouseOptions
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: filteredAreaOptions.value
+      }
+    },
+    {
+      label: '鐗╂枡鍒嗙粍',
+      key: 'groupId',
+      type: 'treeselect',
+      props: {
+        data: props.groupOptions,
+        props: {
+          label: 'displayLabel',
+          value: 'id',
+          children: 'children'
+        },
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙粍',
+        clearable: false,
+        checkStrictly: true,
+        defaultExpandAll: true
+      }
+    },
+    {
+      label: '鐗╂枡',
+      key: 'matnrId',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: filteredMatnrOptions.value
+      }
+    },
+    {
+      label: '搴撲綅绫诲瀷',
+      key: 'typeId',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨搴撲綅绫诲瀷',
+        clearable: true,
+        multiple: true,
+        collapseTags: true,
+        filterable: true,
+        options: props.locTypeOptions
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaMatRelationBindMatnrState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaMatRelationBindMatnrModel(props.bindData))
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.bindData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-detail-drawer.vue b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-detail-drawer.vue
new file mode 100644
index 0000000..045641d
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-detail-drawer.vue
@@ -0,0 +1,60 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳尯鐗╂枡璇︽儏"
+    size="1120px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="閫昏緫缂栧彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱">{{ detail.warehouseName || detail.warehouseIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯">{{ detail.areaName || detail.areaIdText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閫昏緫鎻忚堪">{{ detail.depict || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <LocAreaMatRelationPanel :area-mat-data="detail" />
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import LocAreaMatRelationPanel from './loc-area-mat-relation-panel.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-dialog.vue b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-dialog.vue
new file mode 100644
index 0000000..3ee1b5e
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-dialog.vue
@@ -0,0 +1,198 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="920px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaMatDialogModel,
+    createLocAreaMatFormState
+  } from '../locAreaMatPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    locAreaMatData: { type: Object, default: () => ({}) },
+    warehouseOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+
+  const formRef = ref()
+  const form = reactive(createLocAreaMatFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳尯鐗╂枡' : '鏂板搴撳尯鐗╂枡'))
+
+  const selectedWarehouseId = computed(() =>
+    form.warehouseId !== void 0 && form.warehouseId !== null && form.warehouseId !== ''
+      ? Number(form.warehouseId)
+      : void 0
+  )
+
+  const filteredAreaOptions = computed(() => {
+    if (selectedWarehouseId.value === void 0) {
+      return props.areaOptions
+    }
+    return props.areaOptions.filter(
+      (item) => item.warehouseId === void 0 || Number(item.warehouseId) === selectedWarehouseId.value
+    )
+  })
+
+  const rules = computed(() => ({
+    code: [{ required: true, message: '璇疯緭鍏ラ�昏緫缂栧彿', trigger: 'blur' }],
+    warehouseId: [{ required: true, message: '璇烽�夋嫨浠撳簱', trigger: 'change' }],
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }],
+    depict: [{ required: true, message: '璇疯緭鍏ラ�昏緫鎻忚堪', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '閫昏緫缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ�昏緫缂栧彿',
+        clearable: true
+      }
+    },
+    {
+      label: '浠撳簱',
+      key: 'warehouseId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+        clearable: true,
+        filterable: true,
+        options: props.warehouseOptions
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: filteredAreaOptions.value
+      }
+    },
+    {
+      label: '閫昏緫鎻忚堪',
+      key: 'depict',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ラ�昏緫鎻忚堪',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaMatFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaMatDialogModel(props.locAreaMatData))
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.locAreaMatData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-relation-panel.vue b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-relation-panel.vue
new file mode 100644
index 0000000..8a250b8
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-mat/modules/loc-area-mat-relation-panel.vue
@@ -0,0 +1,435 @@
+<template>
+  <ElCard class="art-table-card" shadow="never">
+    <template #header>
+      <div class="flex items-center justify-between gap-4">
+        <div>
+          <div class="text-base font-medium text-[var(--art-text-primary)]">鍏崇郴绠$悊</div>
+          <div class="text-xs text-[var(--art-text-secondary)]">
+            {{ panelSubtitle }}
+          </div>
+        </div>
+
+        <ElSpace wrap>
+          <ElButton size="small" @click="handleRefresh" v-ripple>鍒锋柊</ElButton>
+          <ElButton size="small" type="primary" @click="openBindMatnrDialog" v-ripple>缁戝畾鐗╂枡</ElButton>
+          <ElButton size="small" type="success" @click="openBindLocDialog" v-ripple>缁戝畾搴撲綅</ElButton>
+          <ElButton
+            size="small"
+            type="danger"
+            :disabled="selectedRows.length === 0"
+            @click="handleBatchDelete"
+            v-ripple
+          >
+            鎵归噺鍒犻櫎
+          </ElButton>
+        </ElSpace>
+      </div>
+    </template>
+
+    <ArtTable
+      :loading="loading"
+      :data="data"
+      :columns="columns"
+      :pagination="pagination"
+      @selection-change="handleSelectionChange"
+      @pagination:size-change="handleSizeChange"
+      @pagination:current-change="handleCurrentChange"
+    />
+
+    <LocAreaMatBindMatnrDialog
+      v-model:visible="bindMatnrDialogVisible"
+      :bind-data="bindMatnrData"
+      :warehouse-options="warehouseOptions"
+      :area-options="areaOptions"
+      :group-options="groupOptions"
+      :matnr-options="matnrOptions"
+      :loc-type-options="locTypeOptions"
+      @submit="handleBindMatnrSubmit"
+    />
+
+    <LocAreaMatBindLocDialog
+      v-model:visible="bindLocDialogVisible"
+      :bind-data="bindLocData"
+      :warehouse-options="warehouseOptions"
+      :area-options="areaOptions"
+      :group-options="groupOptions"
+      :loc-options="locOptions"
+      @submit="handleBindLocSubmit"
+    />
+  </ElCard>
+</template>
+
+<script setup>
+  import { computed, h, onMounted, ref, watch } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchWarehouseAreasList, fetchWarehouseList, fetchLocPage, fetchLocTypeList } from '@/api/loc'
+  import { fetchMatnrGroupTree, fetchMatnrPage } from '@/api/wh-mat'
+  import {
+    fetchBindLocAreaMatByMatnr,
+    fetchDeleteLocAreaMatRela,
+    fetchLocAreaMatRelaPage
+  } from '@/api/loc-area-mat'
+  import {
+    buildLocAreaMatRelationBindMatnrModel,
+    buildLocAreaMatRelationBindLocModel,
+    normalizeLocAreaMatRelationRow,
+    normalizeLocAreaMatGroupTreeOptions,
+    resolveLocAreaMatAreaOptions,
+    resolveLocAreaMatLocOptions,
+    resolveLocAreaMatLocTypeOptions,
+    resolveLocAreaMatMatnrOptions,
+    resolveLocAreaMatWarehouseOptions
+  } from '../locAreaMatPage.helpers'
+  import { createLocAreaMatRelationBindMatnrState } from '../locAreaMatPage.helpers'
+  import LocAreaMatBindMatnrDialog from './loc-area-mat-bind-matnr-dialog.vue'
+  import LocAreaMatBindLocDialog from './loc-area-mat-bind-loc-dialog.vue'
+  import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+
+  const props = defineProps({
+    areaMatData: { type: Object, default: () => ({}) }
+  })
+
+  const warehouseOptions = ref([])
+  const areaOptions = ref([])
+  const groupOptions = ref([])
+  const matnrOptions = ref([])
+  const locTypeOptions = ref([])
+  const locOptions = ref([])
+  const bindMatnrDialogVisible = ref(false)
+  const bindLocDialogVisible = ref(false)
+
+  const areaMatId = computed(() =>
+    props.areaMatData?.id !== void 0 && props.areaMatData?.id !== null && props.areaMatData?.id !== ''
+      ? Number(props.areaMatData.id)
+      : void 0
+  )
+
+  const bindMatnrData = computed(() =>
+    buildLocAreaMatRelationBindMatnrModel({
+      ...createLocAreaMatRelationBindMatnrState(areaMatId.value),
+      areaMatId: areaMatId.value,
+      warehouseId: props.areaMatData?.warehouseId,
+      areaId: props.areaMatData?.areaId
+    })
+  )
+
+  const bindLocData = computed(() =>
+    buildLocAreaMatRelationBindLocModel({
+      areaMatId: areaMatId.value,
+      warehouseId: props.areaMatData?.warehouseId,
+      areaId: props.areaMatData?.areaId
+    })
+  )
+
+  const panelSubtitle = computed(() => {
+    if (!areaMatId.value) {
+      return '璇烽�夋嫨搴撳尯鐗╂枡鏌ョ湅鍏崇郴鏄庣粏'
+    }
+    const code = String(props.areaMatData?.code || '').trim()
+    const depict = String(props.areaMatData?.depict || '').trim()
+    return [code, depict].filter(Boolean).join(' 路 ') || `涓诲崟ID ${areaMatId.value}`
+  })
+
+  const columns = computed(() => [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'relationTypeText',
+      label: '缁戝畾绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.relationTypeText || '--'
+    },
+    {
+      prop: 'warehouseIdText',
+      label: '浠撳簱',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.warehouseIdText || '--'
+    },
+    {
+      prop: 'areaIdText',
+      label: '搴撳尯',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaIdText || '--'
+    },
+    {
+      prop: 'groupIdText',
+      label: '鐗╂枡鍒嗙粍',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.groupIdText || '--'
+    },
+    {
+      prop: 'matnrIdText',
+      label: '鐗╂枡',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrIdText || '--'
+    },
+    {
+      prop: 'locTypeIdText',
+      label: '搴撲綅绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locTypeIdText || '--'
+    },
+    {
+      prop: 'locIdText',
+      label: '搴撲綅',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locIdText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 120,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: [{ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' }],
+          onClick: (item) => {
+            if (item.key === 'delete') {
+              handleDeleteRelation(row)
+            }
+          }
+        })
+    }
+  ])
+
+  const {
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchLocAreaMatRelaPage,
+      apiParams: {
+        current: 1,
+        pageSize: 20
+      },
+      immediate: false,
+      columnsFactory: () => columns.value,
+      paginationKey: {
+        current: 'current',
+        size: 'pageSize'
+      }
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocAreaMatRelationRow(item))
+      }
+    }
+  })
+
+  const selectedRows = ref([])
+
+  const handleSelectionChange = (rows) => {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  const handleRefresh = async () => {
+    await refreshData()
+  }
+
+  async function loadRelationData() {
+    if (!areaMatId.value) {
+      selectedRows.value = []
+      replaceSearchParams({ areaMatId: void 0 })
+      return
+    }
+    replaceSearchParams({
+      areaMatId: areaMatId.value
+    })
+    await getData({
+      areaMatId: areaMatId.value
+    })
+  }
+
+  async function handleDeleteRelation(record) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄ゅ叧绯汇��${record?.relationTypeText || record?.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteLocAreaMatRela(record.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshData()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  async function handleBatchDelete() {
+    if (!selectedRows.value.length) {
+      return
+    }
+    const ids = selectedRows.value
+      .map((item) => item.id)
+      .filter((id) => id !== void 0 && id !== null)
+    if (!ids.length) {
+      return
+    }
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佹壒閲忓垹闄ら�変腑鐨� ${ids.length} 鏉″叧绯诲悧锛焋, '鎵归噺鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteLocAreaMatRela(ids.join(','))
+      ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+      selectedRows.value = []
+      await refreshData()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鎵归噺鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  function openBindMatnrDialog() {
+    if (!areaMatId.value) {
+      ElMessage.warning('璇峰厛閫夋嫨搴撳尯鐗╂枡涓诲崟')
+      return
+    }
+    bindMatnrDialogVisible.value = true
+  }
+
+  function openBindLocDialog() {
+    if (!areaMatId.value) {
+      ElMessage.warning('璇峰厛閫夋嫨搴撳尯鐗╂枡涓诲崟')
+      return
+    }
+    bindLocDialogVisible.value = true
+  }
+
+  async function handleBindMatnrSubmit(formData) {
+    try {
+      await fetchBindLocAreaMatByMatnr({
+        areaMatId: areaMatId.value,
+        warehouseId: formData.warehouseId,
+        areaId: formData.areaId,
+        groupId: formData.groupId,
+        matnrId: formData.matnrId,
+        typeId: formData.typeId
+      })
+      ElMessage.success('鐗╂枡缁戝畾鎴愬姛')
+      bindMatnrDialogVisible.value = false
+      await refreshData()
+    } catch (error) {
+      ElMessage.error(error?.message || '鐗╂枡缁戝畾澶辫触')
+    }
+  }
+
+  async function handleBindLocSubmit(formData) {
+    try {
+      await fetchBindLocAreaMatByMatnr({
+        areaMatId: areaMatId.value,
+        warehouseId: formData.warehouseId,
+        areaId: formData.areaId,
+        groupId: formData.groupId,
+        locId: formData.locId
+      })
+      ElMessage.success('搴撲綅缁戝畾鎴愬姛')
+      bindLocDialogVisible.value = false
+      await refreshData()
+    } catch (error) {
+      ElMessage.error(error?.message || '搴撲綅缁戝畾澶辫触')
+    }
+  }
+
+  async function loadWarehouseOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseList(), [], {
+      timeoutMessage: '浠撳簱閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    warehouseOptions.value = resolveLocAreaMatWarehouseOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveLocAreaMatAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadGroupOptions() {
+    const records = await guardRequestWithMessage(fetchMatnrGroupTree({}), [], {
+      timeoutMessage: '鐗╂枡鍒嗙粍閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    groupOptions.value = normalizeLocAreaMatGroupTreeOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadMatnrOptions() {
+    const response = await guardRequestWithMessage(
+      fetchMatnrPage({ current: 1, pageSize: 200 }),
+      { records: [] },
+      { timeoutMessage: '鐗╂枡閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    matnrOptions.value = resolveLocAreaMatMatnrOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadLocTypeOptions() {
+    const records = await guardRequestWithMessage(fetchLocTypeList(), [], {
+      timeoutMessage: '搴撲綅绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    locTypeOptions.value = resolveLocAreaMatLocTypeOptions(defaultResponseAdapter(records).records)
+  }
+
+  async function loadLocOptions() {
+    const response = await guardRequestWithMessage(
+      fetchLocPage({ current: 1, pageSize: 200 }),
+      { records: [] },
+      { timeoutMessage: '搴撲綅閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    locOptions.value = resolveLocAreaMatLocOptions(defaultResponseAdapter(response).records)
+  }
+
+  watch(
+    () => props.areaMatData,
+    () => {
+      loadRelationData()
+    },
+    { deep: true, immediate: true }
+  )
+
+  onMounted(async () => {
+    await Promise.all([
+      loadWarehouseOptions(),
+      loadAreaOptions(),
+      loadGroupOptions(),
+      loadMatnrOptions(),
+      loadLocTypeOptions(),
+      loadLocOptions()
+    ])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-rela/index.vue b/rsf-design/src/views/basic-info/loc-area-rela/index.vue
new file mode 100644
index 0000000..34c5be8
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-rela/index.vue
@@ -0,0 +1,361 @@
+<template>
+  <div class="loc-area-rela-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳尯鍏崇郴</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocAreaRelaDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :loc-area-rela-data="currentLocAreaRelaData"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocAreaRelaDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchDeleteLocAreaRela,
+    fetchExportLocAreaRelaReport,
+    fetchGetLocAreaRelaDetail,
+    fetchLocAreaRelaMany,
+    fetchLocAreaRelaPage,
+    fetchSaveLocAreaRela,
+    fetchUpdateLocAreaRela
+  } from '@/api/loc-area-rela'
+  import LocAreaRelaDialog from './modules/loc-area-rela-dialog.vue'
+  import LocAreaRelaDetailDrawer from './modules/loc-area-rela-detail-drawer.vue'
+  import { createLocAreaRelaTableColumns } from './locAreaRelaTable.columns'
+  import {
+    LOC_AREA_RELA_REPORT_STYLE,
+    LOC_AREA_RELA_REPORT_TITLE,
+    buildLocAreaRelaDialogModel,
+    buildLocAreaRelaPageQueryParams,
+    buildLocAreaRelaPrintRows,
+    buildLocAreaRelaReportMeta,
+    buildLocAreaRelaSavePayload,
+    buildLocAreaRelaSearchParams,
+    createLocAreaRelaSearchState,
+    getLocAreaRelaPaginationKey,
+    getLocAreaRelaStatusOptions,
+    normalizeLocAreaRelaListRow
+  } from './locAreaRelaPage.helpers'
+
+  defineOptions({ name: 'LocAreaRela' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createLocAreaRelaSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = LOC_AREA_RELA_REPORT_TITLE
+  const reportQueryParams = computed(() => buildLocAreaRelaSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ垎鍖篒D/搴撲綅ID/澶囨敞'
+      }
+    },
+    {
+      label: '鍒嗗尯ID',
+      key: 'locAreaId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ垎鍖篒D'
+      }
+    },
+    {
+      label: '搴撲綅ID',
+      key: 'locId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ簱浣岻D'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocAreaRelaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃ユ湡'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchGetLocAreaRelaDetail(row.id),
+        {},
+        {
+          timeoutMessage: '搴撳尯鍏崇郴璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      detailData.value = normalizeLocAreaRelaListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鍏崇郴璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchGetLocAreaRelaDetail(row.id),
+        {},
+        {
+          timeoutMessage: '搴撳尯鍏崇郴璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯鍏崇郴璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchLocAreaRelaPage,
+      apiParams: buildLocAreaRelaPageQueryParams(searchForm.value),
+      paginationKey: getLocAreaRelaPaginationKey(),
+      columnsFactory: () =>
+        createLocAreaRelaTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocAreaRelaListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentLocAreaRelaData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildLocAreaRelaDialogModel(),
+    buildEditModel: (record) => buildLocAreaRelaDialogModel(record),
+    buildSavePayload: (formData) => buildLocAreaRelaSavePayload(formData),
+    saveRequest: fetchSaveLocAreaRela,
+    updateRequest: fetchUpdateLocAreaRela,
+    deleteRequest: fetchDeleteLocAreaRela,
+    entityName: '搴撳尯鍏崇郴',
+    resolveRecordLabel: (record) => record?.locAreaId || record?.locId || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...LOC_AREA_RELA_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchLocAreaRelaMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchLocAreaRelaPage({
+        ...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: 'loc-area-rela.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocAreaRelaReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocAreaRelaPrintRows(records),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocAreaRelaReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || LOC_AREA_RELA_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocAreaRelaSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocAreaRelaSearchState())
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    await getData()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaPage.helpers.js b/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaPage.helpers.js
new file mode 100644
index 0000000..e55dfc9
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaPage.helpers.js
@@ -0,0 +1,193 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const LOC_AREA_RELA_REPORT_TITLE = '搴撳尯鍏崇郴鎶ヨ〃'
+export const LOC_AREA_RELA_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+export function createLocAreaRelaSearchState() {
+  return {
+    condition: '',
+    locAreaId: '',
+    locId: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createLocAreaRelaFormState() {
+  return {
+    id: void 0,
+    locAreaId: void 0,
+    locId: void 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getLocAreaRelaPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getLocAreaRelaStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getLocAreaRelaStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildLocAreaRelaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    locAreaId:
+      params.locAreaId !== undefined && params.locAreaId !== null && params.locAreaId !== ''
+        ? normalizeNumber(params.locAreaId)
+        : void 0,
+    locId:
+      params.locId !== undefined && params.locId !== null && params.locId !== ''
+        ? normalizeNumber(params.locId)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
+  )
+}
+
+export function buildLocAreaRelaPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocAreaRelaSearchParams(params)
+  }
+}
+
+export function buildLocAreaRelaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(formData.locAreaId !== undefined && formData.locAreaId !== null && formData.locAreaId !== ''
+      ? { locAreaId: normalizeNumber(formData.locAreaId) }
+      : {}),
+    ...(formData.locId !== undefined && formData.locId !== null && formData.locId !== ''
+      ? { locId: normalizeNumber(formData.locId) }
+      : {}),
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? normalizeNumber(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocAreaRelaDialogModel(record = {}) {
+  return {
+    ...createLocAreaRelaFormState(),
+    ...(record.id !== undefined && record.id !== null && record.id !== ''
+      ? { id: normalizeNumber(record.id) }
+      : {}),
+    locAreaId:
+      record.locAreaId !== undefined && record.locAreaId !== null && record.locAreaId !== ''
+        ? normalizeNumber(record.locAreaId)
+        : void 0,
+    locId:
+      record.locId !== undefined && record.locId !== null && record.locId !== ''
+        ? normalizeNumber(record.locId)
+        : void 0,
+    status:
+      record.status !== undefined && record.status !== null ? normalizeNumber(record.status, 1) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeLocAreaRelaDetailRecord(record = {}) {
+  const statusMeta = getLocAreaRelaStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    locAreaIdText: normalizeText(
+      record.locAreaId$ || record.locAreaIdText || record.locAreaId || ''
+    ),
+    locIdText: normalizeText(record.locId$ || record.locIdText || record.locId || ''),
+    memo: normalizeText(record.memo || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeLocAreaRelaListRow(record = {}) {
+  return normalizeLocAreaRelaDetailRecord(record)
+}
+
+export function buildLocAreaRelaPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocAreaRelaListRow(record))
+}
+
+export function buildLocAreaRelaReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_AREA_RELA_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_AREA_RELA_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_AREA_RELA_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaTable.columns.js b/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaTable.columns.js
new file mode 100644
index 0000000..bdfd92d
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-rela/locAreaRelaTable.columns.js
@@ -0,0 +1,115 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getLocAreaRelaStatusMeta } from './locAreaRelaPage.helpers'
+
+export function createLocAreaRelaTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({
+      key: 'delete',
+      label: '鍒犻櫎',
+      icon: 'ri:delete-bin-5-line',
+      color: 'var(--art-error)'
+    })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'locAreaIdText',
+      label: '鍒嗗尯ID',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locAreaIdText || row.locAreaId || '--'
+    },
+    {
+      prop: 'locIdText',
+      label: '搴撲綅ID',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locIdText || row.locId || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getLocAreaRelaStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-detail-drawer.vue b/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-detail-drawer.vue
new file mode 100644
index 0000000..088f2e6
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-detail-drawer.vue
@@ -0,0 +1,63 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳尯鍏崇郴璇︽儏"
+    size="960px"
+    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">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鍒嗗尯ID">{{
+            detail.locAreaIdText || detail.locAreaId || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅ID">{{
+            detail.locIdText || detail.locId || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-dialog.vue b/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-dialog.vue
new file mode 100644
index 0000000..766188f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area-rela/modules/loc-area-rela-dialog.vue
@@ -0,0 +1,152 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="720px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaRelaDialogModel,
+    createLocAreaRelaFormState,
+    getLocAreaRelaStatusOptions
+  } from '../locAreaRelaPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    locAreaRelaData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createLocAreaRelaFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳尯鍏崇郴' : '鏂板搴撳尯鍏崇郴'))
+
+  const rules = computed(() => ({
+    locAreaId: [{ required: true, message: '璇疯緭鍏ュ垎鍖篒D', trigger: 'blur' }],
+    locId: [{ required: true, message: '璇疯緭鍏ュ簱浣岻D', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '鍒嗗尯ID',
+      key: 'locAreaId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ垎鍖篒D'
+      }
+    },
+    {
+      label: '搴撲綅ID',
+      key: 'locId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ簱浣岻D'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: getLocAreaRelaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaRelaDialogModel(props.locAreaRelaData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaRelaFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.locAreaRelaData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area/index.vue b/rsf-design/src/views/basic-info/loc-area/index.vue
new file mode 100644
index 0000000..8d3032c
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area/index.vue
@@ -0,0 +1,345 @@
+<template>
+  <div class="loc-area-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳尯</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocAreaDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :loc-area-data="currentLocAreaData"
+        :area-options="areaOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocAreaDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import {
+    fetchDeleteLocArea,
+    fetchExportLocAreaReport,
+    fetchGetLocAreaDetail,
+    fetchLocAreaMany,
+    fetchLocAreaPage,
+    fetchSaveLocArea,
+    fetchUpdateLocArea
+  } from '@/api/loc-area'
+  import LocAreaDialog from './modules/loc-area-dialog.vue'
+  import LocAreaDetailDrawer from './modules/loc-area-detail-drawer.vue'
+  import { createLocAreaTableColumns } from './locAreaTable.columns'
+  import {
+    LOC_AREA_REPORT_STYLE,
+    LOC_AREA_REPORT_TITLE,
+    buildLocAreaDialogModel,
+    buildLocAreaPageQueryParams,
+    buildLocAreaPrintRows,
+    buildLocAreaReportMeta,
+    buildLocAreaSavePayload,
+    buildLocAreaSearchParams,
+    createLocAreaSearchState,
+    getLocAreaPaginationKey,
+    getLocAreaStatusOptions,
+    normalizeLocAreaListRow,
+    resolveLocAreaOptions
+  } from './locAreaPage.helpers'
+
+  defineOptions({ name: 'LocArea' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createLocAreaSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const areaOptions = ref([])
+  let handleDeleteAction = null
+
+  const reportTitle = LOC_AREA_REPORT_TITLE
+  const reportQueryParams = computed(() => buildLocAreaSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�/缂栫爜/澶囨敞'
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: areaOptions.value
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悕绉�'
+      }
+    },
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鐮�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocAreaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeLocAreaListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocAreaDetail(row.id), {}, {
+        timeoutMessage: '搴撳尯璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳尯璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchLocAreaPage,
+      apiParams: buildLocAreaPageQueryParams(searchForm.value),
+      paginationKey: getLocAreaPaginationKey(),
+      columnsFactory: () =>
+        createLocAreaTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocAreaListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentLocAreaData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildLocAreaDialogModel(),
+    buildEditModel: (record) => buildLocAreaDialogModel(record),
+    buildSavePayload: (formData) => buildLocAreaSavePayload(formData),
+    saveRequest: fetchSaveLocArea,
+    updateRequest: fetchUpdateLocArea,
+    deleteRequest: fetchDeleteLocArea,
+    entityName: '搴撳尯',
+    resolveRecordLabel: (record) => record?.areaName || record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveLocAreaOptions(defaultResponseAdapter(records).records)
+  }
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response = Array.isArray(payload?.ids) && payload.ids.length > 0
+      ? await fetchLocAreaMany(payload.ids)
+      : await fetchLocAreaPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-area.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocAreaReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocAreaPrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocAreaReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || LOC_AREA_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocAreaSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocAreaSearchState())
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadAreaOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area/locAreaPage.helpers.js b/rsf-design/src/views/basic-info/loc-area/locAreaPage.helpers.js
new file mode 100644
index 0000000..2441e70
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area/locAreaPage.helpers.js
@@ -0,0 +1,202 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const LOC_AREA_REPORT_TITLE = '搴撳尯鎶ヨ〃'
+export const LOC_AREA_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+export function createLocAreaSearchState() {
+  return {
+    condition: '',
+    areaId: '',
+    name: '',
+    code: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createLocAreaFormState() {
+  return {
+    id: void 0,
+    areaId: void 0,
+    name: '',
+    code: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getLocAreaPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getLocAreaStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getLocAreaStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildLocAreaSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    areaId:
+      params.areaId !== undefined && params.areaId !== null && params.areaId !== ''
+        ? Number(params.areaId)
+        : void 0,
+    name: normalizeText(params.name),
+    code: normalizeText(params.code),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocAreaPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocAreaSearchParams(params)
+  }
+}
+
+export function buildLocAreaSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.areaId !== undefined && formData.areaId !== null && formData.areaId !== ''
+      ? { areaId: Number(formData.areaId) }
+      : {}),
+    name: normalizeText(formData.name) || '',
+    code: normalizeText(formData.code) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocAreaDialogModel(record = {}) {
+  return {
+    ...createLocAreaFormState(),
+    ...(record.id !== undefined && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    areaId:
+      record.areaId !== undefined && record.areaId !== null && record.areaId !== ''
+        ? Number(record.areaId)
+        : void 0,
+    name: normalizeText(record.name || ''),
+    code: normalizeText(record.code || ''),
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function resolveLocAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.name || item.code || `搴撳尯 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function normalizeLocAreaDetailRecord(record = {}) {
+  const statusMeta = getLocAreaStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    areaName: normalizeText(record.areaId$ || record.areaName || ''),
+    name: normalizeText(record.name || ''),
+    code: normalizeText(record.code || ''),
+    memo: normalizeText(record.memo || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeLocAreaListRow(record = {}) {
+  return normalizeLocAreaDetailRecord(record)
+}
+
+export function buildLocAreaPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocAreaListRow(record))
+}
+
+export function buildLocAreaReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_AREA_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_AREA_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_AREA_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/loc-area/locAreaTable.columns.js b/rsf-design/src/views/basic-info/loc-area/locAreaTable.columns.js
new file mode 100644
index 0000000..d54c857
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area/locAreaTable.columns.js
@@ -0,0 +1,108 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getLocAreaStatusMeta } from './locAreaPage.helpers'
+
+export function createLocAreaTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'areaName',
+      label: '搴撳尯',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.areaName || row.areaId$ || row.areaId || '--'
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.name || '--'
+    },
+    {
+      prop: 'code',
+      label: '缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getLocAreaStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/loc-area/modules/loc-area-detail-drawer.vue b/rsf-design/src/views/basic-info/loc-area/modules/loc-area-detail-drawer.vue
new file mode 100644
index 0000000..5bfc323
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area/modules/loc-area-detail-drawer.vue
@@ -0,0 +1,56 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳尯璇︽儏"
+    size="960px"
+    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">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="搴撳尯">{{ detail.areaName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-area/modules/loc-area-dialog.vue b/rsf-design/src/views/basic-info/loc-area/modules/loc-area-dialog.vue
new file mode 100644
index 0000000..94c9009
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-area/modules/loc-area-dialog.vue
@@ -0,0 +1,162 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="720px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocAreaDialogModel,
+    createLocAreaFormState,
+    getLocAreaStatusOptions
+  } from '../locAreaPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    locAreaData: { type: Object, default: () => ({}) },
+    areaOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createLocAreaFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳尯' : '鏂板搴撳尯'))
+
+  const rules = computed(() => ({
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: getLocAreaStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocAreaDialogModel(props.locAreaData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createLocAreaFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.locAreaData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-type/index.vue b/rsf-design/src/views/basic-info/loc-type/index.vue
new file mode 100644
index 0000000..b686593
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-type/index.vue
@@ -0,0 +1,342 @@
+<template>
+  <div class="loc-type-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撲綅绫诲瀷</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocTypeDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :loc-type-data="currentLocTypeData"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocTypeDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchDeleteLocType,
+    fetchExportLocTypeReport,
+    fetchGetLocTypeDetail,
+    fetchLocTypeMany,
+    fetchLocTypePage,
+    fetchSaveLocType,
+    fetchUpdateLocType
+  } from '@/api/loc-type'
+  import LocTypeDialog from './modules/loc-type-dialog.vue'
+  import LocTypeDetailDrawer from './modules/loc-type-detail-drawer.vue'
+  import { createLocTypeTableColumns } from './locTypeTable.columns'
+  import {
+    LOC_TYPE_REPORT_STYLE,
+    LOC_TYPE_REPORT_TITLE,
+    buildLocTypeDialogModel,
+    buildLocTypePageQueryParams,
+    buildLocTypePrintRows,
+    buildLocTypeReportMeta,
+    buildLocTypeSavePayload,
+    buildLocTypeSearchParams,
+    createLocTypeSearchState,
+    getLocTypePaginationKey,
+    getLocTypeStatusOptions,
+    normalizeLocTypeListRow
+  } from './locTypePage.helpers'
+
+  defineOptions({ name: 'LocType' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createLocTypeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = LOC_TYPE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildLocTypeSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鐮�/鍚嶇О/鏍囪瘑/澶囨敞'
+      }
+    },
+    {
+      label: '鏍囪瘑',
+      key: 'uuid',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ爣璇�'
+      }
+    },
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鐮�'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悕绉�'
+      }
+    },
+    {
+      label: '鏉$爜瑙勫垯',
+      key: 'regex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮佽鍒�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocTypeStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocTypeDetail(row.id), {}, {
+        timeoutMessage: '搴撲綅绫诲瀷璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeLocTypeListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撲綅绫诲瀷璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocTypeDetail(row.id), {}, {
+        timeoutMessage: '搴撲綅绫诲瀷璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撲綅绫诲瀷璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchLocTypePage,
+      apiParams: buildLocTypePageQueryParams(searchForm.value),
+      paginationKey: getLocTypePaginationKey(),
+      columnsFactory: () =>
+        createLocTypeTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeLocTypeListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentLocTypeData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildLocTypeDialogModel(),
+    buildEditModel: (record) => buildLocTypeDialogModel(record),
+    buildSavePayload: (formData) => buildLocTypeSavePayload(formData),
+    saveRequest: fetchSaveLocType,
+    updateRequest: fetchUpdateLocType,
+    deleteRequest: fetchDeleteLocType,
+    entityName: '搴撲綅绫诲瀷',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.uuid || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response = Array.isArray(payload?.ids) && payload.ids.length > 0
+      ? await fetchLocTypeMany(payload.ids)
+      : await fetchLocTypePage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-type.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocTypeReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocTypePrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocTypeReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || LOC_TYPE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocTypeSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocTypeSearchState())
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    await getData()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-type/locTypePage.helpers.js b/rsf-design/src/views/basic-info/loc-type/locTypePage.helpers.js
new file mode 100644
index 0000000..8819c81
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-type/locTypePage.helpers.js
@@ -0,0 +1,175 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const LOC_TYPE_REPORT_TITLE = '搴撲綅绫诲瀷鎶ヨ〃'
+export const LOC_TYPE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+export function createLocTypeSearchState() {
+  return {
+    condition: '',
+    uuid: '',
+    code: '',
+    name: '',
+    regex: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createLocTypeFormState() {
+  return {
+    id: void 0,
+    code: '',
+    name: '',
+    regex: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getLocTypePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getLocTypeStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getLocTypeStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildLocTypeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    uuid: normalizeText(params.uuid),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    regex: normalizeText(params.regex),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildLocTypePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocTypeSearchParams(params)
+  }
+}
+
+export function buildLocTypeSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    regex: normalizeText(formData.regex) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildLocTypeDialogModel(record = {}) {
+  return {
+    ...createLocTypeFormState(),
+    ...(record.id !== undefined && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    regex: normalizeText(record.regex || ''),
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeLocTypeDetailRecord(record = {}) {
+  const statusMeta = getLocTypeStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    uuid: normalizeText(record.uuid || ''),
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    regex: normalizeText(record.regex || ''),
+    memo: normalizeText(record.memo || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeLocTypeListRow(record = {}) {
+  return normalizeLocTypeDetailRecord(record)
+}
+
+export function buildLocTypePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocTypeListRow(record))
+}
+
+export function buildLocTypeReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_TYPE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_TYPE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_TYPE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/loc-type/locTypeTable.columns.js b/rsf-design/src/views/basic-info/loc-type/locTypeTable.columns.js
new file mode 100644
index 0000000..c6ef1c8
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-type/locTypeTable.columns.js
@@ -0,0 +1,115 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getLocTypeStatusMeta } from './locTypePage.helpers'
+
+export function createLocTypeTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'uuid',
+      label: '鏍囪瘑',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.uuid || '--'
+    },
+    {
+      prop: 'code',
+      label: '缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.name || '--'
+    },
+    {
+      prop: 'regex',
+      label: '鏉$爜瑙勫垯',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.regex || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getLocTypeStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/loc-type/modules/loc-type-detail-drawer.vue b/rsf-design/src/views/basic-info/loc-type/modules/loc-type-detail-drawer.vue
new file mode 100644
index 0000000..672caac
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-type/modules/loc-type-detail-drawer.vue
@@ -0,0 +1,57 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撲綅绫诲瀷璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鏍囪瘑">{{ detail.uuid || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉$爜瑙勫垯">{{ detail.regex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/loc-type/modules/loc-type-dialog.vue b/rsf-design/src/views/basic-info/loc-type/modules/loc-type-dialog.vue
new file mode 100644
index 0000000..9c5a74d
--- /dev/null
+++ b/rsf-design/src/views/basic-info/loc-type/modules/loc-type-dialog.vue
@@ -0,0 +1,161 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="920px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocTypeDialogModel,
+    createLocTypeFormState,
+    getLocTypeStatusOptions
+  } from '../locTypePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    locTypeData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createLocTypeFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撲綅绫诲瀷' : '鏂板搴撲綅绫诲瀷'))
+
+  const rules = computed(() => ({
+    code: [{ required: true, message: '璇疯緭鍏ョ紪鐮�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],
+    regex: [{ required: true, message: '璇疯緭鍏ユ潯鐮佽鍒�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鏉$爜瑙勫垯',
+      key: 'regex',
+      type: 'input',
+      span: 24,
+      props: {
+        placeholder: '璇疯緭鍏ユ潯鐮佽鍒�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: getLocTypeStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildLocTypeDialogModel(props.locTypeData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createLocTypeFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.locTypeData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/matnr-group/index.vue b/rsf-design/src/views/basic-info/matnr-group/index.vue
new file mode 100644
index 0000000..dadb999
--- /dev/null
+++ b/rsf-design/src/views/basic-info/matnr-group/index.vue
@@ -0,0 +1,501 @@
+<template>
+  <div class="matnr-group-page art-full-height flex flex-col gap-4 xl:flex-row">
+    <ElCard class="w-full shrink-0 xl:w-[320px]">
+      <div class="mb-3 flex items-center justify-between gap-3">
+        <div>
+          <div class="text-base font-medium text-[var(--art-text-primary)]">鐗╂枡鍒嗙粍</div>
+          <div class="text-xs text-[var(--art-text-secondary)]">
+            {{ selectedGroupLabel }}
+          </div>
+        </div>
+        <ElButton text @click="handleResetGroup">鍏ㄩ儴</ElButton>
+      </div>
+
+      <div class="mb-3 flex items-center gap-2">
+        <ElInput
+          v-model.trim="groupSearch"
+          clearable
+          placeholder="鎼滅储鐗╂枡鍒嗙粍"
+          @clear="handleGroupSearch"
+          @keyup.enter="handleGroupSearch"
+        />
+        <ElButton @click="handleGroupSearch">鎼滅储</ElButton>
+      </div>
+
+      <ElScrollbar class="h-[calc(100vh-260px)] pr-1">
+        <div v-if="groupTreeLoading" class="py-6">
+          <ElSkeleton :rows="10" animated />
+        </div>
+        <ElEmpty v-else-if="!groupTreeData.length" description="鏆傛棤鐗╂枡鍒嗙粍" />
+        <ElTree
+          v-else
+          :data="groupTreeData"
+          :props="treeProps"
+          node-key="id"
+          highlight-current
+          default-expand-all
+          :current-node-key="selectedGroupId"
+          @node-click="handleGroupNodeClick"
+        >
+          <template #default="{ data }">
+            <div class="flex items-center gap-2">
+              <span class="font-medium">{{ data.name || '--' }}</span>
+              <span class="text-xs text-[var(--art-text-secondary)]">{{ data.code || '--' }}</span>
+            </div>
+          </template>
+        </ElTree>
+      </ElScrollbar>
+    </ElCard>
+
+    <div class="min-w-0 flex-1 space-y-4">
+      <ArtSearchBar
+        v-model="searchForm"
+        :items="searchItems"
+        :showExpand="true"
+        @search="handleSearch"
+        @reset="handleReset"
+      />
+
+      <ElCard class="art-table-card">
+        <ArtTableHeader :loading="loading" v-model:columns="columnChecks" @refresh="refreshData">
+          <template #left>
+            <ElSpace wrap>
+              <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鍒嗙粍</ElButton>
+              <ElButton
+                v-auth="'delete'"
+                type="danger"
+                :disabled="selectedRows.length === 0"
+                @click="handleBatchDelete"
+                v-ripple
+              >
+                鎵归噺鍒犻櫎
+              </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>
+
+        <ArtTable
+          :loading="loading"
+          :data="data"
+          :columns="columns"
+          :pagination="pagination"
+          @selection-change="handleSelectionChange"
+          @pagination:size-change="handleSizeChange"
+          @pagination:current-change="handleCurrentChange"
+        />
+
+        <MatnrGroupDialog
+          v-model:visible="dialogVisible"
+          :dialog-type="dialogType"
+          :group-data="currentGroupData"
+          :parent-group-options="parentGroupOptions"
+          :resolve-parent-code="resolveGroupCode"
+          @submit="handleDialogSubmit"
+        />
+
+        <MatnrGroupDetailDrawer
+          v-model:visible="detailDrawerVisible"
+          :loading="detailLoading"
+          :detail="detailData"
+        />
+      </ElCard>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  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 {
+    fetchDeleteMatnrGroup,
+    fetchExportMatnrGroupReport,
+    fetchGetMatnrGroupDetail,
+    fetchGetMatnrGroupMany,
+    fetchMatnrGroupPage,
+    fetchMatnrGroupTree,
+    fetchSaveMatnrGroup,
+    fetchUpdateMatnrGroup
+  } from '@/api/matnr-group'
+  import MatnrGroupDialog from './modules/matnr-group-dialog.vue'
+  import MatnrGroupDetailDrawer from './modules/matnr-group-detail-drawer.vue'
+  import { createMatnrGroupTableColumns } from './matnrGroupTable.columns'
+  import {
+    MATNR_GROUP_REPORT_STYLE,
+    MATNR_GROUP_REPORT_TITLE,
+    buildMatnrGroupDialogModel,
+    buildMatnrGroupPageQueryParams,
+    buildMatnrGroupPrintRows,
+    buildMatnrGroupReportMeta,
+    buildMatnrGroupSavePayload,
+    buildMatnrGroupTreeLookupMap,
+    buildMatnrGroupTreeQueryParams,
+    createMatnrGroupSearchState,
+    createMatnrGroupTreeSelectOptions,
+    getMatnrGroupPaginationKey,
+    normalizeMatnrGroupDetailRecord,
+    normalizeMatnrGroupListRow,
+    normalizeMatnrGroupTreeRows,
+    resolveMatnrGroupTreeNodeLabel
+  } from './matnrGroupPage.helpers'
+
+  defineOptions({ name: 'MatnrGroup' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createMatnrGroupSearchState())
+  const groupSearch = ref('')
+  const groupTreeLoading = ref(false)
+  const groupTreeData = ref([])
+  const selectedGroupId = ref(null)
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = MATNR_GROUP_REPORT_TITLE
+  const reportQueryParams = computed(() =>
+    buildMatnrGroupPageQueryParams({
+      ...searchForm.value,
+      parentId: selectedGroupId.value ?? ''
+    })
+  )
+
+  const groupLookupMap = computed(() => buildMatnrGroupTreeLookupMap(groupTreeData.value))
+  const parentGroupOptions = computed(() => createMatnrGroupTreeSelectOptions(groupTreeData.value))
+
+  const treeProps = {
+    label: 'displayLabel',
+    children: 'children'
+  }
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ垎缁勭紪鐮�/鍚嶇О/澶囨敞'
+      }
+    },
+    {
+      label: '鍒嗙粍缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ垎缁勭紪鐮�'
+      }
+    },
+    {
+      label: '鍒嗙粍鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ垎缁勫悕绉�'
+      }
+    },
+    {
+      label: '涓婄骇缂栫爜',
+      key: 'parCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ笂绾х紪鐮�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resolveGroupCode(id) {
+    return groupLookupMap.value.get(Number(id))?.code || ''
+  }
+
+  function resolveGroupLabel(id) {
+    const node = groupLookupMap.value.get(Number(id))
+    if (!node) {
+      return ''
+    }
+    return node.displayLabel || resolveMatnrGroupTreeNodeLabel(node)
+  }
+
+  const selectedGroupLabel = computed(() => {
+    if (selectedGroupId.value === null || selectedGroupId.value === undefined) {
+      return '鍏ㄩ儴鍒嗙粍'
+    }
+    return resolveGroupLabel(selectedGroupId.value) || '鍏ㄩ儴鍒嗙粍'
+  })
+
+  async function applyTableFilters() {
+    replaceSearchParams(
+      buildMatnrGroupPageQueryParams({
+        ...searchForm.value,
+        parentId: selectedGroupId.value ?? ''
+      })
+    )
+    await getData()
+  }
+
+  async function loadGroupTree() {
+    groupTreeLoading.value = true
+    let selectionCleared = false
+    try {
+      const records = await guardRequestWithMessage(
+        fetchMatnrGroupTree(buildMatnrGroupTreeQueryParams({ condition: groupSearch.value })),
+        [],
+        { timeoutMessage: '鐗╂枡鍒嗙粍鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      const normalizedTree = normalizeMatnrGroupTreeRows(Array.isArray(records) ? records : [])
+      groupTreeData.value = normalizedTree
+      if (selectedGroupId.value && !groupLookupMap.value.get(Number(selectedGroupId.value))) {
+        selectedGroupId.value = null
+        selectionCleared = true
+      }
+      if (selectionCleared) {
+        await applyTableFilters()
+      }
+    } catch (error) {
+      groupTreeData.value = []
+      ElMessage.error(error?.message || '鐗╂枡鍒嗙粍鍔犺浇澶辫触')
+    } finally {
+      groupTreeLoading.value = false
+    }
+    return selectionCleared
+  }
+
+  async function loadGroupDetail(id) {
+    return guardRequestWithMessage(fetchGetMatnrGroupDetail(id), {}, {
+      timeoutMessage: '鐗╂枡鍒嗙粍璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchMatnrGroupPage,
+        apiParams: buildMatnrGroupPageQueryParams(reportQueryParams.value),
+        paginationKey: getMatnrGroupPaginationKey(),
+        columnsFactory: () =>
+          createMatnrGroupTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            resolveParentLabel: resolveGroupLabel,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeMatnrGroupListRow(item, resolveGroupLabel))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentGroupData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () =>
+      buildMatnrGroupDialogModel({}, {
+        parentId: selectedGroupId.value ?? 0,
+        parCode: resolveGroupCode(selectedGroupId.value ?? 0),
+        resolveParentCode: resolveGroupCode
+      }),
+    buildEditModel: (record) => buildMatnrGroupDialogModel(record, { resolveParentCode: resolveGroupCode }),
+    buildSavePayload: (formData) => buildMatnrGroupSavePayload(formData),
+    saveRequest: (payload) => {
+      if (payload.id !== void 0 && payload.id !== null && Number(payload.parentId) === Number(payload.id)) {
+        throw new Error('涓婄骇鍒嗙粍涓嶈兘閫夋嫨褰撳墠鍒嗙粍')
+      }
+      return fetchSaveMatnrGroup(payload)
+    },
+    updateRequest: (payload) => {
+      if (payload.id !== void 0 && payload.id !== null && Number(payload.parentId) === Number(payload.id)) {
+        throw new Error('涓婄骇鍒嗙粍涓嶈兘閫夋嫨褰撳墠鍒嗙粍')
+      }
+      return fetchUpdateMatnrGroup(payload)
+    },
+    deleteRequest: fetchDeleteMatnrGroup,
+    entityName: '鐗╂枡鍒嗙粍',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate: refreshTreeAndTable,
+    refreshUpdate: refreshTreeAndTable,
+    refreshRemove: refreshTreeAndTable
+  })
+  handleDeleteAction = handleDelete
+
+  async function refreshTreeAndTable() {
+    const selectionCleared = await loadGroupTree()
+    if (!selectionCleared) {
+      await refreshData()
+    }
+  }
+
+  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: { ...MATNR_GROUP_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetMatnrGroupMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchMatnrGroupPage({
+        ...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: 'matnr-group.xlsx',
+    requestExport: (payload) =>
+      fetchExportMatnrGroupReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildMatnrGroupPrintRows(records, resolveGroupLabel),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildMatnrGroupReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || MATNR_GROUP_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(
+      buildMatnrGroupPageQueryParams({
+        ...params,
+        parentId: selectedGroupId.value ?? ''
+      })
+    )
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createMatnrGroupSearchState())
+    selectedGroupId.value = null
+    resetSearchParams()
+  }
+
+  async function handleGroupSearch() {
+    await loadGroupTree()
+  }
+
+  async function handleGroupNodeClick(node) {
+    selectedGroupId.value = Number(node?.id || 0) || null
+    await applyTableFilters()
+  }
+
+  async function handleResetGroup() {
+    selectedGroupId.value = null
+    await applyTableFilters()
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await loadGroupDetail(row.id)
+      detailData.value = normalizeMatnrGroupDetailRecord(detail, resolveGroupLabel)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鐗╂枡鍒嗙粍璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await loadGroupDetail(row.id)
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鐗╂枡鍒嗙粍璇︽儏澶辫触')
+    }
+  }
+
+  onMounted(async () => {
+    await loadGroupTree()
+    await getData()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/matnr-group/matnrGroupPage.helpers.js b/rsf-design/src/views/basic-info/matnr-group/matnrGroupPage.helpers.js
new file mode 100644
index 0000000..4db6d5c
--- /dev/null
+++ b/rsf-design/src/views/basic-info/matnr-group/matnrGroupPage.helpers.js
@@ -0,0 +1,311 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const MATNR_GROUP_REPORT_TITLE = '鐗╂枡鍒嗙粍鎶ヨ〃'
+export const MATNR_GROUP_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function buildLabel(name, code) {
+  return [normalizeText(name), normalizeText(code)].filter(Boolean).join(' 路 ') || '--'
+}
+
+function normalizeId(value, fallback = void 0) {
+  const numberValue = normalizeNumber(value, fallback)
+  return numberValue === void 0 ? fallback : numberValue
+}
+
+export function createMatnrGroupSearchState() {
+  return {
+    condition: '',
+    code: '',
+    name: '',
+    parCode: '',
+    status: '',
+    memo: '',
+    sort: '',
+    parentId: ''
+  }
+}
+
+export function createMatnrGroupFormState() {
+  return {
+    id: void 0,
+    parentId: 0,
+    parCode: '',
+    code: '',
+    name: '',
+    sort: 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getMatnrGroupPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getMatnrGroupStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getMatnrGroupStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildMatnrGroupPageQueryParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  ;['condition', 'code', 'name', 'parCode', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.sort !== '' && params.sort !== null && params.sort !== undefined) {
+    result.sort = Number(params.sort)
+  }
+
+  if (params.status !== '' && params.status !== null && params.status !== undefined) {
+    result.status = Number(params.status)
+  }
+
+  if (params.parentId !== '' && params.parentId !== null && params.parentId !== undefined) {
+    result.parentId = Number(params.parentId)
+  }
+
+  return result
+}
+
+export function buildMatnrGroupTreeQueryParams(params = {}) {
+  return {
+    condition: normalizeText(params.condition)
+  }
+}
+
+export function normalizeMatnrGroupTreeRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records.map((item) => {
+    const children = normalizeMatnrGroupTreeRows(item?.children || [])
+    const id = normalizeId(item?.id)
+    const parentId = normalizeId(item?.parentId, 0)
+    const code = normalizeText(item?.code)
+    const name = normalizeText(item?.name)
+    const label = buildLabel(name, code)
+    const statusMeta = getMatnrGroupStatusMeta(item?.statusBool ?? item?.status)
+
+    return {
+      ...item,
+      id,
+      parentId,
+      code,
+      name,
+      label,
+      displayLabel: label,
+      parCode: normalizeText(item?.parCode),
+      sort: normalizeId(item?.sort, 0),
+      status: normalizeId(item?.status),
+      statusText: statusMeta.text,
+      statusType: statusMeta.type,
+      memo: normalizeText(item?.memo) || '-',
+      children
+    }
+  })
+}
+
+export function createMatnrGroupTreeSelectOptions(records = []) {
+  const tree = normalizeMatnrGroupTreeRows(records)
+  return [
+    {
+      id: 0,
+      name: '椤剁骇鍒嗙粍',
+      code: '',
+      label: '椤剁骇鍒嗙粍',
+      displayLabel: '椤剁骇鍒嗙粍',
+      children: tree
+    }
+  ]
+}
+
+export function createMatnrGroupTreeLookupMap(records = []) {
+  const map = new Map()
+
+  const visit = (nodes = []) => {
+    nodes.forEach((node) => {
+      if (!node || typeof node !== 'object') {
+        return
+      }
+      const id = normalizeId(node.id)
+      if (id !== void 0) {
+        map.set(id, node)
+      }
+      if (Array.isArray(node.children) && node.children.length) {
+        visit(node.children)
+      }
+    })
+  }
+
+  visit(records)
+  map.set(0, {
+    id: 0,
+    name: '椤剁骇鍒嗙粍',
+    code: '',
+    label: '椤剁骇鍒嗙粍',
+    displayLabel: '椤剁骇鍒嗙粍'
+  })
+  return map
+}
+
+export function buildMatnrGroupTreeLookupMap(records = []) {
+  return createMatnrGroupTreeLookupMap(records)
+}
+
+export function resolveMatnrGroupTreeNodeLabel(node = {}) {
+  return buildLabel(node?.name, node?.code)
+}
+
+export function buildMatnrGroupDialogModel(record = {}, options = {}) {
+  const resolveParentCode = typeof options.resolveParentCode === 'function' ? options.resolveParentCode : null
+  const parentId =
+    record.parentId !== void 0 && record.parentId !== null && record.parentId !== ''
+      ? normalizeId(record.parentId, 0)
+      : normalizeId(options.parentId, 0)
+  const parentCode =
+    normalizeText(record.parCode) ||
+    normalizeText(options.parCode) ||
+    (resolveParentCode ? normalizeText(resolveParentCode(parentId)) : '')
+
+  return {
+    ...createMatnrGroupFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    parentId,
+    parCode: parentCode,
+    code: normalizeText(record.code || ''),
+    name: normalizeText(record.name || ''),
+    sort:
+      record.sort !== void 0 && record.sort !== null && record.sort !== ''
+        ? Number(record.sort)
+        : 0,
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function buildMatnrGroupSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    parentId:
+      formData.parentId !== void 0 && formData.parentId !== null && formData.parentId !== ''
+        ? Number(formData.parentId)
+        : 0,
+    parCode: normalizeText(formData.parCode) || '',
+    code: normalizeText(formData.code) || '',
+    name: normalizeText(formData.name) || '',
+    sort:
+      formData.sort !== void 0 && formData.sort !== null && formData.sort !== ''
+        ? Number(formData.sort)
+        : 0,
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function normalizeMatnrGroupDetailRecord(record = {}, resolveParentLabel) {
+  const statusMeta = getMatnrGroupStatusMeta(record.statusBool ?? record.status)
+  const parentId = normalizeId(record.parentId, 0)
+  const parentLabel =
+    typeof resolveParentLabel === 'function'
+      ? normalizeText(resolveParentLabel(parentId))
+      : normalizeText(record.parentLabel || record.parentLabelText || '')
+
+  return {
+    ...record,
+    id: normalizeId(record.id),
+    parentId,
+    parentLabel: parentLabel || (parentId === 0 ? '椤剁骇鍒嗙粍' : '--'),
+    code: normalizeText(record.code) || '--',
+    name: normalizeText(record.name) || '--',
+    parCode: normalizeText(record.parCode) || '--',
+    sort: normalizeId(record.sort, 0),
+    status: normalizeId(record.status),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo) || '--',
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeMatnrGroupListRow(record = {}, resolveParentLabel) {
+  return normalizeMatnrGroupDetailRecord(record, resolveParentLabel)
+}
+
+export function buildMatnrGroupPrintRows(records = [], resolveParentLabel) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeMatnrGroupListRow(record, resolveParentLabel))
+}
+
+export function buildMatnrGroupReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = MATNR_GROUP_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: MATNR_GROUP_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...MATNR_GROUP_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js b/rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js
new file mode 100644
index 0000000..aaf9e3e
--- /dev/null
+++ b/rsf-design/src/views/basic-info/matnr-group/matnrGroupTable.columns.js
@@ -0,0 +1,131 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getMatnrGroupStatusMeta } from './matnrGroupPage.helpers'
+
+export function createMatnrGroupTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  resolveParentLabel,
+  canEdit = true,
+  canDelete = true
+}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'name',
+      label: '鍒嗙粍鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'code',
+      label: '鍒嗙粍缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'parentLabel',
+      label: '涓婄骇鍒嗙粍',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => {
+        if (typeof resolveParentLabel === 'function') {
+          const resolved = resolveParentLabel(row.parentId)
+          if (resolved) {
+            return resolved
+          }
+        }
+        return row.parentLabel || (Number(row.parentId) === 0 ? '椤剁骇鍒嗙粍' : '--')
+      }
+    },
+    {
+      prop: 'parCode',
+      label: '涓婄骇缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭',
+      width: 90,
+      align: 'center'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getMatnrGroupStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-detail-drawer.vue b/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-detail-drawer.vue
new file mode 100644
index 0000000..3659726
--- /dev/null
+++ b/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-detail-drawer.vue
@@ -0,0 +1,58 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鐗╂枡鍒嗙粍璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鍒嗙粍鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒嗙粍缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓婄骇鍒嗙粍">{{ detail.parentLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓婄骇缂栫爜">{{ detail.parCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎺掑簭">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-dialog.vue b/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-dialog.vue
new file mode 100644
index 0000000..d888371
--- /dev/null
+++ b/rsf-design/src/views/basic-info/matnr-group/modules/matnr-group-dialog.vue
@@ -0,0 +1,202 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildMatnrGroupDialogModel, createMatnrGroupFormState, getMatnrGroupStatusOptions } from '../matnrGroupPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    groupData: { type: Object, default: () => ({}) },
+    parentGroupOptions: { type: Array, default: () => [] },
+    resolveParentCode: { type: Function, default: null }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createMatnrGroupFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鐗╂枡鍒嗙粍' : '鏂板鐗╂枡鍒嗙粍'))
+
+  const rules = computed(() => ({
+    parentId: [{ required: true, message: '璇烽�夋嫨涓婄骇鍒嗙粍', trigger: 'change' }],
+    code: [{ required: true, message: '璇疯緭鍏ュ垎缁勭紪鐮�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ垎缁勫悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '涓婄骇鍒嗙粍',
+      key: 'parentId',
+      type: 'treeselect',
+      span: 24,
+      props: {
+        data: props.parentGroupOptions,
+        props: {
+          label: 'displayLabel',
+          value: 'id',
+          children: 'children'
+        },
+        placeholder: '璇烽�夋嫨涓婄骇鍒嗙粍',
+        clearable: false,
+        checkStrictly: true,
+        defaultExpandAll: true,
+        onChange: handleParentChange
+      }
+    },
+    {
+      label: '涓婄骇缂栫爜',
+      key: 'parCode',
+      type: 'input',
+      props: {
+        placeholder: '鑷姩甯﹀嚭涓婄骇缂栫爜',
+        clearable: true,
+        readonly: true
+      }
+    },
+    {
+      label: '鍒嗙粍缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ垎缁勭紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍒嗙粍鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ垎缁勫悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鎺掑簭',
+      key: 'sort',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getMatnrGroupStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createMatnrGroupFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(
+      form,
+      buildMatnrGroupDialogModel(props.groupData, {
+        parentId: props.groupData?.parentId ?? 0,
+        parCode: props.groupData?.parCode || '',
+        resolveParentCode: props.resolveParentCode
+      })
+    )
+  }
+
+  function handleParentChange(value) {
+    if (typeof props.resolveParentCode === 'function') {
+      form.parCode = String(props.resolveParentCode(value) || '').trim()
+    }
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.groupData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-merge/index.vue b/rsf-design/src/views/basic-info/task-path-template-merge/index.vue
new file mode 100644
index 0000000..7203b7f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-merge/index.vue
@@ -0,0 +1,323 @@
+<template>
+  <div class="task-path-template-merge-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鍚堝苟</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <TaskPathTemplateMergeDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :task-path-template-merge-data="currentTaskPathTemplateMergeData"
+        :type-options="typeOptions"
+        :template-options="templateOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <TaskPathTemplateMergeDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchTaskPathTemplateList } from '@/api/task-path-template'
+  import {
+    fetchDeleteTaskPathTemplateMerge,
+    fetchExportTaskPathTemplateMergeReport,
+    fetchGetTaskPathTemplateMergeDetail,
+    fetchGetTaskPathTemplateMergeMany,
+    fetchSaveTaskPathTemplateMerge,
+    fetchTaskPathTemplateMergeCreateSelectList,
+    fetchTaskPathTemplateMergePage,
+    fetchUpdateTaskPathTemplateMerge
+  } from '@/api/task-path-template-merge'
+  import TaskPathTemplateMergeDialog from './modules/task-path-template-merge-dialog.vue'
+  import TaskPathTemplateMergeDetailDrawer from './modules/task-path-template-merge-detail-drawer.vue'
+  import { createTaskPathTemplateMergeTableColumns } from './taskPathTemplateMergeTable.columns'
+  import {
+    TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE,
+    TASK_PATH_TEMPLATE_MERGE_REPORT_TITLE,
+    buildTaskPathTemplateMergeConditionOptions,
+    buildTaskPathTemplateMergeDialogModel,
+    buildTaskPathTemplateMergePageQueryParams,
+    buildTaskPathTemplateMergePrintRows,
+    buildTaskPathTemplateMergeReportMeta,
+    buildTaskPathTemplateMergeSavePayload,
+    buildTaskPathTemplateMergeSearchParams,
+    buildTaskPathTemplateMergeTypeOptions,
+    createTaskPathTemplateMergeSearchState,
+    getTaskPathTemplateMergePaginationKey,
+    normalizeTaskPathTemplateMergeListRow
+  } from './taskPathTemplateMergePage.helpers'
+
+  defineOptions({ name: 'TaskPathTemplateMerge' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createTaskPathTemplateMergeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const typeOptions = ref([])
+  const templateOptions = ref([])
+  let handleDeleteAction = null
+
+  const reportTitle = TASK_PATH_TEMPLATE_MERGE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskPathTemplateMergeSearchParams(searchForm.value))
+
+  const typeLabelMap = computed(() => new Map(typeOptions.value.map((item) => [String(item.value), item.label])))
+  const templateLabelMap = computed(
+    () => new Map(templateOptions.value.map((item) => [String(item.value), item.label]))
+  )
+
+  function resolveTypeLabel(value) {
+    return typeLabelMap.value.get(String(value)) || String(value ?? '')
+  }
+
+  function resolveTemplateLabel(value) {
+    if (Array.isArray(value)) {
+      return value
+        .map((item) => templateLabelMap.value.get(String(item)) || String(item ?? ''))
+        .filter(Boolean)
+        .join('銆�')
+    }
+    return templateLabelMap.value.get(String(value)) || String(value ?? '')
+  }
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユā鏉跨紪鐮�/鍚嶇О/鏉′欢鎻忚堪' } },
+    { label: '妯℃澘缂栫爜', key: 'templateCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユā鏉跨紪鐮�' } },
+    { label: '妯℃澘鍚嶇О', key: 'templateName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユā鏉垮悕绉�' } },
+    { label: '璧风偣绫诲瀷', key: 'sourceType', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ捣鐐圭被鍨�' } },
+    { label: '缁堢偣绫诲瀷', key: 'targetType', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ粓鐐圭被鍨�' } },
+    { label: '鏉′欢琛ㄨ揪寮�', key: 'conditionExpression', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ潯浠惰〃杈惧紡' } },
+    { label: '鏉′欢鎻忚堪', key: 'conditionDesc', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ潯浠舵弿杩�' } },
+    { label: '鐗堟湰鍙�', key: 'version', type: 'number', props: { min: 1, controlsPosition: 'right', placeholder: '璇疯緭鍏ョ増鏈彿' } },
+    { label: '褰撳墠鐗堟湰', key: 'isCurrent', type: 'select', props: { clearable: true, options: [{ label: '褰撳墠鐗堟湰', value: 1 }, { label: '鍘嗗彶鐗堟湰', value: 0 }] } },
+    { label: '浼樺厛绾�', key: 'priority', type: 'number', props: { min: 1, controlsPosition: 'right', placeholder: '璇疯緭鍏ヤ紭鍏堢骇' } },
+    { label: '瓒呮椂(鍒�)', key: 'timeoutMinutes', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�' } },
+    { label: '鏈�澶ч噸璇�', key: 'maxRetryTimes', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ユ渶澶ч噸璇曟鏁�' } },
+    { label: '閲嶈瘯闂撮殧(绉�)', key: 'retryIntervalSeconds', type: 'number', props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ラ噸璇曢棿闅�' } },
+    { label: '姝ュ簭闀垮害', key: 'stepSize', type: 'number', props: { min: 1, controlsPosition: 'right', placeholder: '璇疯緭鍏ユ搴忛暱搴�' } },
+    { label: '鐘舵��', key: 'status', type: 'select', props: { clearable: true, options: [{ label: '鍚敤', value: 1 }, { label: '绂佺敤', value: 0 }] } },
+    { label: '鐢熸晥鏃堕棿', key: 'effectiveTime', type: 'datetime', props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss', placeholder: '璇烽�夋嫨鐢熸晥鏃堕棿' } },
+    { label: '澶辨晥鏃堕棿', key: 'expireTime', type: 'datetime', props: { type: 'datetime', valueFormat: 'YYYY-MM-DD HH:mm:ss', format: 'YYYY-MM-DD HH:mm:ss', placeholder: '璇烽�夋嫨澶辨晥鏃堕棿' } }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateMergeDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘鍚堝苟璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeTaskPathTemplateMergeListRow(detail, resolveTypeLabel, templateLabelMap.value)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘鍚堝苟璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateMergeDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘鍚堝苟璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘鍚堝苟璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchTaskPathTemplateMergePage,
+        apiParams: buildTaskPathTemplateMergePageQueryParams(searchForm.value),
+        paginationKey: getTaskPathTemplateMergePaginationKey(),
+        columnsFactory: () =>
+          createTaskPathTemplateMergeTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            resolveTypeLabel,
+            resolveTemplateLabel,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) =>
+            normalizeTaskPathTemplateMergeListRow(item, resolveTypeLabel, templateLabelMap.value)
+          )
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentTaskPathTemplateMergeData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildTaskPathTemplateMergeDialogModel({}),
+    buildEditModel: (record) => buildTaskPathTemplateMergeDialogModel(record),
+    buildSavePayload: (formData) => buildTaskPathTemplateMergeSavePayload(formData),
+    saveRequest: fetchSaveTaskPathTemplateMerge,
+    updateRequest: fetchUpdateTaskPathTemplateMerge,
+    deleteRequest: fetchDeleteTaskPathTemplateMerge,
+    entityName: '浠诲姟璺緞妯℃澘鍚堝苟',
+    resolveRecordLabel: (record) => record?.templateCode || record?.templateName || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskPathTemplateMergeMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskPathTemplateMergePage({
+        ...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: 'task-path-template-merge.xlsx',
+      requestExport: (payload) =>
+        fetchExportTaskPathTemplateMergeReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildTaskPathTemplateMergePrintRows(records, resolveTypeLabel, templateLabelMap.value),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildTaskPathTemplateMergeReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskPathTemplateMergeSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskPathTemplateMergeSearchState())
+    resetSearchParams()
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(fetchTaskPathTemplateMergeCreateSelectList(), [], {
+      timeoutMessage: '璧风偣/缁堢偣绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    typeOptions.value = buildTaskPathTemplateMergeTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadTemplateOptions() {
+    const response = await guardRequestWithMessage(fetchTaskPathTemplateList(), [], {
+      timeoutMessage: '鏉′欢琛ㄨ揪寮忔ā鏉垮姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+    })
+    templateOptions.value = buildTaskPathTemplateMergeConditionOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.all([loadTypeOptions(), loadTemplateOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-detail-drawer.vue b/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-detail-drawer.vue
new file mode 100644
index 0000000..447b123
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-detail-drawer.vue
@@ -0,0 +1,83 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟璺緞妯℃澘鍚堝苟璇︽儏"
+    size="920px"
+    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">
+          <template #header>
+            <div class="flex items-center justify-between gap-3">
+              <div>
+                <h3 class="m-0 text-base font-semibold">
+                  {{ detail.templateCode || detail.templateName || '浠诲姟璺緞妯℃澘鍚堝苟' }}
+                </h3>
+                <p class="m-0 text-sm text-[var(--art-text-secondary)]">
+                  杩欓噷灞曠ず鐨勬槸鍚庣鐪熷疄璁板綍鐨勭粍鍚堣鎯咃紝涓嶅仛棰濆鎺ㄦ紨銆�
+                </p>
+              </div>
+              <ElSpace wrap>
+                <ElTag :type="detail.isCurrentType || 'info'" effect="light">
+                  {{ detail.isCurrentText || '--' }}
+                </ElTag>
+                <ElTag :type="detail.statusType || 'info'" effect="light">
+                  {{ detail.statusText || '--' }}
+                </ElTag>
+              </ElSpace>
+            </div>
+          </template>
+
+          <ElDescriptions :column="2" border>
+            <ElDescriptionsItem label="妯℃澘缂栫爜">{{ detail.templateCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="妯℃澘鍚嶇О">{{ detail.templateName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璧风偣绫诲瀷">{{ detail.sourceTypeText || detail.sourceType || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="缁堢偣绫诲瀷">{{ detail.targetTypeText || detail.targetType || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉′欢琛ㄨ揪寮�">{{ detail.conditionExpressionText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉′欢鎻忚堪">{{ detail.conditionDesc || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗堟湰鍙�">{{ detail.version ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浼樺厛绾�">{{ detail.priority ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="褰撳墠鐗堟湰">{{ detail.isCurrentText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐢熸晥鏃堕棿">{{ detail.effectiveTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="澶辨晥鏃堕棿">{{ detail.expireTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瓒呮椂(鍒�)">{{ detail.timeoutMinutes ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏈�澶ч噸璇�">{{ detail.maxRetryTimes ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閲嶈瘯闂撮殧(绉�)">{{ detail.retryIntervalSeconds ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="姝ュ簭闀垮害">{{ detail.stepSize ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-dialog.vue b/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-dialog.vue
new file mode 100644
index 0000000..5616749
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-merge/modules/task-path-template-merge-dialog.vue
@@ -0,0 +1,320 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="1040px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTaskPathTemplateMergeDialogModel,
+    createTaskPathTemplateMergeFormState,
+    getTaskPathTemplateMergeCurrentOptions,
+    getTaskPathTemplateMergeStatusOptions
+  } from '../taskPathTemplateMergePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    taskPathTemplateMergeData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] },
+    templateOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTaskPathTemplateMergeFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫浠诲姟璺緞妯℃澘鍚堝苟' : '鏂板浠诲姟璺緞妯℃澘鍚堝苟'))
+
+  const rules = computed(() => {
+    const baseRules = {
+      conditionExpression: [{ required: true, message: '璇烽�夋嫨鏉′欢琛ㄨ揪寮�', trigger: 'change' }],
+      conditionDesc: [{ required: true, message: '璇疯緭鍏ユ潯浠舵弿杩�', trigger: 'blur' }],
+      version: [{ required: true, message: '璇疯緭鍏ョ増鏈彿', trigger: 'blur' }],
+      isCurrent: [{ required: true, message: '璇烽�夋嫨鏄惁褰撳墠鐗堟湰', trigger: 'change' }],
+      effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }],
+      priority: [{ required: true, message: '璇疯緭鍏ヤ紭鍏堢骇', trigger: 'blur' }],
+      status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+    }
+
+    if (isEdit.value) {
+      baseRules.sourceType = [{ required: true, message: '璇烽�夋嫨璧风偣绫诲瀷', trigger: 'change' }]
+      baseRules.targetType = [{ required: true, message: '璇烽�夋嫨缁堢偣绫诲瀷', trigger: 'change' }]
+    } else {
+      baseRules.sourceTypeR = [{ type: 'array', required: true, message: '璇烽�夋嫨璧风偣绫诲瀷', trigger: 'change' }]
+      baseRules.targetTypeR = [{ type: 'array', required: true, message: '璇烽�夋嫨缁堢偣绫诲瀷', trigger: 'change' }]
+    }
+
+    return baseRules
+  })
+
+  const commonItems = () => [
+    {
+      label: '鏉′欢琛ㄨ揪寮�',
+      key: 'conditionExpression',
+      type: 'select',
+      span: 24,
+      props: {
+        placeholder: '璇烽�夋嫨鏉′欢琛ㄨ揪寮�',
+        clearable: true,
+        filterable: true,
+        options: props.templateOptions || []
+      }
+    },
+    {
+      label: '鏉′欢鎻忚堪',
+      key: 'conditionDesc',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ユ潯浠舵弿杩�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐗堟湰鍙�',
+      key: 'version',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ増鏈彿'
+      }
+    },
+    {
+      label: '鏄惁褰撳墠鐗堟湰',
+      key: 'isCurrent',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁褰撳墠鐗堟湰',
+        clearable: true,
+        options: getTaskPathTemplateMergeCurrentOptions()
+      }
+    },
+    {
+      label: '鐢熸晥鏃堕棿',
+      key: 'effectiveTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨鐢熸晥鏃堕棿'
+      }
+    },
+    {
+      label: '澶辨晥鏃堕棿',
+      key: 'expireTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨澶辨晥鏃堕棿'
+      }
+    },
+    {
+      label: '浼樺厛绾�',
+      key: 'priority',
+      type: 'number',
+      props: {
+        min: 1,
+        max: 99,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ紭鍏堢骇'
+      }
+    },
+    {
+      label: '瓒呮椂(鍒�)',
+      key: 'timeoutMinutes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�'
+      }
+    },
+    {
+      label: '鏈�澶ч噸璇曟鏁�',
+      key: 'maxRetryTimes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ渶澶ч噸璇曟鏁�'
+      }
+    },
+    {
+      label: '閲嶈瘯闂撮殧(绉�)',
+      key: 'retryIntervalSeconds',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ噸璇曢棿闅�'
+      }
+    },
+    {
+      label: '姝ュ簭闀垮害',
+      key: 'stepSize',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ搴忛暱搴�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getTaskPathTemplateMergeStatusOptions()
+      }
+    }
+  ]
+
+  const formItems = computed(() => {
+    if (isEdit.value) {
+      return [
+        {
+          label: '璧风偣绫诲瀷',
+          key: 'sourceType',
+          type: 'select',
+          props: {
+            placeholder: '璇烽�夋嫨璧风偣绫诲瀷',
+            clearable: true,
+            filterable: true,
+            options: props.typeOptions || []
+          }
+        },
+        {
+          label: '缁堢偣绫诲瀷',
+          key: 'targetType',
+          type: 'select',
+          props: {
+            placeholder: '璇烽�夋嫨缁堢偣绫诲瀷',
+            clearable: true,
+            filterable: true,
+            options: props.typeOptions || []
+          }
+        },
+        ...commonItems()
+      ]
+    }
+
+    return [
+      {
+        label: '璧风偣绫诲瀷',
+        key: 'sourceTypeR',
+        type: 'select',
+        span: 24,
+        props: {
+          placeholder: '璇烽�夋嫨璧风偣绫诲瀷',
+          clearable: true,
+          multiple: true,
+          collapseTags: true,
+          filterable: true,
+          options: props.typeOptions || []
+        }
+      },
+      {
+        label: '缁堢偣绫诲瀷',
+        key: 'targetTypeR',
+        type: 'select',
+        span: 24,
+        props: {
+          placeholder: '璇烽�夋嫨缁堢偣绫诲瀷',
+          clearable: true,
+          multiple: true,
+          collapseTags: true,
+          filterable: true,
+          options: props.typeOptions || []
+        }
+      },
+      ...commonItems()
+    ]
+  })
+
+  const loadFormData = () => {
+    Object.assign(form, buildTaskPathTemplateMergeDialogModel(props.taskPathTemplateMergeData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createTaskPathTemplateMergeFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.taskPathTemplateMergeData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergePage.helpers.js b/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergePage.helpers.js
new file mode 100644
index 0000000..965931a
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergePage.helpers.js
@@ -0,0 +1,484 @@
+const STATUS_META = {
+  1: { text: '鍚敤', type: 'success', bool: true },
+  0: { text: '绂佺敤', type: 'danger', bool: false }
+}
+
+const CURRENT_META = {
+  1: { text: '褰撳墠鐗堟湰', type: 'success', bool: true },
+  0: { text: '鍘嗗彶鐗堟湰', type: 'info', bool: false }
+}
+
+export const TASK_PATH_TEMPLATE_MERGE_REPORT_TITLE = '浠诲姟璺緞妯℃澘鍚堝苟鎶ヨ〃'
+export const TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeStringList(value) {
+  if (Array.isArray(value)) {
+    return value.map((item) => normalizeText(item)).filter(Boolean)
+  }
+  if (value === null || value === undefined || value === '') {
+    return []
+  }
+  if (typeof value === 'string') {
+    const text = value.trim()
+    if (!text) {
+      return []
+    }
+    if (text.startsWith('[')) {
+      try {
+        const parsed = JSON.parse(text)
+        if (Array.isArray(parsed)) {
+          return parsed.map((item) => normalizeText(item)).filter(Boolean)
+        }
+      } catch {
+        return [text]
+      }
+    }
+    return text.split(/[;,锛孿n\r]+/g).map((item) => item.trim()).filter(Boolean)
+  }
+  return [normalizeText(value)].filter(Boolean)
+}
+
+function normalizeIntegerList(value) {
+  if (Array.isArray(value)) {
+    return value
+      .map((item) => {
+        if (item === null || item === undefined || item === '') {
+          return null
+        }
+        const numberValue = Number(item)
+        return Number.isNaN(numberValue) ? null : numberValue
+      })
+      .filter((item) => item !== null)
+  }
+  if (value === null || value === undefined || value === '') {
+    return []
+  }
+  if (typeof value === 'string') {
+    const text = value.trim()
+    if (!text) {
+      return []
+    }
+    if (text.startsWith('[')) {
+      try {
+        const parsed = JSON.parse(text)
+        if (Array.isArray(parsed)) {
+          return parsed
+            .map((item) => {
+              const numberValue = Number(item)
+              return Number.isNaN(numberValue) ? null : numberValue
+            })
+            .filter((item) => item !== null)
+        }
+      } catch {
+        return text
+          .split(/[;,锛孿n\r]+/g)
+          .map((item) => Number(item.trim()))
+          .filter((item) => !Number.isNaN(item))
+      }
+    }
+    const numberValue = Number(text)
+    return Number.isNaN(numberValue) ? [] : [numberValue]
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? [] : [numberValue]
+}
+
+export function createTaskPathTemplateMergeSearchState() {
+  return {
+    condition: '',
+    templateCode: '',
+    templateName: '',
+    sourceType: '',
+    targetType: '',
+    conditionExpression: '',
+    conditionDesc: '',
+    version: '',
+    isCurrent: '',
+    effectiveTime: '',
+    expireTime: '',
+    priority: '',
+    timeoutMinutes: '',
+    maxRetryTimes: '',
+    retryIntervalSeconds: '',
+    stepSize: '',
+    status: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createTaskPathTemplateMergeFormState() {
+  return {
+    id: void 0,
+    templateCode: '',
+    templateName: '',
+    sourceTypeR: [],
+    targetTypeR: [],
+    sourceType: '',
+    targetType: '',
+    conditionExpression: '',
+    conditionDesc: '',
+    version: 1,
+    isCurrent: 1,
+    effectiveTime: '',
+    expireTime: '',
+    priority: 1,
+    timeoutMinutes: '',
+    maxRetryTimes: 3,
+    retryIntervalSeconds: 60,
+    stepSize: '',
+    status: 1
+  }
+}
+
+export function getTaskPathTemplateMergePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getTaskPathTemplateMergeStatusOptions() {
+  return [
+    { label: '鍚敤', value: 1 },
+    { label: '绂佺敤', value: 0 }
+  ]
+}
+
+export function getTaskPathTemplateMergeCurrentOptions() {
+  return [
+    { label: '褰撳墠鐗堟湰', value: 1 },
+    { label: '鍘嗗彶鐗堟湰', value: 0 }
+  ]
+}
+
+export function getTaskPathTemplateMergeStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function getTaskPathTemplateMergeCurrentMeta(isCurrent) {
+  if (isCurrent === true || Number(isCurrent) === 1) {
+    return CURRENT_META[1]
+  }
+  if (isCurrent === false || Number(isCurrent) === 0) {
+    return CURRENT_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildTaskPathTemplateMergeTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: String(value),
+        label: normalizeText(item.name || item.label || `绫诲瀷 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildTaskPathTemplateMergeConditionOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') {
+        return null
+      }
+      const value = item.id ?? item.value
+      if (value === void 0 || value === null || value === '') {
+        return null
+      }
+      return {
+        value: Number(value),
+        label: normalizeText(item.templateName || item.templateCode || item.name || `妯℃澘 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function buildTaskPathTemplateMergeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    templateCode: normalizeText(params.templateCode),
+    templateName: normalizeText(params.templateName),
+    sourceType: normalizeText(params.sourceType),
+    targetType: normalizeText(params.targetType),
+    conditionExpression: normalizeText(params.conditionExpression),
+    conditionDesc: normalizeText(params.conditionDesc),
+    version:
+      params.version !== undefined && params.version !== null && params.version !== ''
+        ? Number(params.version)
+        : void 0,
+    isCurrent:
+      params.isCurrent !== undefined && params.isCurrent !== null && params.isCurrent !== ''
+        ? Number(params.isCurrent)
+        : void 0,
+    priority:
+      params.priority !== undefined && params.priority !== null && params.priority !== ''
+        ? Number(params.priority)
+        : void 0,
+    timeoutMinutes:
+      params.timeoutMinutes !== undefined && params.timeoutMinutes !== null && params.timeoutMinutes !== ''
+        ? Number(params.timeoutMinutes)
+        : void 0,
+    maxRetryTimes:
+      params.maxRetryTimes !== undefined && params.maxRetryTimes !== null && params.maxRetryTimes !== ''
+        ? Number(params.maxRetryTimes)
+        : void 0,
+    retryIntervalSeconds:
+      params.retryIntervalSeconds !== undefined && params.retryIntervalSeconds !== null && params.retryIntervalSeconds !== ''
+        ? Number(params.retryIntervalSeconds)
+        : void 0,
+    stepSize:
+      params.stepSize !== undefined && params.stepSize !== null && params.stepSize !== ''
+        ? Number(params.stepSize)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    effectiveTime: normalizeText(params.effectiveTime),
+    expireTime: normalizeText(params.expireTime),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplateMergePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskPathTemplateMergeSearchParams(params)
+  }
+}
+
+export function buildTaskPathTemplateMergeSavePayload(formData = {}) {
+  const isEditMode = formData.id !== void 0 && formData.id !== null && formData.id !== ''
+  const sourceTypeR = normalizeStringList(formData.sourceTypeR)
+  const targetTypeR = normalizeStringList(formData.targetTypeR)
+  const sourceType = normalizeText(formData.sourceType || '')
+  const targetType = normalizeText(formData.targetType || '')
+  const conditionExpression = normalizeIntegerList(formData.conditionExpression)
+  const derivedSource = sourceType || sourceTypeR[0] || ''
+  const derivedTarget = targetType || targetTypeR[0] || ''
+  const templateName =
+    normalizeText(formData.templateName || '') || (derivedSource && derivedTarget ? `${derivedSource}==>${derivedTarget}` : '')
+  const templateCode = normalizeText(formData.templateCode || '') || templateName
+
+  const payload = {
+    ...(isEditMode ? { id: Number(formData.id) } : {}),
+    templateCode,
+    templateName,
+    conditionExpression,
+    conditionDesc: normalizeText(formData.conditionDesc || ''),
+    version:
+      formData.version !== void 0 && formData.version !== null && formData.version !== ''
+        ? Number(formData.version)
+        : 1,
+    isCurrent:
+      formData.isCurrent !== void 0 && formData.isCurrent !== null && formData.isCurrent !== ''
+        ? Number(formData.isCurrent)
+        : 1,
+    effectiveTime: normalizeText(formData.effectiveTime || ''),
+    expireTime: normalizeText(formData.expireTime || ''),
+    priority:
+      formData.priority !== void 0 && formData.priority !== null && formData.priority !== ''
+        ? Number(formData.priority)
+        : 1,
+    ...(formData.timeoutMinutes !== void 0 && formData.timeoutMinutes !== null && formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    maxRetryTimes:
+      formData.maxRetryTimes !== void 0 && formData.maxRetryTimes !== null && formData.maxRetryTimes !== ''
+        ? Number(formData.maxRetryTimes)
+        : 3,
+    retryIntervalSeconds:
+      formData.retryIntervalSeconds !== void 0 &&
+      formData.retryIntervalSeconds !== null &&
+      formData.retryIntervalSeconds !== ''
+        ? Number(formData.retryIntervalSeconds)
+        : 60,
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    ...(formData.stepSize !== void 0 && formData.stepSize !== null && formData.stepSize !== ''
+      ? { stepSize: Number(formData.stepSize) }
+      : {})
+  }
+
+  if (isEditMode) {
+    payload.sourceType = sourceType
+    payload.targetType = targetType
+  } else {
+    payload.sourceTypeR = sourceTypeR
+    payload.targetTypeR = targetTypeR
+  }
+
+  return payload
+}
+
+export function buildTaskPathTemplateMergeDialogModel(record = {}) {
+  const sourceTypeR = normalizeStringList(record.sourceTypeR)
+  const targetTypeR = normalizeStringList(record.targetTypeR)
+  const conditionExpression = normalizeIntegerList(record.conditionExpression)
+
+  return {
+    ...createTaskPathTemplateMergeFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    templateCode: normalizeText(record.templateCode || ''),
+    templateName: normalizeText(record.templateName || ''),
+    sourceTypeR,
+    targetTypeR,
+    sourceType: normalizeText(record.sourceType || ''),
+    targetType: normalizeText(record.targetType || ''),
+    conditionExpression: conditionExpression.length ? conditionExpression[0] : '',
+    conditionDesc: normalizeText(record.conditionDesc || ''),
+    version: normalizeNumber(record.version, 1),
+    isCurrent: normalizeNumber(record.isCurrent, 1),
+    effectiveTime: normalizeText(record.effectiveTime$ || record.effectiveTime || ''),
+    expireTime: normalizeText(record.expireTime$ || record.expireTime || ''),
+    priority: normalizeNumber(record.priority, 1),
+    timeoutMinutes: normalizeNumber(record.timeoutMinutes, ''),
+    maxRetryTimes: normalizeNumber(record.maxRetryTimes, 3),
+    retryIntervalSeconds: normalizeNumber(record.retryIntervalSeconds, 60),
+    stepSize: normalizeNumber(record.stepSize, ''),
+    status: normalizeNumber(record.status, 1)
+  }
+}
+
+function resolveConditionExpressionText(value, templateOptionsMap) {
+  const list = normalizeIntegerList(value)
+  if (!list.length) {
+    return ''
+  }
+  return list
+    .map((item) => templateOptionsMap?.get(String(item)) || templateOptionsMap?.get(Number(item)) || String(item))
+    .filter(Boolean)
+    .join('銆�')
+}
+
+export function normalizeTaskPathTemplateMergeDetailRecord(
+  record = {},
+  resolveTypeLabel,
+  templateOptionsMap
+) {
+  const sourceType = normalizeText(record.sourceType || '')
+  const targetType = normalizeText(record.targetType || '')
+  const conditionExpression = normalizeIntegerList(record.conditionExpression)
+  const statusMeta = getTaskPathTemplateMergeStatusMeta(record.statusBool ?? record.status)
+  const currentMeta = getTaskPathTemplateMergeCurrentMeta(record.isCurrent)
+  const sourceTypeText =
+    normalizeText(record.sourceType$ || record.sourceTypeText || '') ||
+    (typeof resolveTypeLabel === 'function' ? normalizeText(resolveTypeLabel(sourceType)) : '')
+  const targetTypeText =
+    normalizeText(record.targetType$ || record.targetTypeText || '') ||
+    (typeof resolveTypeLabel === 'function' ? normalizeText(resolveTypeLabel(targetType)) : '')
+  const conditionExpressionText =
+    normalizeText(record.conditionExpressionText || '') ||
+    resolveConditionExpressionText(conditionExpression, templateOptionsMap)
+
+  return {
+    ...record,
+    templateCode: normalizeText(record.templateCode || ''),
+    templateName: normalizeText(record.templateName || ''),
+    sourceType,
+    targetType,
+    sourceTypeText: sourceTypeText || sourceType || '--',
+    targetTypeText: targetTypeText || targetType || '--',
+    conditionExpression,
+    conditionExpressionText: conditionExpressionText || '--',
+    conditionDesc: normalizeText(record.conditionDesc || ''),
+    version: normalizeNumber(record.version, void 0),
+    isCurrent: normalizeNumber(record.isCurrent, void 0),
+    priority: normalizeNumber(record.priority, void 0),
+    timeoutMinutes: normalizeNumber(record.timeoutMinutes, void 0),
+    maxRetryTimes: normalizeNumber(record.maxRetryTimes, void 0),
+    retryIntervalSeconds: normalizeNumber(record.retryIntervalSeconds, void 0),
+    stepSize: normalizeNumber(record.stepSize, void 0),
+    status: normalizeNumber(record.status, void 0),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    isCurrentText: currentMeta.text,
+    isCurrentType: currentMeta.type,
+    isCurrentBool: record.isCurrentBool !== void 0 ? Boolean(record.isCurrentBool) : currentMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    effectiveTimeText: normalizeText(record.effectiveTime$ || record.effectiveTime || ''),
+    expireTimeText: normalizeText(record.expireTime$ || record.expireTime || '')
+  }
+}
+
+export function normalizeTaskPathTemplateMergeListRow(record = {}, resolveTypeLabel, templateOptionsMap) {
+  return normalizeTaskPathTemplateMergeDetailRecord(record, resolveTypeLabel, templateOptionsMap)
+}
+
+export function buildTaskPathTemplateMergePrintRows(records = [], resolveTypeLabel, templateOptionsMap) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeTaskPathTemplateMergeListRow(record, resolveTypeLabel, templateOptionsMap))
+}
+
+export function buildTaskPathTemplateMergeReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: TASK_PATH_TEMPLATE_MERGE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...TASK_PATH_TEMPLATE_MERGE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergeTable.columns.js b/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergeTable.columns.js
new file mode 100644
index 0000000..c52aaa6
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-merge/taskPathTemplateMergeTable.columns.js
@@ -0,0 +1,200 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import {
+  getTaskPathTemplateMergeCurrentMeta,
+  getTaskPathTemplateMergeStatusMeta
+} from './taskPathTemplateMergePage.helpers'
+
+export function createTaskPathTemplateMergeTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  resolveTypeLabel,
+  resolveTemplateLabel,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'templateCode',
+      label: '妯℃澘缂栫爜',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.templateCode || '--'
+    },
+    {
+      prop: 'templateName',
+      label: '妯℃澘鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.templateName || '--'
+    },
+    {
+      prop: 'sourceTypeText',
+      label: '璧风偣绫诲瀷',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        row.sourceTypeText || (typeof resolveTypeLabel === 'function' ? resolveTypeLabel(row.sourceType) : '') || '--'
+    },
+    {
+      prop: 'targetTypeText',
+      label: '缁堢偣绫诲瀷',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        row.targetTypeText || (typeof resolveTypeLabel === 'function' ? resolveTypeLabel(row.targetType) : '') || '--'
+    },
+    {
+      prop: 'conditionExpressionText',
+      label: '鏉′欢琛ㄨ揪寮�',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        row.conditionExpressionText ||
+        (typeof resolveTemplateLabel === 'function' ? resolveTemplateLabel(row.conditionExpression) : '') ||
+        '--'
+    },
+    {
+      prop: 'conditionDesc',
+      label: '鏉′欢鎻忚堪',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.conditionDesc || '--'
+    },
+    {
+      prop: 'version',
+      label: '鐗堟湰鍙�',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.version ?? '--'
+    },
+    {
+      prop: 'isCurrent',
+      label: '褰撳墠鐗堟湰',
+      width: 110,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateMergeCurrentMeta(row.isCurrentBool ?? row.isCurrent)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'effectiveTimeText',
+      label: '鐢熸晥鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.effectiveTimeText || '--'
+    },
+    {
+      prop: 'expireTimeText',
+      label: '澶辨晥鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.expireTimeText || '--'
+    },
+    {
+      prop: 'priority',
+      label: '浼樺厛绾�',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.priority ?? '--'
+    },
+    {
+      prop: 'timeoutMinutes',
+      label: '瓒呮椂(鍒�)',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.timeoutMinutes ?? '--'
+    },
+    {
+      prop: 'maxRetryTimes',
+      label: '鏈�澶ч噸璇�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.maxRetryTimes ?? '--'
+    },
+    {
+      prop: 'retryIntervalSeconds',
+      label: '閲嶈瘯闂撮殧(绉�)',
+      minWidth: 120,
+      align: 'center',
+      formatter: (row) => row.retryIntervalSeconds ?? '--'
+    },
+    {
+      prop: 'stepSize',
+      label: '姝ュ簭闀垮害',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.stepSize ?? '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateMergeStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || row.createBy$ || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || row.createTime$ || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
+
diff --git a/rsf-design/src/views/basic-info/task-path-template-node/index.vue b/rsf-design/src/views/basic-info/task-path-template-node/index.vue
new file mode 100644
index 0000000..be5aefa
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-node/index.vue
@@ -0,0 +1,362 @@
+<template>
+  <div class="task-path-template-node-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鑺傜偣</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <TaskPathTemplateNodeDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :task-path-template-node-data="currentTaskPathTemplateNodeData"
+        @submit="handleDialogSubmit"
+      />
+
+      <TaskPathTemplateNodeDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchDeleteTaskPathTemplateNode,
+    fetchGetTaskPathTemplateNodeDetail,
+    fetchSaveTaskPathTemplateNode,
+    fetchTaskPathTemplateNodePage,
+    fetchUpdateTaskPathTemplateNode
+  } from '@/api/task-path-template-node'
+  import TaskPathTemplateNodeDialog from './modules/task-path-template-node-dialog.vue'
+  import TaskPathTemplateNodeDetailDrawer from './modules/task-path-template-node-detail-drawer.vue'
+  import { createTaskPathTemplateNodeTableColumns } from './taskPathTemplateNodeTable.columns'
+  import {
+    buildTaskPathTemplateNodeDialogModel,
+    buildTaskPathTemplateNodePageQueryParams,
+    buildTaskPathTemplateNodeSearchParams,
+    createTaskPathTemplateNodeFormState,
+    createTaskPathTemplateNodeSearchState,
+    getTaskPathTemplateNodePaginationKey,
+    getTaskPathTemplateNodeBooleanOptions,
+    getTaskPathTemplateNodeTypeOptions,
+    normalizeTaskPathTemplateNodeListRow
+  } from './taskPathTemplateNodePage.helpers'
+
+  defineOptions({ name: 'TaskPathTemplateNode' })
+
+  const { hasAuth } = useAuth()
+
+  const searchForm = ref(createTaskPathTemplateNodeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�/鑺傜偣缂栫爜/鑺傜偣鍚嶇О'
+      }
+    },
+    {
+      label: '妯℃澘ID',
+      key: 'templateId',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユā鏉縄D'
+      }
+    },
+    {
+      label: '妯℃澘缂栫爜',
+      key: 'templateCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�'
+      }
+    },
+    {
+      label: '鑺傜偣椤哄簭',
+      key: 'nodeOrder',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ妭鐐归『搴�'
+      }
+    },
+    {
+      label: '鑺傜偣缂栫爜',
+      key: 'nodeCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ妭鐐圭紪鐮�'
+      }
+    },
+    {
+      label: '鑺傜偣鍚嶇О',
+      key: 'nodeName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ妭鐐瑰悕绉�'
+      }
+    },
+    {
+      label: '鑺傜偣绫诲瀷',
+      key: 'nodeType',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getTaskPathTemplateNodeTypeOptions()
+      }
+    },
+    {
+      label: '绯荤粺缂栫爜',
+      key: 'systemCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ郴缁熺紪鐮�'
+      }
+    },
+    {
+      label: '绯荤粺鍚嶇О',
+      key: 'systemName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ郴缁熷悕绉�'
+      }
+    },
+    {
+      label: '蹇呴』鑺傜偣',
+      key: 'mandatory',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getTaskPathTemplateNodeBooleanOptions()
+      }
+    },
+    {
+      label: '鍙苟琛�',
+      key: 'parallelExecutable',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getTaskPathTemplateNodeBooleanOptions()
+      }
+    },
+    {
+      label: '瓒呮椂(鍒�)',
+      key: 'timeoutMinutes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�'
+      }
+    },
+    {
+      label: '鍓嶇疆鏉′欢',
+      key: 'preCondition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ墠缃潯浠�'
+      }
+    },
+    {
+      label: '鍚庣疆鏉′欢',
+      key: 'postCondition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悗缃潯浠�'
+      }
+    },
+    {
+      label: '涓嬩竴鑺傜偣瑙勫垯',
+      key: 'nextNodeRules',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateNodeDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘鑺傜偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeTaskPathTemplateNodeListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘鑺傜偣璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateNodeDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘鑺傜偣璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘鑺傜偣璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchTaskPathTemplateNodePage,
+      apiParams: buildTaskPathTemplateNodePageQueryParams(searchForm.value),
+      paginationKey: getTaskPathTemplateNodePaginationKey(),
+      columnsFactory: () =>
+        createTaskPathTemplateNodeTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeTaskPathTemplateNodeListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentTaskPathTemplateNodeData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => createTaskPathTemplateNodeFormState(),
+    buildEditModel: (record) => buildTaskPathTemplateNodeDialogModel(record),
+    buildSavePayload: (formData) => buildTaskPathTemplateNodeDialogModel(formData),
+    saveRequest: fetchSaveTaskPathTemplateNode,
+    updateRequest: fetchUpdateTaskPathTemplateNode,
+    deleteRequest: fetchDeleteTaskPathTemplateNode,
+    entityName: '浠诲姟璺緞妯℃澘鑺傜偣',
+    resolveRecordLabel: (record) => record?.nodeName || record?.nodeCode || record?.templateCode || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskPathTemplateNodeSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskPathTemplateNodeSearchState())
+    resetSearchParams()
+  }
+
+  onMounted(() => {
+    getData()
+  })
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-detail-drawer.vue b/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-detail-drawer.vue
new file mode 100644
index 0000000..416de4b
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-detail-drawer.vue
@@ -0,0 +1,88 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟璺緞妯℃澘鑺傜偣璇︽儏"
+    size="980px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="妯℃澘ID">{{ detail.templateId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="妯℃澘缂栫爜">{{ detail.templateCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣椤哄簭">{{ detail.nodeOrder ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣缂栫爜">{{ detail.nodeCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣鍚嶇О">{{ detail.nodeName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣绫诲瀷">
+            <ElTag :type="detail.nodeTypeType || 'info'" effect="light">
+              {{ detail.nodeTypeText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="绯荤粺缂栫爜">{{ detail.systemCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绯荤粺鍚嶇О">{{ detail.systemName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="蹇呴』鑺傜偣">
+            <ElTag :type="detail.mandatoryType || 'info'" effect="light">
+              {{ detail.mandatoryText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍙苟琛�">
+            <ElTag :type="detail.parallelExecutableType || 'info'" effect="light">
+              {{ detail.parallelExecutableText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="瓒呮椂(鍒�)">{{ detail.timeoutMinutes ?? '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="鎵ц瑙勫垯" :column="2" border>
+          <ElDescriptionsItem label="鎵ц鍙傛暟" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.executeParams || '--' }}</pre>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴灉缁撴瀯" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.resultSchema || '--' }}</pre>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍓嶇疆鏉′欢" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.preCondition || '--' }}</pre>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍚庣疆鏉′欢" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.postCondition || '--' }}</pre>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="涓嬩竴鑺傜偣瑙勫垯" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.nextNodeRules || '--' }}</pre>
+          </ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="瀹¤淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || detail.createBy || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || detail.createTime || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || detail.updateBy || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || detail.updateTime || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-dialog.vue b/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-dialog.vue
new file mode 100644
index 0000000..f10842f
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-node/modules/task-path-template-node-dialog.vue
@@ -0,0 +1,283 @@
+<template>
+  <ElDialog
+    :model-value="visible"
+    :title="dialogTitle"
+    width="980px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTaskPathTemplateNodeDialogModel,
+    createTaskPathTemplateNodeFormState,
+    getTaskPathTemplateNodeBooleanOptions,
+    getTaskPathTemplateNodeTypeOptions
+  } from '../taskPathTemplateNodePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    taskPathTemplateNodeData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTaskPathTemplateNodeFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫浠诲姟璺緞妯℃澘鑺傜偣' : '鏂板浠诲姟璺緞妯℃澘鑺傜偣'))
+
+  const rules = computed(() => ({
+    templateId: [{ required: true, message: '璇疯緭鍏ユā鏉縄D', trigger: 'blur' }],
+    templateCode: [{ required: true, message: '璇疯緭鍏ユā鏉跨紪鐮�', trigger: 'blur' }],
+    nodeOrder: [{ required: true, message: '璇疯緭鍏ヨ妭鐐归『搴�', trigger: 'blur' }],
+    nodeCode: [{ required: true, message: '璇疯緭鍏ヨ妭鐐圭紪鐮�', trigger: 'blur' }],
+    nodeName: [{ required: true, message: '璇疯緭鍏ヨ妭鐐瑰悕绉�', trigger: 'blur' }],
+    nodeType: [{ required: true, message: '璇烽�夋嫨鑺傜偣绫诲瀷', trigger: 'change' }],
+    systemCode: [{ required: true, message: '璇疯緭鍏ョ郴缁熺紪鐮�', trigger: 'blur' }],
+    mandatory: [{ required: true, message: '璇烽�夋嫨鏄惁蹇呴』鑺傜偣', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '妯℃澘ID',
+      key: 'templateId',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユā鏉縄D'
+      }
+    },
+    {
+      label: '妯℃澘缂栫爜',
+      key: 'templateCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鑺傜偣椤哄簭',
+      key: 'nodeOrder',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ妭鐐归『搴�'
+      }
+    },
+    {
+      label: '鑺傜偣缂栫爜',
+      key: 'nodeCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ妭鐐圭紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鑺傜偣鍚嶇О',
+      key: 'nodeName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ妭鐐瑰悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鑺傜偣绫诲瀷',
+      key: 'nodeType',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鑺傜偣绫诲瀷',
+        clearable: true,
+        options: getTaskPathTemplateNodeTypeOptions()
+      }
+    },
+    {
+      label: '绯荤粺缂栫爜',
+      key: 'systemCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ郴缁熺紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '绯荤粺鍚嶇О',
+      key: 'systemName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ郴缁熷悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '瓒呮椂(鍒�)',
+      key: 'timeoutMinutes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�'
+      }
+    },
+    {
+      label: '蹇呴』鑺傜偣',
+      key: 'mandatory',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁蹇呴』鑺傜偣',
+        clearable: true,
+        options: getTaskPathTemplateNodeBooleanOptions()
+      }
+    },
+    {
+      label: '鍙苟琛�',
+      key: 'parallelExecutable',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁鍙苟琛�',
+        clearable: true,
+        options: getTaskPathTemplateNodeBooleanOptions()
+      }
+    },
+    {
+      label: '鎵ц鍙傛暟',
+      key: 'executeParams',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ヨ妭鐐规墽琛屽弬鏁版ā鏉�',
+        clearable: true
+      }
+    },
+    {
+      label: '缁撴灉缁撴瀯',
+      key: 'resultSchema',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ユ湡鏈涚粨鏋滄暟鎹牸寮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍓嶇疆鏉′欢',
+      key: 'preCondition',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ユ墽琛屽墠缃潯浠�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍚庣疆鏉′欢',
+      key: 'postCondition',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ユ墽琛屽悗缃潯浠�',
+        clearable: true
+      }
+    },
+    {
+      label: '涓嬩竴鑺傜偣瑙勫垯',
+      key: 'nextNodeRules',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ヤ笅涓�鑺傜偣璺敱瑙勫垯',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildTaskPathTemplateNodeDialogModel(props.taskPathTemplateNodeData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createTaskPathTemplateNodeFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.taskPathTemplateNodeData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodePage.helpers.js b/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodePage.helpers.js
new file mode 100644
index 0000000..92e944c
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodePage.helpers.js
@@ -0,0 +1,311 @@
+const NODE_TYPE_META = {
+  EXECUTE: { text: '鎵ц鑺傜偣', type: 'primary' },
+  CHECK: { text: '妫�鏌ョ偣', type: 'warning' },
+  DECISION: { text: '鍐崇瓥鐐�', type: 'success' }
+}
+
+const BOOLEAN_META = {
+  1: { text: '鏄�', type: 'success', bool: true },
+  0: { text: '鍚�', type: 'info', bool: false }
+}
+
+export const TASK_PATH_TEMPLATE_NODE_REPORT_TITLE = '浠诲姟璺緞妯℃澘鑺傜偣鎶ヨ〃'
+export const TASK_PATH_TEMPLATE_NODE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeMaybeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+function normalizeMaybeText(value) {
+  return normalizeText(value)
+}
+
+function normalizeMaybeBooleanNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+export function createTaskPathTemplateNodeSearchState() {
+  return {
+    condition: '',
+    templateId: void 0,
+    templateCode: '',
+    nodeOrder: void 0,
+    nodeCode: '',
+    nodeName: '',
+    nodeType: '',
+    systemCode: '',
+    systemName: '',
+    executeParams: '',
+    resultSchema: '',
+    timeoutMinutes: void 0,
+    mandatory: '',
+    parallelExecutable: '',
+    preCondition: '',
+    postCondition: '',
+    nextNodeRules: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createTaskPathTemplateNodeFormState() {
+  return {
+    id: void 0,
+    templateId: void 0,
+    templateCode: '',
+    nodeOrder: 1,
+    nodeCode: '',
+    nodeName: '',
+    nodeType: 'EXECUTE',
+    systemCode: '',
+    systemName: '',
+    executeParams: '',
+    resultSchema: '',
+    timeoutMinutes: void 0,
+    mandatory: 1,
+    parallelExecutable: 0,
+    preCondition: '',
+    postCondition: '',
+    nextNodeRules: ''
+  }
+}
+
+export function getTaskPathTemplateNodePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getTaskPathTemplateNodeTypeOptions() {
+  return [
+    { label: '鎵ц鑺傜偣', value: 'EXECUTE' },
+    { label: '妫�鏌ョ偣', value: 'CHECK' },
+    { label: '鍐崇瓥鐐�', value: 'DECISION' }
+  ]
+}
+
+export function getTaskPathTemplateNodeBooleanOptions() {
+  return [
+    { label: '鍚�', value: 0 },
+    { label: '鏄�', value: 1 }
+  ]
+}
+
+export function getTaskPathTemplateNodeTypeMeta(nodeType) {
+  const normalized = normalizeMaybeText(nodeType).toUpperCase()
+  if (normalized in NODE_TYPE_META) {
+    return NODE_TYPE_META[normalized]
+  }
+  return { text: normalized || '鏈煡', type: 'info' }
+}
+
+export function getTaskPathTemplateNodeBooleanMeta(value) {
+  if (value === true || Number(value) === 1) {
+    return BOOLEAN_META[1]
+  }
+  if (value === false || Number(value) === 0) {
+    return BOOLEAN_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildTaskPathTemplateNodeSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeMaybeText(params.condition),
+    templateId: normalizeMaybeNumber(params.templateId),
+    templateCode: normalizeMaybeText(params.templateCode),
+    nodeOrder: normalizeMaybeNumber(params.nodeOrder),
+    nodeCode: normalizeMaybeText(params.nodeCode),
+    nodeName: normalizeMaybeText(params.nodeName),
+    nodeType: normalizeMaybeText(params.nodeType),
+    systemCode: normalizeMaybeText(params.systemCode),
+    systemName: normalizeMaybeText(params.systemName),
+    executeParams: normalizeMaybeText(params.executeParams),
+    resultSchema: normalizeMaybeText(params.resultSchema),
+    timeoutMinutes: normalizeMaybeNumber(params.timeoutMinutes),
+    mandatory: normalizeMaybeNumber(params.mandatory),
+    parallelExecutable: normalizeMaybeNumber(params.parallelExecutable),
+    preCondition: normalizeMaybeText(params.preCondition),
+    postCondition: normalizeMaybeText(params.postCondition),
+    nextNodeRules: normalizeMaybeText(params.nextNodeRules),
+    timeStart: normalizeMaybeText(params.timeStart),
+    timeEnd: normalizeMaybeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplateNodePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskPathTemplateNodeSearchParams(params)
+  }
+}
+
+export function buildTaskPathTemplateNodeSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    ...(formData.templateId !== void 0 && formData.templateId !== null && formData.templateId !== ''
+      ? { templateId: Number(formData.templateId) }
+      : {}),
+    templateCode: normalizeMaybeText(formData.templateCode) || '',
+    ...(formData.nodeOrder !== void 0 && formData.nodeOrder !== null && formData.nodeOrder !== ''
+      ? { nodeOrder: Number(formData.nodeOrder) }
+      : { nodeOrder: 1 }),
+    nodeCode: normalizeMaybeText(formData.nodeCode) || '',
+    nodeName: normalizeMaybeText(formData.nodeName) || '',
+    nodeType: normalizeMaybeText(formData.nodeType) || 'EXECUTE',
+    systemCode: normalizeMaybeText(formData.systemCode) || '',
+    systemName: normalizeMaybeText(formData.systemName) || '',
+    executeParams: normalizeMaybeText(formData.executeParams) || '',
+    resultSchema: normalizeMaybeText(formData.resultSchema) || '',
+    ...(formData.timeoutMinutes !== void 0 && formData.timeoutMinutes !== null && formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    ...(formData.mandatory !== void 0 && formData.mandatory !== null && formData.mandatory !== ''
+      ? { mandatory: Number(formData.mandatory) }
+      : { mandatory: 1 }),
+    ...(formData.parallelExecutable !== void 0 &&
+    formData.parallelExecutable !== null &&
+    formData.parallelExecutable !== ''
+      ? { parallelExecutable: Number(formData.parallelExecutable) }
+      : { parallelExecutable: 0 }),
+    preCondition: normalizeMaybeText(formData.preCondition) || '',
+    postCondition: normalizeMaybeText(formData.postCondition) || '',
+    nextNodeRules: normalizeMaybeText(formData.nextNodeRules) || ''
+  }
+}
+
+export function buildTaskPathTemplateNodeDialogModel(record = {}) {
+  return {
+    ...createTaskPathTemplateNodeFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    ...(record.templateId !== void 0 && record.templateId !== null && record.templateId !== ''
+      ? { templateId: Number(record.templateId) }
+      : {}),
+    templateCode: normalizeMaybeText(record.templateCode || ''),
+    nodeOrder:
+      record.nodeOrder !== void 0 && record.nodeOrder !== null && record.nodeOrder !== ''
+        ? Number(record.nodeOrder)
+        : 1,
+    nodeCode: normalizeMaybeText(record.nodeCode || ''),
+    nodeName: normalizeMaybeText(record.nodeName || ''),
+    nodeType: normalizeMaybeText(record.nodeType || '') || 'EXECUTE',
+    systemCode: normalizeMaybeText(record.systemCode || ''),
+    systemName: normalizeMaybeText(record.systemName || ''),
+    executeParams: normalizeMaybeText(record.executeParams || ''),
+    resultSchema: normalizeMaybeText(record.resultSchema || ''),
+    timeoutMinutes: normalizeMaybeNumber(record.timeoutMinutes, void 0),
+    mandatory:
+      record.mandatory !== void 0 && record.mandatory !== null && record.mandatory !== ''
+        ? Number(record.mandatory)
+        : 1,
+    parallelExecutable:
+      record.parallelExecutable !== void 0 && record.parallelExecutable !== null && record.parallelExecutable !== ''
+        ? Number(record.parallelExecutable)
+        : 0,
+    preCondition: normalizeMaybeText(record.preCondition || ''),
+    postCondition: normalizeMaybeText(record.postCondition || ''),
+    nextNodeRules: normalizeMaybeText(record.nextNodeRules || '')
+  }
+}
+
+export function normalizeTaskPathTemplateNodeDetailRecord(record = {}) {
+  const nodeTypeMeta = getTaskPathTemplateNodeTypeMeta(record.nodeType)
+  const mandatoryMeta = getTaskPathTemplateNodeBooleanMeta(record.mandatory)
+  const parallelMeta = getTaskPathTemplateNodeBooleanMeta(record.parallelExecutable)
+
+  return {
+    ...record,
+    templateId: normalizeMaybeNumber(record.templateId, void 0),
+    templateCode: normalizeMaybeText(record.templateCode || ''),
+    nodeOrder: normalizeMaybeNumber(record.nodeOrder, void 0),
+    nodeCode: normalizeMaybeText(record.nodeCode || ''),
+    nodeName: normalizeMaybeText(record.nodeName || ''),
+    nodeType: normalizeMaybeText(record.nodeType || ''),
+    nodeTypeText: nodeTypeMeta.text,
+    nodeTypeType: nodeTypeMeta.type,
+    systemCode: normalizeMaybeText(record.systemCode || ''),
+    systemName: normalizeMaybeText(record.systemName || ''),
+    executeParams: normalizeMaybeText(record.executeParams || ''),
+    resultSchema: normalizeMaybeText(record.resultSchema || ''),
+    timeoutMinutes: normalizeMaybeNumber(record.timeoutMinutes, void 0),
+    mandatory: normalizeMaybeNumber(record.mandatory, void 0),
+    mandatoryText: mandatoryMeta.text,
+    mandatoryType: mandatoryMeta.type,
+    mandatoryBool: record.mandatoryBool !== void 0 ? Boolean(record.mandatoryBool) : mandatoryMeta.bool,
+    parallelExecutable: normalizeMaybeNumber(record.parallelExecutable, void 0),
+    parallelExecutableText: parallelMeta.text,
+    parallelExecutableType: parallelMeta.type,
+    parallelExecutableBool:
+      record.parallelExecutableBool !== void 0 ? Boolean(record.parallelExecutableBool) : parallelMeta.bool,
+    preCondition: normalizeMaybeText(record.preCondition || ''),
+    postCondition: normalizeMaybeText(record.postCondition || ''),
+    nextNodeRules: normalizeMaybeText(record.nextNodeRules || ''),
+    createByText: normalizeMaybeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeMaybeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeMaybeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeMaybeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeTaskPathTemplateNodeListRow(record = {}) {
+  return normalizeTaskPathTemplateNodeDetailRecord(record)
+}
+
+export function buildTaskPathTemplateNodePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeTaskPathTemplateNodeListRow(record))
+}
+
+export function buildTaskPathTemplateNodeReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = TASK_PATH_TEMPLATE_NODE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: TASK_PATH_TEMPLATE_NODE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...TASK_PATH_TEMPLATE_NODE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodeTable.columns.js b/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodeTable.columns.js
new file mode 100644
index 0000000..68e74b3
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template-node/taskPathTemplateNodeTable.columns.js
@@ -0,0 +1,145 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import {
+  getTaskPathTemplateNodeBooleanMeta,
+  getTaskPathTemplateNodeTypeMeta
+} from './taskPathTemplateNodePage.helpers'
+
+export function createTaskPathTemplateNodeTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'templateId',
+      label: '妯℃澘ID',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.templateId ?? '--'
+    },
+    {
+      prop: 'templateCode',
+      label: '妯℃澘缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.templateCode || '--'
+    },
+    {
+      prop: 'nodeOrder',
+      label: '鑺傜偣椤哄簭',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.nodeOrder ?? '--'
+    },
+    {
+      prop: 'nodeCode',
+      label: '鑺傜偣缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.nodeCode || '--'
+    },
+    {
+      prop: 'nodeName',
+      label: '鑺傜偣鍚嶇О',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.nodeName || '--'
+    },
+    {
+      prop: 'nodeTypeText',
+      label: '鑺傜偣绫诲瀷',
+      width: 120,
+      align: 'center',
+      formatter: (row) => {
+        const typeMeta = getTaskPathTemplateNodeTypeMeta(row.nodeType)
+        return h(ElTag, { type: typeMeta.type, effect: 'light' }, () => typeMeta.text)
+      }
+    },
+    {
+      prop: 'systemCode',
+      label: '绯荤粺缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.systemCode || '--'
+    },
+    {
+      prop: 'systemName',
+      label: '绯荤粺鍚嶇О',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.systemName || '--'
+    },
+    {
+      prop: 'mandatoryText',
+      label: '蹇呴』鑺傜偣',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateNodeBooleanMeta(row.mandatory)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'parallelExecutableText',
+      label: '鍙苟琛�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateNodeBooleanMeta(row.parallelExecutable)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'timeoutMinutes',
+      label: '瓒呮椂(鍒�)',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.timeoutMinutes ?? '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/task-path-template/index.vue b/rsf-design/src/views/basic-info/task-path-template/index.vue
new file mode 100644
index 0000000..88007c5
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/index.vue
@@ -0,0 +1,428 @@
+<template>
+  <div class="task-path-template-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板妯℃澘</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <TaskPathTemplateDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :task-path-template-data="currentTaskPathTemplateData"
+        @submit="handleDialogSubmit"
+      />
+
+      <TaskPathTemplateDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+
+      <TaskPathTemplateFlowDrawer
+        v-model:visible="flowDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    buildTaskPathTemplatePageQueryParams,
+    buildTaskPathTemplatePrintRows,
+    buildTaskPathTemplateReportMeta,
+    buildTaskPathTemplateSavePayload,
+    buildTaskPathTemplateSearchParams,
+    createTaskPathTemplateSearchState,
+    getTaskPathTemplatePaginationKey,
+    normalizeTaskPathTemplateListRow,
+    TASK_PATH_TEMPLATE_REPORT_STYLE,
+    TASK_PATH_TEMPLATE_REPORT_TITLE
+  } from './taskPathTemplatePage.helpers'
+  import {
+    fetchDeleteTaskPathTemplate,
+    fetchExportTaskPathTemplateReport,
+    fetchGetTaskPathTemplateDetail,
+    fetchGetTaskPathTemplateMany,
+    fetchSaveTaskPathTemplate,
+    fetchTaskPathTemplatePage,
+    fetchUpdateTaskPathTemplate
+  } from '@/api/task-path-template'
+  import TaskPathTemplateDialog from './modules/task-path-template-dialog.vue'
+  import TaskPathTemplateDetailDrawer from './modules/task-path-template-detail-drawer.vue'
+  import TaskPathTemplateFlowDrawer from './modules/task-path-template-flow-drawer.vue'
+  import { createTaskPathTemplateTableColumns } from './taskPathTemplateTable.columns'
+
+  defineOptions({ name: 'TaskPathTemplate' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createTaskPathTemplateSearchState())
+  const detailDrawerVisible = ref(false)
+  const flowDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = TASK_PATH_TEMPLATE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskPathTemplateSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�/鍚嶇О/鏉′欢鎻忚堪'
+      }
+    },
+    {
+      label: '妯℃澘缂栫爜',
+      key: 'templateCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�'
+      }
+    },
+    {
+      label: '妯℃澘鍚嶇О',
+      key: 'templateName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鏉垮悕绉�'
+      }
+    },
+    {
+      label: '璧风偣绫诲瀷',
+      key: 'sourceType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ捣鐐圭被鍨�'
+      }
+    },
+    {
+      label: '缁堢偣绫诲瀷',
+      key: 'targetType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粓鐐圭被鍨�'
+      }
+    },
+    {
+      label: '鐗堟湰鍙�',
+      key: 'version',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ増鏈彿'
+      }
+    },
+    {
+      label: '褰撳墠鐗堟湰',
+      key: 'isCurrent',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '褰撳墠鐗堟湰', value: 1 },
+          { label: '鍘嗗彶鐗堟湰', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '浼樺厛绾�',
+      key: 'priority',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ紭鍏堢骇'
+      }
+    },
+    {
+      label: '瓒呮椂(鍒�)',
+      key: 'timeoutMinutes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�'
+      }
+    },
+    {
+      label: '姝ュ簭闀垮害',
+      key: 'stepSize',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ搴忛暱搴�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鍚敤', value: 1 },
+          { label: '绂佺敤', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '鐢熸晥鏃堕棿',
+      key: 'effectiveTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨鐢熸晥鏃堕棿'
+      }
+    },
+    {
+      label: '澶辨晥鏃堕棿',
+      key: 'expireTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨澶辨晥鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    flowDrawerVisible.value = false
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeTaskPathTemplateListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openFlow(row) {
+    detailDrawerVisible.value = false
+    flowDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateDetail(row.id), {}, {
+        timeoutMessage: '娴佺▼鍥炬暟鎹姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      detailData.value = normalizeTaskPathTemplateListRow(detail)
+    } catch (error) {
+      flowDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇娴佺▼鍥炬暟鎹け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetTaskPathTemplateDetail(row.id), {}, {
+        timeoutMessage: '浠诲姟璺緞妯℃澘璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇浠诲姟璺緞妯℃澘璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchTaskPathTemplatePage,
+      apiParams: buildTaskPathTemplatePageQueryParams(searchForm.value),
+      paginationKey: getTaskPathTemplatePaginationKey(),
+      columnsFactory: () =>
+        createTaskPathTemplateTableColumns({
+          handleView: openDetail,
+          handleFlow: openFlow,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete')
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeTaskPathTemplateListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentTaskPathTemplateData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildTaskPathTemplateSavePayload({}),
+    buildEditModel: (record) => buildTaskPathTemplateSavePayload(record),
+    buildSavePayload: (formData) => buildTaskPathTemplateSavePayload(formData),
+    saveRequest: fetchSaveTaskPathTemplate,
+    updateRequest: fetchUpdateTaskPathTemplate,
+    deleteRequest: fetchDeleteTaskPathTemplate,
+    entityName: '浠诲姟璺緞妯℃澘',
+    resolveRecordLabel: (record) => record?.templateCode || record?.templateName || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...TASK_PATH_TEMPLATE_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskPathTemplateMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskPathTemplatePage({
+        ...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: 'task-path-template.xlsx',
+      requestExport: (payload) =>
+        fetchExportTaskPathTemplateReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildTaskPathTemplatePrintRows(records),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildTaskPathTemplateReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: TASK_PATH_TEMPLATE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskPathTemplateSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskPathTemplateSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-detail-drawer.vue b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-detail-drawer.vue
new file mode 100644
index 0000000..ce9f174
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-detail-drawer.vue
@@ -0,0 +1,76 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟璺緞妯℃澘璇︽儏"
+    size="980px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <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.version ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="褰撳墠鐗堟湰">
+            <ElTag :type="detail.isCurrentType || 'info'" effect="light">
+              {{ detail.isCurrentText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鐢熸晥鏃堕棿">{{ detail.effectiveTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶辨晥鏃堕棿">{{ detail.expireTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浼樺厛绾�">{{ detail.priority ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瓒呮椂(鍒�)">{{ detail.timeoutMinutes ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="姝ュ簭闀垮害">{{ detail.stepSize ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏈�澶ч噸璇曟鏁�">{{ detail.maxRetryTimes ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲嶈瘯闂撮殧(绉�)">{{ detail.retryIntervalSeconds ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉′欢鎻忚堪" :span="2">
+            {{ detail.conditionDesc || '--' }}
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉′欢琛ㄨ揪寮�" :span="2">
+            <pre class="m-0 whitespace-pre-wrap break-words text-[var(--art-text-secondary)]">{{ detail.conditionExpression || '--' }}</pre>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.remark || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-dialog.vue b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-dialog.vue
new file mode 100644
index 0000000..4870ac3
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-dialog.vue
@@ -0,0 +1,294 @@
+<template>
+  <ElDialog
+    :model-value="visible"
+    :title="dialogTitle"
+    width="960px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTaskPathTemplateDialogModel,
+    createTaskPathTemplateFormState,
+    getTaskPathTemplateCurrentOptions,
+    getTaskPathTemplateStatusOptions
+  } from '../taskPathTemplatePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    taskPathTemplateData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTaskPathTemplateFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫浠诲姟璺緞妯℃澘' : '鏂板浠诲姟璺緞妯℃澘'))
+
+  const rules = computed(() => ({
+    templateCode: [{ required: true, message: '璇疯緭鍏ユā鏉跨紪鐮�', trigger: 'blur' }],
+    templateName: [{ required: true, message: '璇疯緭鍏ユā鏉垮悕绉�', trigger: 'blur' }],
+    sourceType: [{ required: true, message: '璇疯緭鍏ヨ捣鐐圭被鍨�', trigger: 'blur' }],
+    targetType: [{ required: true, message: '璇疯緭鍏ョ粓鐐圭被鍨�', trigger: 'blur' }],
+    version: [{ required: true, message: '璇疯緭鍏ョ増鏈彿', trigger: 'blur' }],
+    isCurrent: [{ required: true, message: '璇烽�夋嫨鏄惁褰撳墠鐗堟湰', trigger: 'change' }],
+    effectiveTime: [{ required: true, message: '璇烽�夋嫨鐢熸晥鏃堕棿', trigger: 'change' }],
+    priority: [{ required: true, message: '璇疯緭鍏ヤ紭鍏堢骇', trigger: 'blur' }],
+    status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '妯℃澘缂栫爜',
+      key: 'templateCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユā鏉跨紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '妯℃澘鍚嶇О',
+      key: 'templateName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユā鏉垮悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '璧风偣绫诲瀷',
+      key: 'sourceType',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ捣鐐圭被鍨�',
+        clearable: true
+      }
+    },
+    {
+      label: '缁堢偣绫诲瀷',
+      key: 'targetType',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ粓鐐圭被鍨�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐗堟湰鍙�',
+      key: 'version',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ増鏈彿'
+      }
+    },
+    {
+      label: '鏄惁褰撳墠鐗堟湰',
+      key: 'isCurrent',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鏄惁褰撳墠鐗堟湰',
+        clearable: true,
+        options: getTaskPathTemplateCurrentOptions()
+      }
+    },
+    {
+      label: '鐢熸晥鏃堕棿',
+      key: 'effectiveTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨鐢熸晥鏃堕棿'
+      }
+    },
+    {
+      label: '澶辨晥鏃堕棿',
+      key: 'expireTime',
+      type: 'datetime',
+      props: {
+        type: 'datetime',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨澶辨晥鏃堕棿'
+      }
+    },
+    {
+      label: '浼樺厛绾�',
+      key: 'priority',
+      type: 'number',
+      props: {
+        min: 1,
+        max: 99,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ紭鍏堢骇'
+      }
+    },
+    {
+      label: '瓒呮椂(鍒�)',
+      key: 'timeoutMinutes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ暣浣撹秴鏃舵椂闂�'
+      }
+    },
+    {
+      label: '姝ュ簭闀垮害',
+      key: 'stepSize',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ搴忛暱搴�'
+      }
+    },
+    {
+      label: '鏈�澶ч噸璇曟鏁�',
+      key: 'maxRetryTimes',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ渶澶ч噸璇曟鏁�'
+      }
+    },
+    {
+      label: '閲嶈瘯闂撮殧(绉�)',
+      key: 'retryIntervalSeconds',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ噸璇曢棿闅�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getTaskPathTemplateStatusOptions()
+      }
+    },
+    {
+      label: '鏉′欢琛ㄨ揪寮�',
+      key: 'conditionExpression',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 4,
+        placeholder: '璇疯緭鍏ユ潯浠惰〃杈惧紡 JSON',
+        clearable: true
+      }
+    },
+    {
+      label: '鏉′欢鎻忚堪',
+      key: 'conditionDesc',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ユ潯浠舵弿杩�',
+        clearable: true
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'remark',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildTaskPathTemplateDialogModel(props.taskPathTemplateData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createTaskPathTemplateFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.taskPathTemplateData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
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
new file mode 100644
index 0000000..c0f68ba
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue
@@ -0,0 +1,78 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娴佺▼鍥炬煡鐪�"
+    size="900px"
+    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">
+          <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>
+            </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>
+            </div>
+          </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>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import { buildTaskPathTemplateFlowSnapshot } from '../taskPathTemplatePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  const flowSnapshot = computed(() => buildTaskPathTemplateFlowSnapshot(props.detail))
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/task-path-template/taskPathTemplatePage.helpers.js b/rsf-design/src/views/basic-info/task-path-template/taskPathTemplatePage.helpers.js
new file mode 100644
index 0000000..60ad90c
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/taskPathTemplatePage.helpers.js
@@ -0,0 +1,282 @@
+const STATUS_META = {
+  1: { text: '鍚敤', type: 'success', bool: true },
+  0: { text: '绂佺敤', type: 'danger', bool: false }
+}
+
+const CURRENT_META = {
+  1: { text: '褰撳墠鐗堟湰', type: 'success', bool: true },
+  0: { text: '鍘嗗彶鐗堟湰', type: 'info', bool: false }
+}
+
+export const TASK_PATH_TEMPLATE_REPORT_TITLE = '浠诲姟璺緞妯℃澘鎶ヨ〃'
+export const TASK_PATH_TEMPLATE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+export function createTaskPathTemplateSearchState() {
+  return {
+    condition: '',
+    templateCode: '',
+    templateName: '',
+    sourceType: '',
+    targetType: '',
+    conditionExpression: '',
+    conditionDesc: '',
+    version: '',
+    isCurrent: '',
+    effectiveTime: '',
+    expireTime: '',
+    priority: '',
+    timeoutMinutes: '',
+    stepSize: '',
+    maxRetryTimes: '',
+    retryIntervalSeconds: '',
+    status: '',
+    remark: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createTaskPathTemplateFormState() {
+  return {
+    id: void 0,
+    templateCode: '',
+    templateName: '',
+    sourceType: '',
+    targetType: '',
+    conditionExpression: '',
+    conditionDesc: '',
+    version: 1,
+    isCurrent: 1,
+    effectiveTime: '',
+    expireTime: '',
+    priority: 1,
+    timeoutMinutes: '',
+    stepSize: '',
+    maxRetryTimes: 3,
+    retryIntervalSeconds: 60,
+    status: 1,
+    remark: ''
+  }
+}
+
+export function getTaskPathTemplateStatusOptions() {
+  return [
+    { label: '鍚敤', value: 1 },
+    { label: '绂佺敤', value: 0 }
+  ]
+}
+
+export function getTaskPathTemplateCurrentOptions() {
+  return [
+    { label: '褰撳墠鐗堟湰', value: 1 },
+    { label: '鍘嗗彶鐗堟湰', value: 0 }
+  ]
+}
+
+export function getTaskPathTemplatePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getTaskPathTemplateStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function getTaskPathTemplateCurrentMeta(isCurrent) {
+  if (isCurrent === true || Number(isCurrent) === 1) {
+    return CURRENT_META[1]
+  }
+  if (isCurrent === false || Number(isCurrent) === 0) {
+    return CURRENT_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildTaskPathTemplateSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    templateCode: normalizeText(params.templateCode),
+    templateName: normalizeText(params.templateName),
+    sourceType: normalizeText(params.sourceType),
+    targetType: normalizeText(params.targetType),
+    conditionExpression: normalizeText(params.conditionExpression),
+    conditionDesc: normalizeText(params.conditionDesc),
+    version: normalizeNumber(params.version),
+    isCurrent: normalizeNumber(params.isCurrent),
+    effectiveTime: normalizeText(params.effectiveTime),
+    expireTime: normalizeText(params.expireTime),
+    priority: normalizeNumber(params.priority),
+    timeoutMinutes: normalizeNumber(params.timeoutMinutes),
+    stepSize: normalizeNumber(params.stepSize),
+    maxRetryTimes: normalizeNumber(params.maxRetryTimes),
+    retryIntervalSeconds: normalizeNumber(params.retryIntervalSeconds),
+    status: normalizeNumber(params.status),
+    remark: normalizeText(params.remark),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildTaskPathTemplatePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskPathTemplateSearchParams(params)
+  }
+}
+
+export function buildTaskPathTemplateSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    templateCode: normalizeText(formData.templateCode),
+    templateName: normalizeText(formData.templateName),
+    sourceType: normalizeText(formData.sourceType),
+    targetType: normalizeText(formData.targetType),
+    conditionExpression: normalizeText(formData.conditionExpression),
+    conditionDesc: normalizeText(formData.conditionDesc),
+    version: normalizeNumber(formData.version, 1),
+    isCurrent: normalizeNumber(formData.isCurrent, 1),
+    effectiveTime: normalizeText(formData.effectiveTime),
+    expireTime: normalizeText(formData.expireTime),
+    priority: normalizeNumber(formData.priority, 1),
+    ...(formData.timeoutMinutes !== void 0 &&
+    formData.timeoutMinutes !== null &&
+    formData.timeoutMinutes !== ''
+      ? { timeoutMinutes: Number(formData.timeoutMinutes) }
+      : {}),
+    ...(formData.stepSize !== void 0 && formData.stepSize !== null && formData.stepSize !== ''
+      ? { stepSize: Number(formData.stepSize) }
+      : {}),
+    maxRetryTimes: normalizeNumber(formData.maxRetryTimes, 3),
+    retryIntervalSeconds: normalizeNumber(formData.retryIntervalSeconds, 60),
+    status: normalizeNumber(formData.status, 1),
+    remark: normalizeText(formData.remark)
+  }
+}
+
+export function buildTaskPathTemplateDialogModel(record = {}) {
+  return {
+    ...createTaskPathTemplateFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== ''
+      ? { id: Number(record.id) }
+      : {}),
+    templateCode: normalizeText(record.templateCode || ''),
+    templateName: normalizeText(record.templateName || ''),
+    sourceType: normalizeText(record.sourceType || ''),
+    targetType: normalizeText(record.targetType || ''),
+    conditionExpression: normalizeText(record.conditionExpression || ''),
+    conditionDesc: normalizeText(record.conditionDesc || ''),
+    version: normalizeNumber(record.version, 1),
+    isCurrent: normalizeNumber(record.isCurrent, 1),
+    effectiveTime: normalizeText(record.effectiveTime$ || record.effectiveTime || ''),
+    expireTime: normalizeText(record.expireTime$ || record.expireTime || ''),
+    priority: normalizeNumber(record.priority, 1),
+    timeoutMinutes: normalizeNumber(record.timeoutMinutes, ''),
+    stepSize: normalizeNumber(record.stepSize, ''),
+    maxRetryTimes: normalizeNumber(record.maxRetryTimes, 3),
+    retryIntervalSeconds: normalizeNumber(record.retryIntervalSeconds, 60),
+    status: normalizeNumber(record.status, 1),
+    remark: normalizeText(record.remark || '')
+  }
+}
+
+export function normalizeTaskPathTemplateDetailRecord(record = {}) {
+  const statusMeta = getTaskPathTemplateStatusMeta(record.statusBool ?? record.status)
+  const currentMeta = getTaskPathTemplateCurrentMeta(record.isCurrent)
+  return {
+    ...record,
+    templateCode: normalizeText(record.templateCode || ''),
+    templateName: normalizeText(record.templateName || ''),
+    sourceType: normalizeText(record.sourceType || ''),
+    targetType: normalizeText(record.targetType || ''),
+    conditionExpression: normalizeText(record.conditionExpression || ''),
+    conditionDesc: normalizeText(record.conditionDesc || ''),
+    remark: normalizeText(record.remark || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    isCurrentText: currentMeta.text,
+    isCurrentType: currentMeta.type,
+    isCurrentBool: record.isCurrent !== void 0 ? Boolean(record.isCurrent) : currentMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    effectiveTimeText: normalizeText(record.effectiveTime$ || record.effectiveTime || ''),
+    expireTimeText: normalizeText(record.expireTime$ || record.expireTime || '')
+  }
+}
+
+export function normalizeTaskPathTemplateListRow(record = {}) {
+  return normalizeTaskPathTemplateDetailRecord(record)
+}
+
+export function buildTaskPathTemplatePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeTaskPathTemplateListRow(record))
+}
+
+export function buildTaskPathTemplateReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = TASK_PATH_TEMPLATE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: TASK_PATH_TEMPLATE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...TASK_PATH_TEMPLATE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function buildTaskPathTemplateFlowSnapshot(record = {}) {
+  const sourceType = normalizeText(record.sourceType || '')
+  const targetType = normalizeText(record.targetType || '')
+  const stepSize = normalizeText(record.stepSize || '')
+  const templateCode = normalizeText(record.templateCode || '')
+  return [
+    { key: 'source', title: '璧风偣绫诲瀷', value: sourceType || '--' },
+    { key: 'step', title: '姝ュ簭闀垮害', value: stepSize || '--' },
+    { key: 'target', title: '缁堢偣绫诲瀷', value: targetType || '--' },
+    { key: 'code', title: '妯℃澘缂栫爜', value: templateCode || '--' }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/task-path-template/taskPathTemplateTable.columns.js b/rsf-design/src/views/basic-info/task-path-template/taskPathTemplateTable.columns.js
new file mode 100644
index 0000000..873ab26
--- /dev/null
+++ b/rsf-design/src/views/basic-info/task-path-template/taskPathTemplateTable.columns.js
@@ -0,0 +1,207 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import {
+  getTaskPathTemplateCurrentMeta,
+  getTaskPathTemplateStatusMeta
+} from './taskPathTemplatePage.helpers'
+
+export function createTaskPathTemplateTableColumns({
+  handleView,
+  handleFlow,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true
+} = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (handleFlow) {
+    operations.push({ key: 'flow', label: '娴佺▼鍥�', icon: 'ri:route-line' })
+  }
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'templateCode',
+      label: '妯℃澘缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.templateCode || '--'
+    },
+    {
+      prop: 'templateName',
+      label: '妯℃澘鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.templateName || '--'
+    },
+    {
+      prop: 'sourceType',
+      label: '璧风偣绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.sourceType || '--'
+    },
+    {
+      prop: 'targetType',
+      label: '缁堢偣绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.targetType || '--'
+    },
+    {
+      prop: 'conditionDesc',
+      label: '鏉′欢鎻忚堪',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.conditionDesc || '--'
+    },
+    {
+      prop: 'version',
+      label: '鐗堟湰鍙�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.version ?? '--'
+    },
+    {
+      prop: 'isCurrent',
+      label: '褰撳墠鐗堟湰',
+      width: 110,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateCurrentMeta(row.isCurrentBool ?? row.isCurrent)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'effectiveTimeText',
+      label: '鐢熸晥鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.effectiveTimeText || row.effectiveTime$ || '--'
+    },
+    {
+      prop: 'expireTimeText',
+      label: '澶辨晥鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.expireTimeText || row.expireTime$ || '--'
+    },
+    {
+      prop: 'priority',
+      label: '浼樺厛绾�',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.priority ?? '--'
+    },
+    {
+      prop: 'timeoutMinutes',
+      label: '瓒呮椂(鍒�)',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.timeoutMinutes ?? '--'
+    },
+    {
+      prop: 'stepSize',
+      label: '姝ュ簭闀垮害',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.stepSize ?? '--'
+    },
+    {
+      prop: 'maxRetryTimes',
+      label: '鏈�澶ч噸璇�',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.maxRetryTimes ?? '--'
+    },
+    {
+      prop: 'retryIntervalSeconds',
+      label: '閲嶈瘯闂撮殧(绉�)',
+      width: 120,
+      align: 'center',
+      formatter: (row) => row.retryIntervalSeconds ?? '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const meta = getTaskPathTemplateStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: meta.type, effect: 'light' }, () => meta.text)
+      }
+    },
+    {
+      prop: 'remark',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.remark || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || row.createBy$ || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || row.createTime$ || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 190,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'flow') handleFlow?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/basic-info/warehouse/index.vue b/rsf-design/src/views/basic-info/warehouse/index.vue
new file mode 100644
index 0000000..b1532fd
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse/index.vue
@@ -0,0 +1,325 @@
+<template>
+  <div class="warehouse-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板浠撳簱</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <WarehouseDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :warehouse-data="currentWarehouseData"
+        @submit="handleDialogSubmit"
+      />
+
+      <WarehouseDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  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 {
+    fetchDeleteWarehouse,
+    fetchExportWarehouseReport,
+    fetchGetWarehouseDetail,
+    fetchGetWarehouseMany,
+    fetchSaveWarehouse,
+    fetchUpdateWarehouse,
+    fetchWarehousePage
+  } from '@/api/warehouse'
+  import WarehouseDialog from './modules/warehouse-dialog.vue'
+  import WarehouseDetailDrawer from './modules/warehouse-detail-drawer.vue'
+  import { createWarehouseTableColumns } from './warehouseTable.columns'
+  import {
+    buildWarehouseDialogModel,
+    buildWarehousePageQueryParams,
+    buildWarehousePrintRows,
+    buildWarehouseReportMeta,
+    buildWarehouseSavePayload,
+    buildWarehouseSearchParams,
+    createWarehouseSearchState,
+    getWarehousePaginationKey,
+    normalizeWarehouseDetailRecord,
+    normalizeWarehouseListRow,
+    WAREHOUSE_REPORT_STYLE,
+    WAREHOUSE_REPORT_TITLE
+  } from './warehousePage.helpers'
+
+  defineOptions({ name: 'Warehouse' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createWarehouseSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = WAREHOUSE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildWarehouseSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�/缂栫爜/宸ュ巶'
+      }
+    },
+    {
+      label: '浠撳簱鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�'
+      }
+    },
+    {
+      label: '浠撳簱缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�'
+      }
+    },
+    {
+      label: '鎵�灞炲伐鍘�',
+      key: 'factory',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ墍灞炲伐鍘�'
+      }
+    },
+    {
+      label: '浠撳簱鍦板潃',
+      key: 'address',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撳湴鍧�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetWarehouseDetail(row.id), {}, {
+        timeoutMessage: '浠撳簱璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeWarehouseDetailRecord(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇浠撳簱璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetWarehouseDetail(row.id), {}, {
+        timeoutMessage: '浠撳簱璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇浠撳簱璇︽儏澶辫触')
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchWarehousePage,
+        apiParams: buildWarehousePageQueryParams(searchForm.value),
+        paginationKey: getWarehousePaginationKey(),
+        columnsFactory: () =>
+          createWarehouseTableColumns({
+            handleView: openDetail,
+            handleEdit: hasAuth('update') ? openEditDialog : null,
+            handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+            canEdit: hasAuth('update'),
+            canDelete: hasAuth('delete')
+          })
+      },
+      transform: {
+        dataTransformer: (records) => {
+          if (!Array.isArray(records)) {
+            return []
+          }
+          return records.map((item) => normalizeWarehouseListRow(item))
+        }
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentWarehouseData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildWarehouseDialogModel(),
+    buildEditModel: (record) => buildWarehouseDialogModel(record),
+    buildSavePayload: (formData) => buildWarehouseSavePayload(formData),
+    saveRequest: fetchSaveWarehouse,
+    updateRequest: fetchUpdateWarehouse,
+    deleteRequest: fetchDeleteWarehouse,
+    entityName: '浠撳簱',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetWarehouseMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchWarehousePage({
+        ...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.xlsx',
+      requestExport: (payload) =>
+        fetchExportWarehouseReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildWarehousePrintRows(records),
+      buildPreviewMeta
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWarehouseReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || WAREHOUSE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildWarehouseSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWarehouseSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse/modules/warehouse-detail-drawer.vue b/rsf-design/src/views/basic-info/warehouse/modules/warehouse-detail-drawer.vue
new file mode 100644
index 0000000..6ddd5ac
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse/modules/warehouse-detail-drawer.vue
@@ -0,0 +1,57 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠撳簱璇︽儏"
+    size="960px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="浠撳簱鍚嶇О">{{ detail.name || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵�灞炲伐鍘�">{{ detail.factory || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱鍦板潃">{{ detail.address || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse/modules/warehouse-dialog.vue b/rsf-design/src/views/basic-info/warehouse/modules/warehouse-dialog.vue
new file mode 100644
index 0000000..9d78bf3
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse/modules/warehouse-dialog.vue
@@ -0,0 +1,167 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="880px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildWarehouseDialogModel, createWarehouseFormState, getWarehouseStatusOptions } from '../warehousePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    warehouseData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createWarehouseFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫浠撳簱' : '鏂板浠撳簱'))
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ヤ粨搴撳悕绉�', trigger: 'blur' }],
+    code: [{ required: true, message: '璇疯緭鍏ヤ粨搴撶紪鐮�', trigger: 'blur' }],
+    factory: [{ required: true, message: '璇疯緭鍏ユ墍灞炲伐鍘�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '浠撳簱鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '浠撳簱缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鎵�灞炲伐鍘�',
+      key: 'factory',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ墍灞炲伐鍘�',
+        clearable: true
+      }
+    },
+    {
+      label: '浠撳簱鍦板潃',
+      key: 'address',
+      type: 'input',
+      span: 24,
+      props: {
+        placeholder: '璇疯緭鍏ヤ粨搴撳湴鍧�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getWarehouseStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildWarehouseDialogModel(props.warehouseData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createWarehouseFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.warehouseData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/basic-info/warehouse/warehousePage.helpers.js b/rsf-design/src/views/basic-info/warehouse/warehousePage.helpers.js
new file mode 100644
index 0000000..39e3bd1
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse/warehousePage.helpers.js
@@ -0,0 +1,182 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const WAREHOUSE_REPORT_TITLE = '浠撳簱鎶ヨ〃'
+export const WAREHOUSE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numberValue = Number(value)
+  return Number.isNaN(numberValue) ? fallback : numberValue
+}
+
+export function createWarehouseSearchState() {
+  return {
+    condition: '',
+    factory: '',
+    code: '',
+    name: '',
+    address: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createWarehouseFormState() {
+  return {
+    id: void 0,
+    name: '',
+    code: '',
+    factory: '',
+    address: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getWarehouseStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getWarehousePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWarehouseStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildWarehouseSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    factory: normalizeText(params.factory),
+    code: normalizeText(params.code),
+    name: normalizeText(params.name),
+    address: normalizeText(params.address),
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? Number(params.status)
+        : void 0,
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildWarehousePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWarehouseSearchParams(params)
+  }
+}
+
+export function buildWarehouseSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== void 0 && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    name: normalizeText(formData.name),
+    code: normalizeText(formData.code),
+    factory: normalizeText(formData.factory),
+    address: normalizeText(formData.address),
+    status:
+      formData.status !== void 0 && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo)
+  }
+}
+
+export function buildWarehouseDialogModel(record = {}) {
+  return {
+    ...createWarehouseFormState(),
+    ...(record.id !== void 0 && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    name: normalizeText(record.name || ''),
+    code: normalizeText(record.code || ''),
+    factory: normalizeText(record.factory || ''),
+    address: normalizeText(record.address || ''),
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizeWarehouseDetailRecord(record = {}) {
+  const statusMeta = getWarehouseStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    name: normalizeText(record.name || ''),
+    code: normalizeText(record.code || ''),
+    factory: normalizeText(record.factory || ''),
+    address: normalizeText(record.address || ''),
+    memo: normalizeText(record.memo || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record.createBy$ || record.createByText || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
+
+export function normalizeWarehouseListRow(record = {}) {
+  return normalizeWarehouseDetailRecord(record)
+}
+
+export function buildWarehousePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWarehouseListRow(record))
+}
+
+export function buildWarehouseReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAREHOUSE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAREHOUSE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAREHOUSE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/basic-info/warehouse/warehouseTable.columns.js b/rsf-design/src/views/basic-info/warehouse/warehouseTable.columns.js
new file mode 100644
index 0000000..573a293
--- /dev/null
+++ b/rsf-design/src/views/basic-info/warehouse/warehouseTable.columns.js
@@ -0,0 +1,118 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getWarehouseStatusMeta } from './warehousePage.helpers'
+
+export function createWarehouseTableColumns({ handleView, handleEdit, handleDelete, canEdit = true, canDelete = true } = {}) {
+  const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+  if (canEdit && handleEdit) {
+    operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+  }
+
+  if (canDelete && handleDelete) {
+    operations.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'name',
+      label: '浠撳簱鍚嶇О',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.name || '--'
+    },
+    {
+      prop: 'code',
+      label: '浠撳簱缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'factory',
+      label: '鎵�灞炲伐鍘�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.factory || '--'
+    },
+    {
+      prop: 'address',
+      label: '浠撳簱鍦板潃',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.address || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getWarehouseStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || row.updateBy$ || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || row.updateTime$ || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || row.createBy$ || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || row.createTime$ || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'edit') handleEdit?.(row)
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/dashboard/console/consolePage.helpers.js b/rsf-design/src/views/dashboard/console/consolePage.helpers.js
new file mode 100644
index 0000000..8cd2b64
--- /dev/null
+++ b/rsf-design/src/views/dashboard/console/consolePage.helpers.js
@@ -0,0 +1,164 @@
+import { DEFAULT_REQUEST_GUARD_TIMEOUT, withRequestGuard } from '../../../utils/sys/requestGuard.js'
+
+const EMPTY_SUMMARY = {
+  pendingIn: 0,
+  pendingOut: 0,
+  completedIn: 0,
+  completedOut: 0,
+  taskQty: 0
+}
+const DASHBOARD_SECTION_TIMEOUT = DEFAULT_REQUEST_GUARD_TIMEOUT
+
+function buildDashboardDeadStockQuery() {
+  return {
+    current: 1,
+    pageSize: 10,
+    orderBy: 'create_time asc'
+  }
+}
+
+function buildDashboardTaskQuery() {
+  return {
+    current: 1,
+    pageSize: 100,
+    orderBy: 'sort desc'
+  }
+}
+
+function unwrapDashboardPayload(payload) {
+  let current = payload
+
+  while (
+    current &&
+    typeof current === 'object' &&
+    typeof current.code === 'number' &&
+    Object.prototype.hasOwnProperty.call(current, 'data')
+  ) {
+    current = current.data
+  }
+
+  return current || {}
+}
+
+function normalizeDashboardSummary(payload) {
+  const data = unwrapDashboardPayload(payload)
+  return {
+    pendingIn: normalizeNumber(data?.inAnf),
+    pendingOut: normalizeNumber(data?.outAnf),
+    completedIn: normalizeNumber(data?.taskIn),
+    completedOut: normalizeNumber(data?.taskOut),
+    taskQty: normalizeNumber(data?.taskQty)
+  }
+}
+
+function normalizeDashboardTrend(payload) {
+  const data = unwrapDashboardPayload(payload)
+  const items = Array.isArray(data?.trandItem) ? data.trandItem : []
+
+  return {
+    xAxisData: items.map((item) => formatTrendDate(item?.orderTime)),
+    series: [
+      {
+        name: '鍏ュ簱鏁伴噺',
+        data: items.map((item) => normalizeNumber(item?.inQty))
+      },
+      {
+        name: '鍑哄簱鏁伴噺',
+        data: items.map((item) => normalizeNumber(item?.outQty))
+      }
+    ],
+    maxQty: normalizeNumber(data?.maxQty)
+  }
+}
+
+function normalizeDashboardDeadStockList(payload) {
+  const records = Array.isArray(payload?.records) ? payload.records : []
+
+  return records.map((item) => ({
+    title: [item?.matnrCode, item?.maktx].filter(Boolean).join(' 路 ') || '-',
+    status: `鏁伴噺 ${formatAmount(item?.anfme)} 路 鍛嗘粸 ${item?.deadTime || '-'}`,
+    time: item?.locCode || '-',
+    icon: 'ri:archive-stack-line',
+    class: 'bg-amber-50 text-amber-600'
+  }))
+}
+
+function normalizeDashboardTaskList(payload) {
+  const records = Array.isArray(payload?.records) ? payload.records : []
+
+  return records.map((item) => ({
+    title: item?.taskCode || '-',
+    status: [item?.taskType$, item?.taskStatus$].filter(Boolean).join(' 路 ') || '寰呭鐞嗕换鍔�',
+    time: formatDateTime(item?.createTime),
+    icon: resolveTaskIcon(item?.taskStatus$),
+    class: resolveTaskClass(item?.taskStatus$)
+  }))
+}
+
+function normalizeDashboardLocUsage(payload) {
+  const data = unwrapDashboardPayload(payload)
+  const items = Array.isArray(data) ? data : []
+  return items.map((item) => ({
+    name: item?.name || '-',
+    value: normalizeNumber(item?.value)
+  }))
+}
+
+function withDashboardRequestGuard(task, fallbackValue, timeoutMs = DASHBOARD_SECTION_TIMEOUT) {
+  return withRequestGuard(task, fallbackValue, { timeoutMs })
+}
+
+function formatTrendDate(value) {
+  if (!value) return '--'
+  const text = String(value)
+  return text.length >= 10 ? text.slice(5, 10) : text
+}
+
+function formatDateTime(value) {
+  if (!value) return '--'
+  const text = String(value).replace('T', ' ')
+  if (text.length >= 16) {
+    return text.slice(5, 16)
+  }
+  return text
+}
+
+function formatAmount(value) {
+  const amount = Number(value)
+  if (!Number.isFinite(amount)) return '0'
+  if (Number.isInteger(amount)) return String(amount)
+  return amount.toFixed(2)
+}
+
+function normalizeNumber(value) {
+  const amount = Number(value)
+  return Number.isFinite(amount) ? amount : 0
+}
+
+function resolveTaskIcon(statusText) {
+  const text = String(statusText || '')
+  if (text.includes('瀹屾垚')) return 'ri:checkbox-circle-line'
+  if (text.includes('寮傚父') || text.includes('澶辫触')) return 'ri:error-warning-line'
+  return 'ri:task-line'
+}
+
+function resolveTaskClass(statusText) {
+  const text = String(statusText || '')
+  if (text.includes('瀹屾垚')) return 'bg-emerald-50 text-emerald-600'
+  if (text.includes('寮傚父') || text.includes('澶辫触')) return 'bg-rose-50 text-rose-600'
+  return 'bg-sky-50 text-sky-600'
+}
+
+export {
+  DASHBOARD_SECTION_TIMEOUT,
+  EMPTY_SUMMARY,
+  buildDashboardDeadStockQuery,
+  buildDashboardTaskQuery,
+  normalizeDashboardDeadStockList,
+  normalizeDashboardLocUsage,
+  normalizeDashboardSummary,
+  normalizeDashboardTaskList,
+  normalizeDashboardTrend,
+  unwrapDashboardPayload,
+  withDashboardRequestGuard
+}
diff --git a/rsf-design/src/views/dashboard/console/index.vue b/rsf-design/src/views/dashboard/console/index.vue
index 90691e1..1a2f229 100644
--- a/rsf-design/src/views/dashboard/console/index.vue
+++ b/rsf-design/src/views/dashboard/console/index.vue
@@ -1,145 +1,145 @@
 <template>
-  <div class="art-full-height flex flex-col gap-5">
-    <section
-      class="overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(135deg,var(--art-main-bg-color),var(--art-card-bg-color))] p-6 shadow-[0_24px_80px_rgba(15,23,42,0.08)]"
-    >
-      <div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
-        <div class="max-w-3xl">
-          <div
-            class="mb-3 inline-flex items-center gap-2 rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-medium text-emerald-600"
-          >
-            <span class="size-2 rounded-full bg-emerald-500"></span>
-            RSF Phase 1 Landing
+  <div class="art-full-height flex flex-col gap-6">
+    <section class="grid gap-6 md:grid-cols-2 xl:grid-cols-4" v-loading="sectionLoading.summary">
+      <div
+        v-for="item in summaryCardItems"
+        :key="item.title"
+        class="art-card flex items-start justify-between rounded-3xl px-7 py-6"
+      >
+        <div class="min-w-0 pr-6">
+          <p class="text-sm font-medium text-g-700">{{ item.title }}</p>
+          <ArtCountTo class="mt-3 block text-[2.3rem] font-semibold leading-none text-g-900" :target="item.count" :duration="1400" />
+          <div class="mt-4 flex items-center gap-2 text-sm">
+            <span :class="item.metaTone">{{ item.metaLabel }}</span>
+            <span class="text-g-500">{{ item.metaValue }}</span>
           </div>
-          <h1
-            class="m-0 text-3xl font-semibold tracking-tight text-[var(--art-gray-900)] md:text-4xl"
-          >
-            杩愯楠ㄦ灦宸茬粡鍒囧埌 `rsf-design`
-          </h1>
-          <p class="mt-4 max-w-2xl text-sm leading-7 text-[var(--art-gray-600)] md:text-base">
-            褰撳墠鍏ュ彛宸茬粡鎺ュ叆鐪熷疄鍚庣鐧诲綍銆佸姩鎬佽彍鍗曞拰鏉冮檺閾捐矾銆傝繖涓椤靛彧灞曠ず宸茬粡鍙敤鐨� phase-1
-            鑳藉姏锛屼笉鍐嶄繚鐣欐ā鏉块噷鐨勭ず渚嬪浘琛ㄥ拰婕旂ず鏁版嵁銆�
-          </p>
         </div>
-
-        <div class="rounded-2xl border border-white/10 bg-white/70 px-4 py-3 backdrop-blur-sm">
-          <p class="text-xs uppercase tracking-[0.24em] text-[var(--art-gray-500)]"
-            >Current Entry</p
-          >
-          <p class="mt-2 text-lg font-semibold text-[var(--art-gray-900)]">
-            {{ currentUserName }}
-          </p>
-          <p class="mt-1 text-sm text-[var(--art-gray-600)]">
-            {{ currentUserRoleText }} 路 {{ currentMenuLabel }}
-          </p>
+        <div class="flex size-13 shrink-0 items-center justify-center rounded-2xl" :class="item.iconBoxClass">
+          <ArtSvgIcon :icon="item.icon" class="text-2xl" :class="item.iconClass" />
         </div>
       </div>
     </section>
 
-    <section class="grid gap-4 md:grid-cols-3">
-      <ArtStatsCard
-        title="宸叉帴鍏ュ悗绔�"
-        :count="backendSwitchCount"
-        description="鐧诲綍銆佺敤鎴蜂俊鎭�佽彍鍗曞叏閮ㄦ潵鑷� rsf-server"
-        icon="ri:server-line"
-      />
-      <ArtStatsCard
-        title="鍔ㄦ�佽彍鍗�"
-        :count="visibleMenuCount"
-        description="浠呭彂甯� phase-1 鍏佽杩涘叆鐨勬柊鍏ュ彛"
-        icon="ri:route-line"
-      />
-      <ArtStatsCard
-        title="鏉冮檺閾捐矾"
-        :count="permissionSignalCount"
-        description="瑙掕壊涓庢潈闄愯妭鐐瑰凡浠庣湡瀹炵敤鎴锋暟鎹仮澶�"
-        icon="ri:shield-check-line"
-      />
+    <section class="grid gap-6 xl:grid-cols-[1.35fr_1fr]">
+      <div class="art-card h-115 overflow-hidden p-6 box-border">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>杩� 30 澶╁嚭鍏ュ簱瓒嬪娍</h4>
+            <p>鐪熷疄閾捐矾 <span class="text-success">宸叉帴閫�</span></p>
+          </div>
+        </div>
+        <div class="h-[calc(100%-4.5rem)]">
+          <ArtBarChart
+            height="22rem"
+            :loading="sectionLoading.trend"
+            :data="trendChartSeries"
+            :x-axis-data="trendChartXAxisData"
+            :show-axis-line="false"
+            :show-legend="true"
+            :show-split-line="true"
+            legend-position="top"
+            bar-width="38%"
+          />
+        </div>
+      </div>
+
+      <div class="art-card h-115 overflow-hidden p-6 box-border" v-loading="sectionLoading.locUsage">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>搴撲綅浣跨敤鍒嗗竷</h4>
+            <p>{{ usageLegendCount }} 涓淮搴�</p>
+          </div>
+        </div>
+        <div class="grid h-[calc(100%-4.5rem)] gap-6 lg:grid-cols-[1fr_0.95fr] lg:items-center">
+          <ArtRingChart
+            height="21rem"
+            :data="locUsageList"
+            center-text="搴撲綅鍗犳瘮"
+            :show-legend="false"
+            :show-label="false"
+          />
+
+          <div class="space-y-1">
+            <div
+              v-for="item in usageLegend"
+              :key="item.name"
+              class="flex items-center justify-between border-b border-[var(--art-border-color)] py-4 last:border-b-0"
+            >
+              <div class="flex items-center gap-3">
+                <span class="size-2.5 rounded-full" :style="{ backgroundColor: item.color }"></span>
+                <span class="text-sm text-[var(--art-gray-900)]">{{ item.name }}</span>
+              </div>
+              <span class="text-sm font-medium text-[var(--art-gray-700)]">{{ item.value }}%</span>
+            </div>
+
+            <ElEmpty v-if="!locUsageList.length" description="鏆傛棤搴撲綅浣跨敤鏁版嵁" :image-size="88" />
+          </div>
+        </div>
+      </div>
     </section>
 
-    <section class="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
-      <ElCard
-        class="rounded-3xl border border-white/10 shadow-[0_18px_50px_rgba(15,23,42,0.06)]"
-        shadow="never"
-      >
-        <template #header>
-          <div class="flex items-center justify-between">
-            <div>
-              <h2 class="m-0 text-base font-semibold text-[var(--art-gray-900)]">鐪熷疄杩愯鐘舵��</h2>
-              <p class="mt-1 text-xs text-[var(--art-gray-500)]">
-                杩欎簺淇℃伅鏉ヨ嚜褰撳墠鐧诲綍鐢ㄦ埛鍜岃彍鍗� store
-              </p>
-            </div>
-            <ElTag type="success" effect="light">Backend mode</ElTag>
-          </div>
-        </template>
-
-        <div class="grid gap-4 md:grid-cols-2">
-          <div class="rounded-2xl bg-[var(--art-gray-50)] p-4">
-            <p class="text-xs uppercase tracking-[0.2em] text-[var(--art-gray-500)]">User</p>
-            <p class="mt-3 text-xl font-semibold text-[var(--art-gray-900)]">
-              {{ currentUserName }}
-            </p>
-            <p class="mt-2 text-sm text-[var(--art-gray-600)]">
-              {{ currentUserRoleText }}
-            </p>
-            <div class="mt-4 flex flex-wrap gap-2">
-              <ElTag v-for="role in currentRoles" :key="role" type="info" effect="plain">
-                {{ role }}
-              </ElTag>
-              <ElTag v-if="!currentRoles.length" type="info" effect="plain">No roles</ElTag>
-            </div>
-          </div>
-
-          <div class="rounded-2xl bg-[var(--art-gray-50)] p-4">
-            <p class="text-xs uppercase tracking-[0.2em] text-[var(--art-gray-500)]">Permissions</p>
-            <p class="mt-3 text-xl font-semibold text-[var(--art-gray-900)]">
-              {{ currentAuthorities.length }} auth nodes
-            </p>
-            <p class="mt-2 text-sm text-[var(--art-gray-600)]">
-              鏉冮檺鑺傜偣鐩存帴鏉ヨ嚜褰撳墠鐢ㄦ埛鐨勭湡瀹� `authorities` 杞借嵎锛屼笉鍐嶄緷璧栨ā鏉挎紨绀烘�併��
-            </p>
-            <div class="mt-4 flex flex-wrap gap-2">
-              <ElTag v-for="item in previewAuthorities" :key="item" type="warning" effect="plain">
-                {{ item }}
-              </ElTag>
-              <ElTag v-if="!previewAuthorities.length" type="warning" effect="plain">
-                No authorities
-              </ElTag>
-            </div>
+    <section class="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
+      <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.tasks">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>鎵ц涓换鍔�</h4>
+            <p>{{ taskSubtitle }}</p>
           </div>
         </div>
-      </ElCard>
 
-      <ElCard
-        class="rounded-3xl border border-white/10 shadow-[0_18px_50px_rgba(15,23,42,0.06)]"
-        shadow="never"
-      >
-        <template #header>
-          <div>
-            <h2 class="m-0 text-base font-semibold text-[var(--art-gray-900)]">鑿滃崟鎺ュ叆娓呭崟</h2>
-            <p class="mt-1 text-xs text-[var(--art-gray-500)]">
-              褰撳墠鑿滃崟鏍戠敤浜庨獙璇� `rsf-design` 鏄惁鐪熸鎺ヤ綇鍚庣鍙戝竷
-            </p>
-          </div>
-        </template>
-
-        <div class="space-y-3">
-          <div
-            v-for="item in menuPreview"
-            :key="item.key"
-            class="flex items-center justify-between rounded-2xl border border-[var(--art-gray-200)] px-4 py-3"
-          >
-            <div>
-              <p class="text-sm font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
-              <p class="mt-1 text-xs text-[var(--art-gray-500)]">{{ item.description }}</p>
+        <div class="mt-3 h-[calc(100%-3.75rem)] overflow-hidden">
+          <ElScrollbar>
+            <div
+              v-for="item in taskCardItems"
+              :key="`${item.time}-${item.title}`"
+              class="flex gap-4 border-b border-g-300 py-4 last:border-b-0"
+            >
+              <div class="flex flex-col items-center pt-1">
+                <span class="size-3 rounded-full bg-[var(--el-color-primary)]"></span>
+                <span class="mt-2 min-h-10 w-px bg-[var(--art-border-color)]"></span>
+              </div>
+              <div class="min-w-0 flex-1">
+                <p class="text-xs text-g-500">{{ item.time }}</p>
+                <div class="mt-2 flex items-center gap-2">
+                  <p class="truncate text-base font-medium text-[var(--art-gray-900)]">
+                    {{ item.title }}
+                  </p>
+                  <ElTag size="small" effect="light" :type="item.tagType">{{ item.tagText }}</ElTag>
+                </div>
+                <p class="mt-2 text-sm text-g-600">{{ item.subtitle }}</p>
+              </div>
             </div>
-            <ElTag :type="item.type" effect="light">
-              {{ item.value }}
-            </ElTag>
+          </ElScrollbar>
+        </div>
+      </div>
+
+      <div class="art-card h-98 p-5 box-border" v-loading="sectionLoading.deadStock">
+        <div class="art-card-header">
+          <div class="title">
+            <h4>搴撳瓨鏈�杩戝姩鎬�</h4>
+            <p>{{ deadStockSubtitle }}</p>
           </div>
         </div>
-      </ElCard>
+
+        <div class="mt-3 h-[calc(100%-3.75rem)] overflow-hidden">
+          <ElScrollbar>
+            <div
+              v-for="item in stockCardItems"
+              :key="`${item.title}-${item.time}`"
+              class="flex items-center gap-4 border-b border-g-300 py-4 last:border-b-0"
+            >
+              <div class="size-12 rounded-2xl bg-[var(--el-color-primary-light-9)] flex-cc">
+                <ArtSvgIcon :icon="item.icon" class="text-xl text-[var(--el-color-primary)]" />
+              </div>
+              <div class="min-w-0 flex-1">
+                <p class="truncate text-base font-medium text-[var(--art-gray-900)]">{{ item.title }}</p>
+                <p class="mt-1 text-sm text-g-500">{{ item.status }}</p>
+              </div>
+              <div class="max-w-40 text-right text-sm text-g-500">{{ item.time }}</div>
+            </div>
+          </ElScrollbar>
+        </div>
+      </div>
     </section>
   </div>
 </template>
@@ -147,15 +147,42 @@
 <script setup>
   import { storeToRefs } from 'pinia'
   import { useUserStore } from '@/store/modules/user'
-  import { useMenuStore } from '@/store/modules/menu'
-  import { formatMenuTitle } from '@/utils/router'
+  import {
+    fetchDashboardHeader,
+    fetchDashboardTrend,
+    fetchDashboardDeadStock,
+    fetchDashboardLocUsage,
+    fetchDashboardTasks
+  } from '@/api/dashboard'
+  import {
+    EMPTY_SUMMARY,
+    buildDashboardDeadStockQuery,
+    buildDashboardTaskQuery,
+    normalizeDashboardSummary,
+    normalizeDashboardTrend,
+    normalizeDashboardTaskList,
+    normalizeDashboardLocUsage,
+    normalizeDashboardDeadStockList,
+    withDashboardRequestGuard
+  } from './consolePage.helpers'
 
   defineOptions({ name: 'Console' })
 
+  const summary = ref({ ...EMPTY_SUMMARY })
+  const trendModel = ref(normalizeDashboardTrend())
+  const taskList = ref([])
+  const deadStockList = ref([])
+  const locUsageList = ref([])
+  const sectionLoading = reactive({
+    summary: false,
+    trend: false,
+    deadStock: false,
+    locUsage: false,
+    tasks: false
+  })
+
   const userStore = useUserStore()
-  const menuStore = useMenuStore()
   const { getUserInfo } = storeToRefs(userStore)
-  const { menuList } = storeToRefs(menuStore)
 
   const currentUser = computed(() => getUserInfo.value || {})
   const currentUserName = computed(() => {
@@ -166,84 +193,191 @@
       'RSF User'
     )
   })
-  const currentRoles = computed(() => {
-    const roles = currentUser.value.roles
-    if (!Array.isArray(roles)) return []
-    return roles
-      .map((role) => {
-        if (typeof role === 'string') {
-          return role
-        }
-        return role?.code || role?.name || role?.title || ''
-      })
-      .filter(Boolean)
+  const currentDateText = computed(() => {
+    return new Date().toLocaleDateString('zh-CN', {
+      year: 'numeric',
+      month: '2-digit',
+      day: '2-digit'
+    })
   })
-  const currentAuthorities = computed(() => {
-    const authorities = currentUser.value.authorities
-    if (!Array.isArray(authorities)) return []
-    return authorities.map((item) => item?.authority || '').filter(Boolean)
-  })
-  const currentMenuLabel = computed(() => {
-    const firstMenu = menuList.value?.[0]
-    return formatMenuTitle(firstMenu?.meta?.title || 'menus.dashboard.console')
-  })
-  const currentUserRoleText = computed(() => {
-    if (!currentRoles.value.length) {
-      return 'No role information yet'
+  const summaryCardItems = computed(() => [
+    {
+      title: '寰呭叆搴撴暟閲�',
+      count: summary.value.pendingIn,
+      metaLabel: '鎴嚦',
+      metaValue: currentDateText.value,
+      metaTone: 'text-g-500',
+      icon: 'ri:pie-chart-line',
+      iconBoxClass: 'bg-[var(--el-color-primary-light-9)]',
+      iconClass: 'text-[var(--el-color-primary)]'
+    },
+    {
+      title: '寰呭嚭搴撴暟閲�',
+      count: summary.value.pendingOut,
+      metaLabel: '鐘舵��',
+      metaValue: '褰撳墠寰呮墽琛屽嚭搴撻噺',
+      metaTone: 'text-success',
+      icon: 'ri:fire-line',
+      iconBoxClass: 'bg-[rgba(255,175,32,0.14)]',
+      iconClass: 'text-[#FFAF20]'
+    },
+    {
+      title: '宸插叆搴撴暟閲�',
+      count: summary.value.completedIn,
+      metaLabel: '缁撴灉',
+      metaValue: '绱瀹屾垚鍏ュ簱缁撴灉',
+      metaTone: 'text-g-500',
+      icon: 'ri:archive-line',
+      iconBoxClass: 'bg-[rgba(20,222,186,0.14)]',
+      iconClass: 'text-[#14DEBA]'
+    },
+    {
+      title: '鎵ц涓换鍔�',
+      count: summary.value.taskQty,
+      metaLabel: '褰撳墠鐢ㄦ埛',
+      metaValue: currentUserName.value,
+      metaTone: 'text-g-500',
+      icon: 'ri:progress-2-line',
+      iconBoxClass: 'bg-[rgba(139,92,246,0.16)]',
+      iconClass: 'text-[#8B5CF6]'
     }
-    return `Roles: ${currentRoles.value.join(' / ')}`
-  })
-  const backendSwitchCount = computed(() => {
-    return currentUserName.value !== 'RSF User' || menuList.value.length > 0 ? 1 : 0
-  })
-  const visibleMenuCount = computed(() => countVisibleMenus(menuList.value))
-  const permissionSignalCount = computed(() => {
-    return currentRoles.value.length + currentAuthorities.value.length
-  })
-  const previewAuthorities = computed(() => currentAuthorities.value.slice(0, 4))
-  const menuPreview = computed(() => {
-    const total = visibleMenuCount.value
-    const rootCount = Array.isArray(menuList.value) ? menuList.value.length : 0
-    const firstMenu = menuList.value?.[0]
-    const firstChildren = Array.isArray(firstMenu?.children) ? firstMenu.children.length : 0
+  ])
+  const trendDisplayModel = computed(() => {
+    const xAxisData = Array.isArray(trendModel.value.xAxisData) ? trendModel.value.xAxisData : []
+    const series = Array.isArray(trendModel.value.series) ? trendModel.value.series : []
+    if (!xAxisData.length || !series.length) {
+      return { xAxisData: [], series: [] }
+    }
 
-    return [
-      {
-        key: 'entry',
-        title: '鍏ュ彛妯″紡',
-        description: '褰撳墠椤甸潰浠� backend mode 杩愯锛岃彍鍗曠敱鏈嶅姟绔┍鍔�',
-        value: 'backend',
-        type: 'success'
-      },
-      {
-        key: 'menus',
-        title: '鍙鑿滃崟',
-        description: '宸查�氳繃鍔ㄦ�佽矾鐢遍�傞厤鍚庤繘鍏ュ墠绔彍鍗曟爲',
-        value: `${total}`,
-        type: 'primary'
-      },
-      {
-        key: 'root',
-        title: '涓�绾х洰褰�',
-        description: '鏍圭骇鑿滃崟鑺傜偣鏁伴噺',
-        value: `${rootCount}`,
-        type: 'info'
-      },
-      {
-        key: 'children',
-        title: '棣栦釜鐩綍瀛愰」',
-        description: '鐢ㄤ簬蹇�熺‘璁よ彍鍗曟爲宸茶姝g‘灞曞紑',
-        value: `${firstChildren}`,
-        type: 'warning'
+    const bucketSize = Math.max(1, Math.ceil(xAxisData.length / 8))
+    const bucketLabels = []
+    const bucketSeries = series.map((item) => ({
+      ...item,
+      data: []
+    }))
+
+    for (let index = 0; index < xAxisData.length; index += bucketSize) {
+      const labelBucket = xAxisData.slice(index, index + bucketSize)
+      bucketLabels.push(labelBucket[labelBucket.length - 1] || '--')
+      bucketSeries.forEach((seriesItem, seriesIndex) => {
+        const sourceBucket = Array.isArray(series[seriesIndex]?.data)
+          ? series[seriesIndex].data.slice(index, index + bucketSize)
+          : []
+        const bucketTotal = sourceBucket.reduce((total, value) => total + Number(value || 0), 0)
+        seriesItem.data.push(bucketTotal)
+      })
+    }
+
+    const visibleIndexes = bucketLabels.reduce((indexes, _, index) => {
+      const hasData = bucketSeries.some((item) => Number(item.data[index] || 0) > 0)
+      if (hasData) {
+        indexes.push(index)
       }
-    ]
+      return indexes
+    }, [])
+
+    const effectiveIndexes =
+      visibleIndexes.length > 0
+        ? visibleIndexes
+        : bucketLabels.map((_, index) => index).slice(-8)
+
+    return {
+      xAxisData: effectiveIndexes.map((index) => bucketLabels[index]),
+      series: bucketSeries.map((item) => ({
+        ...item,
+        data: effectiveIndexes.map((index) => item.data[index])
+      }))
+    }
+  })
+  const trendChartXAxisData = computed(() => trendDisplayModel.value.xAxisData)
+  const trendChartSeries = computed(() => trendDisplayModel.value.series)
+  const taskSubtitle = computed(() => `鏈�杩� ${taskList.value.length} 鏉′换鍔″姩鎬乣)
+  const deadStockSubtitle = computed(() => `鏈�杩� ${deadStockList.value.length} 鏉″簱瀛樿褰昤)
+  const usageLegendCount = computed(() => locUsageList.value.length)
+  const usageLegend = computed(() => {
+    const palette = ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#E8684A', '#6DC8EC']
+    return locUsageList.value.map((item, index) => ({
+      ...item,
+      color: palette[index % palette.length]
+    }))
+  })
+  const taskCardItems = computed(() =>
+    taskList.value.slice(0, 6).map((item) => ({
+      title: item.title,
+      time: item.time,
+      subtitle: item.status,
+      tagText: resolveTaskTagText(item.status),
+      tagType: resolveTaskTagType(item.class)
+    }))
+  )
+  const stockCardItems = computed(() => deadStockList.value.slice(0, 6))
+
+  onMounted(() => {
+    loadDashboard()
   })
 
-  function countVisibleMenus(items) {
-    if (!Array.isArray(items)) return 0
-    return items.reduce((total, item) => {
-      const current = item?.meta?.isHide ? 0 : 1
-      return total + current + countVisibleMenus(item?.children)
-    }, 0)
+  function loadDashboard() {
+    void loadSummarySection()
+    void loadTrendSection()
+    void loadDeadStockSection()
+    void loadLocUsageSection()
+    void loadTaskSection()
+  }
+
+  async function loadSummarySection() {
+    sectionLoading.summary = true
+    const payload = await withDashboardRequestGuard(fetchDashboardHeader(), null)
+    summary.value = normalizeDashboardSummary(payload)
+    sectionLoading.summary = false
+  }
+
+  async function loadTrendSection() {
+    sectionLoading.trend = true
+    const payload = await withDashboardRequestGuard(fetchDashboardTrend(), null)
+    trendModel.value = normalizeDashboardTrend(payload)
+    sectionLoading.trend = false
+  }
+
+  async function loadDeadStockSection() {
+    sectionLoading.deadStock = true
+    const payload = await withDashboardRequestGuard(
+      fetchDashboardDeadStock(buildDashboardDeadStockQuery()),
+      {}
+    )
+    deadStockList.value = normalizeDashboardDeadStockList(payload || {})
+    sectionLoading.deadStock = false
+  }
+
+  async function loadLocUsageSection() {
+    sectionLoading.locUsage = true
+    const payload = await withDashboardRequestGuard(fetchDashboardLocUsage(), null)
+    locUsageList.value = normalizeDashboardLocUsage(payload)
+    sectionLoading.locUsage = false
+  }
+
+  async function loadTaskSection() {
+    sectionLoading.tasks = true
+    const payload = await withDashboardRequestGuard(
+      fetchDashboardTasks(buildDashboardTaskQuery()),
+      {}
+    )
+    taskList.value = normalizeDashboardTaskList(payload || {})
+    sectionLoading.tasks = false
+  }
+
+  function resolveTaskTagType(statusClass) {
+    const text = String(statusClass || '')
+    if (text.includes('emerald')) return 'success'
+    if (text.includes('rose')) return 'danger'
+    return 'primary'
+  }
+
+  function resolveTaskTagText(statusText) {
+    const text = String(statusText || '')
+      .replace(/\b\d+\./g, '')
+      .replace(/\s+/g, ' ')
+      .trim()
+    const parts = text.split('路').map((item) => item.trim()).filter(Boolean)
+    return parts[parts.length - 1] || '澶勭悊涓�'
   }
 </script>
diff --git a/rsf-design/src/views/manager/freeze/freezePage.helpers.js b/rsf-design/src/views/manager/freeze/freezePage.helpers.js
new file mode 100644
index 0000000..f5ece80
--- /dev/null
+++ b/rsf-design/src/views/manager/freeze/freezePage.helpers.js
@@ -0,0 +1,98 @@
+export const FREEZE_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createFreezeSearchState() {
+  return {
+    condition: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    trackCode: ''
+  }
+}
+
+export function buildFreezePageQueryParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  ;['condition', 'locCode', 'matnrCode', 'maktx', 'batch', 'trackCode'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function getFreezeDynamicFieldKey(fieldName) {
+  return `${FREEZE_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeFreezeEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function attachFreezeDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+
+  enabledFields.forEach((field) => {
+    dynamicValues[getFreezeDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+
+  return {
+    ...record,
+    ...dynamicValues
+  }
+}
+
+export function normalizeFreezeRow(record = {}, enabledFields = []) {
+  return attachFreezeDynamicFields(
+    {
+      ...record,
+      locCode: record.locCode || '-',
+      wareArea: record.wareArea || '-',
+      matnrCode: record.matnrCode || '-',
+      maktx: record.maktx || '-',
+      batch: record.batch || '-',
+      trackCode: record.trackCode || '-',
+      unit: record.unit || '-',
+      anfme: normalizeNumber(record.anfme),
+      qty: normalizeNumber(record.qty),
+      workQty: normalizeNumber(record.workQty),
+      splrName: record.splrName || '-',
+      statusText: record['status$'] || '鍐荤粨',
+      updateTimeText: record['updateTime$'] || record.updateTime || '-',
+      createTimeText: record['createTime$'] || record.createTime || '-',
+      memo: record.memo || '-'
+    },
+    enabledFields
+  )
+}
+
+export function normalizeFreezeDetail(record = {}, enabledFields = []) {
+  return normalizeFreezeRow(record, enabledFields)
+}
diff --git a/rsf-design/src/views/manager/freeze/freezeTable.columns.js b/rsf-design/src/views/manager/freeze/freezeTable.columns.js
new file mode 100644
index 0000000..f4f7d10
--- /dev/null
+++ b/rsf-design/src/views/manager/freeze/freezeTable.columns.js
@@ -0,0 +1,101 @@
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFreezeTableColumns({ enabledFields = [] }) {
+  const dynamicColumns = enabledFields.map((field) => ({
+    prop: `extendField__${field.fields}`,
+    label: field.fieldsAlise,
+    minWidth: 140,
+    showOverflowTooltip: true,
+    formatter: (row) => row[`extendField__${field.fields}`] || '-'
+  }))
+
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wareArea',
+      label: '搴撳尯',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'trackCode',
+      label: '杩借釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '鍙敤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '搴撳瓨鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    ...dynamicColumns,
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'action',
+      label: '璇︽儏',
+      width: 100,
+      fixed: 'right',
+      useSlot: true,
+      align: 'center'
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/manager/freeze/index.vue b/rsf-design/src/views/manager/freeze/index.vue
new file mode 100644
index 0000000..a7adef4
--- /dev/null
+++ b/rsf-design/src/views/manager/freeze/index.vue
@@ -0,0 +1,227 @@
+<template>
+  <div class="freeze-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      >
+        <template #action="{ row }">
+          <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" />
+        </template>
+      </ArtTable>
+    </ElCard>
+
+    <FreezeDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchEnabledFields, fetchFreezeDetail, fetchFreezePage } from '@/api/freeze'
+  import FreezeDetailDrawer from './modules/freeze-detail-drawer.vue'
+  import { createFreezeTableColumns } from './freezeTable.columns'
+  import {
+    buildFreezePageQueryParams,
+    createFreezeSearchState,
+    getFreezeDynamicFieldKey,
+    normalizeFreezeDetail,
+    normalizeFreezeEnabledFields,
+    normalizeFreezeRow
+  } from './freezePage.helpers'
+
+  defineOptions({ name: 'Freeze' })
+
+  const loading = ref(false)
+  const tableData = ref([])
+  const enabledFields = ref([])
+  const searchForm = ref(createFreezeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�/鐗╂枡缂栫爜'
+      }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'locCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '杩借釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ拷韪爜'
+      }
+    },
+    ...enabledFields.value.map((field) => ({
+      label: field.fieldsAlise,
+      key: getFreezeDynamicFieldKey(field.fields),
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: `璇疯緭鍏�${field.fieldsAlise}`
+      }
+    }))
+  ])
+
+  const { columns, columnChecks, resetColumns } = useTableColumns(() =>
+    createFreezeTableColumns({
+      enabledFields: enabledFields.value
+    })
+  )
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeFreezeEnabledFields(fields)
+    enabledFields.value.forEach((field) => {
+      const dynamicKey = getFreezeDynamicFieldKey(field.fields)
+      if (searchForm.value[dynamicKey] === undefined) {
+        searchForm.value[dynamicKey] = ''
+      }
+    })
+    resetColumns()
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchFreezePage(
+          buildFreezePageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '鍐荤粨搴撳瓨鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeFreezeRow(record, enabledFields.value))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function openDetailDrawer(row) {
+    detailDrawerVisible.value = true
+    detailData.value = normalizeFreezeDetail(
+      await guardRequestWithMessage(fetchFreezeDetail(row.id), {}, {
+        timeoutMessage: '鍐荤粨搴撳瓨璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }),
+      enabledFields.value
+    )
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createFreezeSearchState()
+    enabledFields.value.forEach((field) => {
+      searchForm.value[getFreezeDynamicFieldKey(field.fields)] = ''
+    })
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  onMounted(async () => {
+    await loadEnabledFieldDefinitions()
+    await loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/freeze/modules/freeze-detail-drawer.vue b/rsf-design/src/views/manager/freeze/modules/freeze-detail-drawer.vue
new file mode 100644
index 0000000..0938e12
--- /dev/null
+++ b/rsf-design/src/views/manager/freeze/modules/freeze-detail-drawer.vue
@@ -0,0 +1,39 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍐荤粨搴撳瓨璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElDescriptions :column="3" border>
+      <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="搴撳尯">{{ detail.wareArea || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="杩借釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍙敤鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="搴撳瓨鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鎵ц涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.splrName || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+    </ElDescriptions>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/in-statistic-item/inStatisticItemPage.helpers.js b/rsf-design/src/views/manager/in-statistic-item/inStatisticItemPage.helpers.js
new file mode 100644
index 0000000..c61ea0a
--- /dev/null
+++ b/rsf-design/src/views/manager/in-statistic-item/inStatisticItemPage.helpers.js
@@ -0,0 +1,86 @@
+import {
+  getInStatisticTaskStatusMeta,
+  getInStatisticTaskTypeMeta
+} from '../../statistics/in-statistic/inStatisticPage.helpers.js'
+
+export const IN_STATISTIC_ITEM_PAGE_TITLE = '鍏ュ簱缁熻鏄庣粏'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function createInStatisticItemSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    maktx: '',
+    matnrCode: '',
+    batch: ''
+  }
+}
+
+export function getInStatisticItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildInStatisticItemSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    dayTime: normalizeText(params.dayTime),
+    maktx: normalizeText(params.maktx),
+    matnrCode: normalizeText(params.matnrCode),
+    batch: normalizeText(params.batch),
+    taskType: normalizeNumber(params.taskType, 1),
+    taskStatus: normalizeNumber(params.taskStatus, 100)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildInStatisticItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildInStatisticItemSearchParams(params)
+  }
+}
+
+export function normalizeInStatisticItemRow(record = {}) {
+  const taskTypeMeta = getInStatisticTaskTypeMeta(record.taskType ?? record.task_type)
+  const taskStatusMeta = getInStatisticTaskStatusMeta(record.taskStatus ?? record.task_status)
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    dayTimeText: normalizeText(record.dayTime || record.day_time || ''),
+    taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text),
+    taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info',
+    taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text),
+    taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info',
+    locCode: normalizeText(record.locCode || record.loc_code || ''),
+    barcode: normalizeText(record.barcode || ''),
+    matnrCode: normalizeText(record.matnrCode || record.matnr_code || ''),
+    maktx: normalizeText(record.maktx || ''),
+    batch: normalizeText(record.batch || ''),
+    unit: normalizeText(record.unit || ''),
+    anfme: record.anfme ?? '--',
+    fieldsIndex: normalizeText(record.fieldsIndex || record.fields_index || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '')
+  }
+}
diff --git a/rsf-design/src/views/manager/in-statistic-item/inStatisticItemTable.columns.js b/rsf-design/src/views/manager/in-statistic-item/inStatisticItemTable.columns.js
new file mode 100644
index 0000000..4c49e25
--- /dev/null
+++ b/rsf-design/src/views/manager/in-statistic-item/inStatisticItemTable.columns.js
@@ -0,0 +1,117 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createInStatisticItemTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
+    {
+      prop: 'dayTimeText',
+      label: '缁熻鏃ユ湡',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.dayTimeText || '--'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/in-statistic-item/index.vue b/rsf-design/src/views/manager/in-statistic-item/index.vue
new file mode 100644
index 0000000..1809adb
--- /dev/null
+++ b/rsf-design/src/views/manager/in-statistic-item/index.vue
@@ -0,0 +1,157 @@
+<template>
+  <div class="in-statistic-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <InStatisticItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchGetInStatisticItemDetail, fetchInStatisticItemPage } from '@/api/in-statistic-item'
+  import {
+    buildInStatisticItemPageQueryParams,
+    buildInStatisticItemSearchParams,
+    createInStatisticItemSearchState,
+    getInStatisticItemPaginationKey,
+    normalizeInStatisticItemRow
+  } from './inStatisticItemPage.helpers'
+  import { createInStatisticItemTableColumns } from './inStatisticItemTable.columns'
+  import InStatisticItemDetailDrawer from './modules/in-statistic-item-detail-drawer.vue'
+
+  defineOptions({ name: 'InStatisticItem' })
+
+  const searchForm = ref(createInStatisticItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�/缂栫爜/鎵规'
+      }
+    },
+    {
+      label: '缁熻鏃ユ湡',
+      key: 'dayTime',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    const normalizedRow = normalizeInStatisticItemRow(row)
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    detailData.value = normalizedRow
+
+    try {
+      if (row?.id === void 0 || row?.id === null || row?.id === '') {
+        return
+      }
+      const response = await fetchGetInStatisticItemDetail(row.id)
+      const detail = response?.data?.data ?? response?.data ?? response ?? {}
+      detailData.value = normalizeInStatisticItemRow(detail)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchInStatisticItemPage,
+      apiParams: buildInStatisticItemPageQueryParams(searchForm.value),
+      paginationKey: getInStatisticItemPaginationKey(),
+      columnsFactory: () =>
+        createInStatisticItemTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeInStatisticItemRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildInStatisticItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createInStatisticItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/in-statistic-item/modules/in-statistic-item-detail-drawer.vue b/rsf-design/src/views/manager/in-statistic-item/modules/in-statistic-item-detail-drawer.vue
new file mode 100644
index 0000000..5c2afed
--- /dev/null
+++ b/rsf-design/src/views/manager/in-statistic-item/modules/in-statistic-item-detail-drawer.vue
@@ -0,0 +1,58 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    :title="IN_STATISTIC_ITEM_PAGE_TITLE + '璇︽儏'"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElSkeleton v-if="loading" :rows="10" animated />
+        <ElDescriptions v-else :column="4" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁熻鏃ユ湡">{{ detail.dayTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">
+            <ElTag :type="detail.taskTypeTagType || 'info'" effect="light">
+              {{ detail.taskTypeText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">
+            <ElTag :type="detail.taskStatusTagType || 'info'" effect="light">
+              {{ detail.taskStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { IN_STATISTIC_ITEM_PAGE_TITLE } from '../inStatisticItemPage.helpers'
+
+  defineOptions({ name: 'InStatisticItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/loc-dead-report/index.vue b/rsf-design/src/views/manager/loc-dead-report/index.vue
new file mode 100644
index 0000000..4ec4b8b
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-dead-report/index.vue
@@ -0,0 +1,402 @@
+<template>
+  <div class="loc-dead-report-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <LocDeadReportDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportLocDeadReport,
+    fetchGetLocDeadReportDetail,
+    fetchGetLocDeadReportMany,
+    fetchLocDeadReportPage
+  } from '@/api/loc-dead-report'
+  import LocDeadReportDetailDrawer from './modules/loc-dead-report-detail-drawer.vue'
+  import { createLocDeadReportTableColumns } from './locDeadReportTable.columns'
+  import {
+    LOC_DEAD_REPORT_TITLE,
+    buildLocDeadReportPageQueryParams,
+    buildLocDeadReportPrintRows,
+    createLocDeadReportSearchState,
+    getLocDeadReportPaginationKey,
+    getLocDeadReportReportColumns,
+    normalizeLocDeadReportDetail,
+    normalizeLocDeadReportRow
+  } from './locDeadReportPage.helpers'
+
+  defineOptions({ name: 'LocDeadReport' })
+
+  const userStore = useUserStore()
+  const reportTitle = LOC_DEAD_REPORT_TITLE
+  const searchForm = ref(createLocDeadReportSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const reportColumns = getLocDeadReportReportColumns()
+
+  const reportQueryParams = computed(() => buildLocDeadReportPageQueryParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�/鐗╂枡缂栫爜/澶囨敞'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'locCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�'
+      }
+    },
+    {
+      label: '搴撲綅ID',
+      key: 'locId',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ュ簱浣岻D'
+      }
+    },
+    {
+      label: '鍗曟嵁ID',
+      key: 'orderId',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ュ崟鎹甀D'
+      }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'type',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鎹被鍨�'
+      }
+    },
+    {
+      label: '璁㈠崟鏄庣粏ID',
+      key: 'orderItemId',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ヨ鍗曟槑缁咺D'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�'
+      }
+    },
+    {
+      label: '鐗╂枡ID',
+      key: 'matnrId',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ョ墿鏂橧D'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '璺熻釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ窡韪爜'
+      }
+    },
+    {
+      label: '鍗曚綅',
+      key: 'unit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟浣�'
+      }
+    },
+    {
+      label: '鏁伴噺',
+      key: 'anfme',
+      type: 'number',
+      props: {
+        clearable: true,
+        controls: false,
+        placeholder: '璇疯緭鍏ユ暟閲�'
+      }
+    },
+    {
+      label: '搴撳瓨鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樻壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '瑙勬牸',
+      key: 'spec',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鏍�'
+      }
+    },
+    {
+      label: '鍨嬪彿',
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瀷鍙�'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchLocDeadReportPage,
+      apiParams: buildLocDeadReportPageQueryParams(searchForm.value),
+      paginationKey: getLocDeadReportPaginationKey(),
+      columnsFactory: () =>
+        createLocDeadReportTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeLocDeadReportRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetLocDeadReportMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchLocDeadReportPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-dead-report.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocDeadReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildLocDeadReportPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetLocDeadReportDetail(id), {}, {
+        timeoutMessage: '搴撳瓨鍋滄粸璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeLocDeadReportDetail({
+        ...fallback,
+        ...detail
+      })
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocDeadReportPageQueryParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createLocDeadReportSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/loc-dead-report/locDeadReportPage.helpers.js b/rsf-design/src/views/manager/loc-dead-report/locDeadReportPage.helpers.js
new file mode 100644
index 0000000..6cffe2a
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-dead-report/locDeadReportPage.helpers.js
@@ -0,0 +1,198 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const LOC_DEAD_REPORT_TITLE = '搴撳瓨鍋滄粸鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+export function createLocDeadReportSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    locCode: '',
+    locId: '',
+    orderId: '',
+    type: '',
+    orderItemId: '',
+    wkType: '',
+    matnrId: '',
+    maktx: '',
+    matnrCode: '',
+    trackCode: '',
+    unit: '',
+    anfme: '',
+    batch: '',
+    splrBatch: '',
+    spec: '',
+    model: '',
+    fieldsIndex: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getLocDeadReportPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildLocDeadReportSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'locCode',
+    'type',
+    'maktx',
+    'matnrCode',
+    'trackCode',
+    'unit',
+    'batch',
+    'splrBatch',
+    'spec',
+    'model',
+    'fieldsIndex',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['locId', 'orderId', 'orderItemId', 'wkType', 'matnrId', 'anfme', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildLocDeadReportPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    orderBy: params.orderBy || 'create_time asc',
+    ...buildLocDeadReportSearchParams(params)
+  }
+}
+
+export function getLocDeadReportStatusMeta(value) {
+  return STATUS_META[Number(value)] || { text: '-', type: 'info' }
+}
+
+export function normalizeLocDeadReportRow(record = {}) {
+  const statusMeta = getLocDeadReportStatusMeta(record.status)
+  return {
+    ...record,
+    id: record.id ?? '--',
+    locCode: record.locCode || '--',
+    deadTime: record.deadTime ?? record['deadTime$'] ?? '--',
+    locId: record.locId ?? '--',
+    orderId: record.orderId ?? '--',
+    typeText: record['type$'] || record.type || '--',
+    orderItemId: record.orderItemId ?? '--',
+    wkTypeText: record['wkType$'] || record.wkType || '--',
+    matnrId: record.matnrId ?? '--',
+    matnrCode: record.matnrCode || '--',
+    maktx: record.maktx || '--',
+    trackCode: record.trackCode || '--',
+    unit: record.unit || '--',
+    anfme: record.anfme ?? '--',
+    batch: record.batch || '--',
+    splrBatch: record.splrBatch || '--',
+    spec: record.spec || '--',
+    model: record.model || '--',
+    fieldsIndex: record.fieldsIndex || '--',
+    createByText: record['createBy$'] || record.createByText || '--',
+    createTimeText: record['createTime$'] || record.createTime || '--',
+    updateByText: record['updateBy$'] || record.updateByText || '--',
+    updateTimeText: record['updateTime$'] || record.updateTime || '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: record.memo || '--'
+  }
+}
+
+export function normalizeLocDeadReportDetail(record = {}) {
+  return normalizeLocDeadReportRow(record)
+}
+
+export function getLocDeadReportReportColumns() {
+  return [
+    { source: 'locCode', label: '搴撲綅缂栫爜' },
+    { source: 'deadTime', label: '鍋滅暀鏃堕棿锛堝ぉ锛�', align: 'right' },
+    { source: 'locId', label: '搴撲綅ID', align: 'right' },
+    { source: 'orderId', label: '鍗曟嵁ID', align: 'right' },
+    { source: 'typeText', label: '鍗曟嵁绫诲瀷' },
+    { source: 'orderItemId', label: '璁㈠崟鏄庣粏ID', align: 'right' },
+    { source: 'wkTypeText', label: '涓氬姟绫诲瀷' },
+    { source: 'matnrId', label: '鐗╂枡ID', align: 'right' },
+    { source: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { source: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { source: 'trackCode', label: '鐗╂枡璺熻釜鐮�' },
+    { source: 'unit', label: '鍗曚綅' },
+    { source: 'anfme', label: '鏁伴噺', align: 'right' },
+    { source: 'batch', label: '搴撳瓨鎵规' },
+    { source: 'splrBatch', label: '渚涘簲鍟嗘壒娆�' },
+    { source: 'spec', label: '瑙勬牸' },
+    { source: 'model', label: '鍨嬪彿' },
+    { source: 'fieldsIndex', label: '瀛楁绱㈠紩' },
+    { source: 'statusText', label: '鐘舵��' },
+    { source: 'createTimeText', label: '鍒涘缓鏃堕棿' },
+    { source: 'updateTimeText', label: '鏇存柊鏃堕棿' }
+  ]
+}
+
+export function buildLocDeadReportPrintRows(records = []) {
+  return records.map((record) => {
+    const row = normalizeLocDeadReportRow(record)
+    return {
+      locCode: row.locCode,
+      deadTime: row.deadTime,
+      locId: row.locId,
+      orderId: row.orderId,
+      typeText: row.typeText,
+      orderItemId: row.orderItemId,
+      wkTypeText: row.wkTypeText,
+      matnrId: row.matnrId,
+      matnrCode: row.matnrCode,
+      maktx: row.maktx,
+      trackCode: row.trackCode,
+      unit: row.unit,
+      anfme: row.anfme,
+      batch: row.batch,
+      splrBatch: row.splrBatch,
+      spec: row.spec,
+      model: row.model,
+      fieldsIndex: row.fieldsIndex,
+      statusText: row.statusText,
+      createTimeText: row.createTimeText,
+      updateTimeText: row.updateTimeText
+    }
+  })
+}
diff --git a/rsf-design/src/views/manager/loc-dead-report/locDeadReportTable.columns.js b/rsf-design/src/views/manager/loc-dead-report/locDeadReportTable.columns.js
new file mode 100644
index 0000000..da7484a
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-dead-report/locDeadReportTable.columns.js
@@ -0,0 +1,150 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createLocDeadReportTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'deadTime',
+      label: '鍋滅暀鏃堕棿锛堝ぉ锛�',
+      width: 118,
+      align: 'right',
+      formatter: (row) => row?.deadTime ?? '--'
+    },
+    {
+      prop: 'locId',
+      label: '搴撲綅ID',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'typeText',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'batch',
+      label: '搴撳瓨鎵规',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '瀛楁绱㈠紩',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/manager/loc-dead-report/modules/loc-dead-report-detail-drawer.vue b/rsf-design/src/views/manager/loc-dead-report/modules/loc-dead-report-detail-drawer.vue
new file mode 100644
index 0000000..1d55d55
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-dead-report/modules/loc-dead-report-detail-drawer.vue
@@ -0,0 +1,82 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨鍋滄粸璇︽儏"
+    size="80%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2" v-loading="loading">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍋滅暀鏃堕棿锛堝ぉ锛�">{{ detail.deadTime ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅ID">{{ detail.locId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁ID">{{ detail.orderId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁㈠崟鏄庣粏ID">{{ detail.orderItemId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡ID">{{ detail.matnrId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璺熻釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀛楁绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <div v-if="extendFieldEntries.length > 0" class="flex min-h-0 flex-col gap-2">
+          <div class="text-sm font-medium text-[var(--art-gray-700)]">鎵╁睍瀛楁</div>
+          <ElDescriptions :column="4" border>
+            <ElDescriptionsItem
+              v-for="entry in extendFieldEntries"
+              :key="entry[0]"
+              :label="entry[0]"
+            >
+              {{ entry[1] || '--' }}
+            </ElDescriptionsItem>
+          </ElDescriptions>
+        </div>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'LocDeadReportDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const extendFieldEntries = computed(() => {
+    const extendFields = props.detail?.extendFields
+    if (!extendFields || typeof extendFields !== 'object') {
+      return []
+    }
+    return Object.entries(extendFields)
+  })
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/loc-item/index.vue b/rsf-design/src/views/manager/loc-item/index.vue
new file mode 100644
index 0000000..f6a40ed
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-item/index.vue
@@ -0,0 +1,234 @@
+<template>
+  <div class="loc-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      >
+        <template #action="{ row }">
+          <ArtButtonTable icon="ri:eye-line" @click="openDetail(row)" />
+        </template>
+      </ArtTable>
+    </ElCard>
+
+    <LocItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :detail="detailData"
+      :enabled-fields="detailFieldConfigs"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { useRoute } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { fetchEnabledFields, fetchLocItemDetail, fetchLocItemPage } from '@/api/loc-item'
+  import LocItemDetailDrawer from './modules/loc-item-detail-drawer.vue'
+  import { createLocItemTableColumns } from './locItemTable.columns'
+  import {
+    buildLocItemPageQueryParams,
+    buildLocItemSearchParams,
+    createLocItemSearchState,
+    getLocItemDynamicFieldKey,
+    getLocItemPaginationKey,
+    getLocItemStatusOptions,
+    normalizeLocItemEnabledFields,
+    normalizeLocItemRow
+  } from './locItemPage.helpers'
+
+  defineOptions({ name: 'LocItem' })
+
+  const route = useRoute()
+  const searchForm = ref(createLocItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const enabledFields = ref([])
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�/鐗╂枡缂栫爜/杩借釜鐮�' }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    },
+    {
+      label: '搴撲綅ID',
+      key: 'locId',
+      type: 'inputNumber',
+      props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ簱浣岻D' }
+    },
+    {
+      label: '鍗曟嵁ID',
+      key: 'orderId',
+      type: 'inputNumber',
+      props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ崟鎹甀D' }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'type',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�' }
+    },
+    {
+      label: '宸ヤ綅绫诲瀷',
+      key: 'wkType',
+      type: 'inputNumber',
+      props: { min: 0, controlsPosition: 'right', placeholder: '璇疯緭鍏ュ伐浣嶇被鍨�' }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' }
+    },
+    {
+      label: '杩借釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ拷韪爜' }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规' }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: { clearable: true, options: getLocItemStatusOptions() }
+    }
+  ])
+
+  const buildFieldConfigs = () =>
+    enabledFields.value.map((field) => ({
+      prop: getLocItemDynamicFieldKey(field.fields),
+      label: field.fieldsAlise
+    }))
+
+  const detailFieldConfigs = computed(() => buildFieldConfigs())
+
+  const { columns, columnChecks, resetColumns } = useTableColumns(() =>
+    createLocItemTableColumns({
+      enabledFields: buildFieldConfigs(),
+      handleView: openDetail
+    })
+  )
+
+  const {
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: (params) =>
+        guardRequestWithMessage(
+          fetchLocItemPage(params),
+          {
+            records: [],
+            total: 0,
+            current: params.current || 1,
+            pageSize: params.pageSize || params.size || 20
+          },
+          { timeoutMessage: '搴撳瓨鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+        ),
+      apiParams: buildLocItemPageQueryParams(searchForm.value),
+      paginationKey: getLocItemPaginationKey()
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeLocItemRow(item, enabledFields.value)) : []
+    }
+  })
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    try {
+      detailData.value = normalizeLocItemRow(await fetchLocItemDetail(row.id), enabledFields.value)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳瓨鏄庣粏璇︽儏澶辫触')
+    }
+  }
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeLocItemEnabledFields(fields)
+    resetColumns()
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildLocItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    const nextState = createLocItemSearchState()
+    if (route.query.locId) {
+      nextState.locId = route.query.locId
+    }
+    Object.assign(searchForm.value, nextState)
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    if (route.query.locId) {
+      searchForm.value.locId = route.query.locId
+      replaceSearchParams(buildLocItemSearchParams(searchForm.value))
+    }
+    await loadEnabledFieldDefinitions()
+    await getData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/loc-item/locItemPage.helpers.js b/rsf-design/src/views/manager/loc-item/locItemPage.helpers.js
new file mode 100644
index 0000000..0bc729d
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-item/locItemPage.helpers.js
@@ -0,0 +1,169 @@
+export const LOC_ITEM_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createLocItemSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    locId: '',
+    orderId: '',
+    type: '',
+    orderItemId: '',
+    wkType: '',
+    matnrId: '',
+    maktx: '',
+    matnrCode: '',
+    trackCode: '',
+    unit: '',
+    anfme: '',
+    batch: '',
+    splrBatch: '',
+    spec: '',
+    model: '',
+    fieldsIndex: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getLocItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildLocItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'type',
+    'maktx',
+    'matnrCode',
+    'trackCode',
+    'unit',
+    'batch',
+    'splrBatch',
+    'spec',
+    'model',
+    'fieldsIndex',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['locId', 'orderId', 'orderItemId', 'wkType', 'matnrId', 'anfme', 'status'].forEach((key) => {
+    if (params[key] === '' || params[key] === null || params[key] === undefined) {
+      return
+    }
+    const numericValue = Number(params[key])
+    if (Number.isFinite(numericValue)) {
+      result[key] = numericValue
+    }
+  })
+
+  if (params.timeStart) {
+    result.timeStart = params.timeStart
+  }
+  if (params.timeEnd) {
+    result.timeEnd = params.timeEnd
+  }
+
+  return result
+}
+
+export function buildLocItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocItemSearchParams(params)
+  }
+}
+
+export function getLocItemStatusOptions() {
+  return [
+    { label: '鍚敤', value: 1 },
+    { label: '鍋滅敤', value: 0 }
+  ]
+}
+
+export function getLocItemDynamicFieldKey(fieldName) {
+  return `${LOC_ITEM_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeLocItemEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function attachLocItemDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+
+  enabledFields.forEach((field) => {
+    dynamicValues[getLocItemDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+
+  return {
+    ...record,
+    ...dynamicValues
+  }
+}
+
+export function normalizeLocItemRow(record = {}, enabledFields = []) {
+  return attachLocItemDynamicFields(
+    {
+      ...record,
+      locId: record.locId ?? '--',
+      locCode: record.locCode || '--',
+      wareArea: record.wareArea || '--',
+      orderId: record.orderId ?? '--',
+      orderItemId: record.orderItemId ?? '--',
+      matnrId: record.matnrId ?? '--',
+      typeText: record['type$'] || record.type || '--',
+      wkTypeText: record['wkType$'] || record.wkType || '--',
+      matnrCode: record.matnrCode || '--',
+      maktx: record.maktx || '--',
+      spec: record.spec || '--',
+      model: record.model || '--',
+      batch: record.batch || '--',
+      splrBatch: record.splrBatch || '--',
+      trackCode: record.trackCode || '--',
+      unit: record.unit || '--',
+      anfme: normalizeNumber(record.anfme),
+      qty: normalizeNumber(record.qty),
+      workQty: normalizeNumber(record.workQty),
+      fieldsIndex: record.fieldsIndex || '--',
+      memo: record.memo || '--',
+      statusText: record.statusBool || Number(record.status) === 1 ? '鍚敤' : '鍋滅敤',
+      createByText: record['createBy$'] || record.createBy || '--',
+      createTimeText: record['createTime$'] || record.createTime || '--',
+      updateByText: record['updateBy$'] || record.updateBy || '--',
+      updateTimeText: record['updateTime$'] || record.updateTime || '--'
+    },
+    enabledFields
+  )
+}
diff --git a/rsf-design/src/views/manager/loc-item/locItemTable.columns.js b/rsf-design/src/views/manager/loc-item/locItemTable.columns.js
new file mode 100644
index 0000000..4a73ded
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-item/locItemTable.columns.js
@@ -0,0 +1,143 @@
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createLocItemTableColumns({ enabledFields = [], handleView }) {
+  return [
+    {
+      prop: 'locId',
+      label: '搴撲綅ID',
+      width: 110
+    },
+    {
+      prop: 'wareArea',
+      label: '搴撳尯',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeText',
+      label: '宸ヤ綅绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orderId',
+      label: '鍗曟嵁ID',
+      minWidth: 120
+    },
+    {
+      prop: 'orderItemId',
+      label: '鍗曟嵁鏄庣粏ID',
+      minWidth: 130
+    },
+    {
+      prop: 'matnrId',
+      label: '鐗╂枡ID',
+      minWidth: 110
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'trackCode',
+      label: '杩借釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '鍙敤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '搴撳瓨鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    ...enabledFields.map((field) => ({
+      prop: field.prop,
+      label: field.label,
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row[field.prop] || '-'
+    })),
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 90
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'action',
+      label: '璇︽儏',
+      width: 96,
+      fixed: 'right',
+      align: 'center',
+      useSlot: true
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/manager/loc-item/modules/loc-item-detail-drawer.vue b/rsf-design/src/views/manager/loc-item/modules/loc-item-detail-drawer.vue
new file mode 100644
index 0000000..e8a75a7
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-item/modules/loc-item-detail-drawer.vue
@@ -0,0 +1,71 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨鏄庣粏璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="搴撲綅ID">{{ detail.locId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撳尯">{{ detail.wareArea || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="宸ヤ綅绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曟嵁ID">{{ detail.orderId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="杩借釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍙敤鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撳瓨鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵ц涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <ElCard shadow="never">
+        <template #header>
+          <div class="text-sm font-medium">鎵╁睍瀛楁</div>
+        </template>
+        <div v-if="dynamicFields.length" class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
+          <div
+            v-for="field in dynamicFields"
+            :key="field.prop"
+            class="rounded-lg border border-[var(--el-border-color-light)] px-3 py-2"
+          >
+            <div class="text-xs text-[var(--art-gray-500)]">{{ field.label }}</div>
+            <div class="mt-1 text-sm text-[var(--art-text-gray-900)]">{{ field.value || '--' }}</div>
+          </div>
+        </div>
+        <ElEmpty v-else description="鏆傛棤鎵╁睍瀛楁" :image-size="84" />
+      </ElCard>
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    enabledFields: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const dynamicFields = computed(() =>
+    props.enabledFields.map((field) => ({
+      ...field,
+      value: props.detail[field.prop]
+    }))
+  )
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/loc-preview/index.vue b/rsf-design/src/views/manager/loc-preview/index.vue
new file mode 100644
index 0000000..c3a0bf6
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-preview/index.vue
@@ -0,0 +1,333 @@
+<template>
+  <div class="loc-preview-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      >
+        <template #action="{ row }">
+          <ArtButtonTable icon="ri:eye-line" @click="openDetailDrawer(row)" />
+        </template>
+      </ArtTable>
+    </ElCard>
+
+    <LocPreviewDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="activeLocDetail"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchEnabledFields,
+    fetchLocPreviewDetail,
+    fetchLocPreviewItemsPage,
+    fetchLocPreviewPage
+  } from '@/api/loc-preview'
+  import LocPreviewDetailDrawer from './modules/loc-preview-detail-drawer.vue'
+  import { createLocPreviewTableColumns } from './locPreviewTable.columns'
+  import {
+    buildLocPreviewPageQueryParams,
+    createLocPreviewSearchState,
+    getLocPreviewDynamicFieldKey,
+    normalizeLocPreviewDetail,
+    normalizeLocPreviewEnabledFields,
+    normalizeLocPreviewItemRow,
+    normalizeLocPreviewRow
+  } from './locPreviewPage.helpers'
+
+  defineOptions({ name: 'LocPreview' })
+
+  const loading = ref(false)
+  const detailLoading = ref(false)
+  const tableData = ref([])
+  const detailTableData = ref([])
+  const detailDrawerVisible = ref(false)
+  const activeLocRow = ref(null)
+  const activeLocDetail = ref({})
+  const enabledFields = ref([])
+  const searchForm = ref(createLocPreviewSearchState())
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�/鏉$爜'
+      }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�'
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮�'
+      }
+    }
+  ])
+
+  function createDetailColumns() {
+    return [
+      {
+        prop: 'locCode',
+        label: '搴撲綅缂栫爜',
+        minWidth: 140,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'wareArea',
+        label: '搴撳尯',
+        minWidth: 140,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'orderCode',
+        label: '鍗曟嵁缂栧彿',
+        minWidth: 180,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'matnrCode',
+        label: '鐗╂枡缂栫爜',
+        minWidth: 160,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'maktx',
+        label: '鐗╂枡鍚嶇О',
+        minWidth: 220,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'batch',
+        label: '鎵规',
+        minWidth: 140,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'trackCode',
+        label: '杩借釜鐮�',
+        minWidth: 150,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'unit',
+        label: '鍗曚綅',
+        width: 100
+      },
+      {
+        prop: 'anfme',
+        label: '鍙敤鏁伴噺',
+        width: 120
+      },
+      {
+        prop: 'qty',
+        label: '搴撳瓨鏁伴噺',
+        width: 120
+      },
+      {
+        prop: 'workQty',
+        label: '鎵ц涓暟閲�',
+        width: 120
+      },
+      ...enabledFields.value.map((field) => ({
+        prop: getLocPreviewDynamicFieldKey(field.fields),
+        label: field.fieldsAlise,
+        minWidth: 140,
+        showOverflowTooltip: true,
+        formatter: (row) => row[getLocPreviewDynamicFieldKey(field.fields)] || '-'
+      })),
+      {
+        prop: 'createTimeText',
+        label: '鍒涘缓鏃堕棿',
+        minWidth: 180,
+        showOverflowTooltip: true
+      }
+    ]
+  }
+
+  const detailColumns = computed(() => createDetailColumns())
+
+  const { columns, columnChecks } = useTableColumns(() =>
+    createLocPreviewTableColumns({
+      handleViewDetail: openDetailDrawer
+    })
+  )
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeLocPreviewEnabledFields(fields)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchLocPreviewPage(
+          buildLocPreviewPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '搴撲綅鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeLocPreviewRow(record))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadDetailResources() {
+    if (!activeLocRow.value?.id) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchLocPreviewDetail(activeLocRow.value.id), {}, {
+          timeoutMessage: '搴撲綅璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }),
+        guardRequestWithMessage(
+          fetchLocPreviewItemsPage({
+            current: detailPagination.current,
+            pageSize: detailPagination.size,
+            locId: activeLocRow.value.id
+          }),
+          {
+            records: [],
+            total: 0,
+            current: detailPagination.current,
+            size: detailPagination.size
+          },
+          { timeoutMessage: '搴撲綅搴撳瓨鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+        )
+      ])
+
+      activeLocDetail.value = normalizeLocPreviewDetail(detailResponse)
+      detailTableData.value = Array.isArray(itemResponse?.records)
+        ? itemResponse.records.map((record) => normalizeLocPreviewItemRow(record, enabledFields.value))
+        : []
+      updatePaginationState(detailPagination, itemResponse, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function openDetailDrawer(row) {
+    activeLocRow.value = row
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    loadDetailResources()
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createLocPreviewSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  onMounted(async () => {
+    await loadEnabledFieldDefinitions()
+    await loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/loc-preview/locPreviewPage.helpers.js b/rsf-design/src/views/manager/loc-preview/locPreviewPage.helpers.js
new file mode 100644
index 0000000..43e6b35
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-preview/locPreviewPage.helpers.js
@@ -0,0 +1,127 @@
+export const LOC_PREVIEW_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createLocPreviewSearchState() {
+  return {
+    condition: '',
+    code: '',
+    barcode: ''
+  }
+}
+
+export function buildLocPreviewPageQueryParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  const condition = normalizeText(params.condition)
+  const code = normalizeText(params.code)
+  const barcode = normalizeText(params.barcode)
+
+  if (condition) {
+    result.condition = condition
+  }
+  if (code) {
+    result.code = code
+  }
+  if (barcode) {
+    result.barcode = barcode
+  }
+
+  return result
+}
+
+export function getLocPreviewDynamicFieldKey(fieldName) {
+  return `${LOC_PREVIEW_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeLocPreviewEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function attachLocPreviewDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+
+  enabledFields.forEach((field) => {
+    dynamicValues[getLocPreviewDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+
+  return {
+    ...record,
+    ...dynamicValues
+  }
+}
+
+export function normalizeLocPreviewRow(record = {}) {
+  return {
+    ...record,
+    warehouseLabel: record['warehouseId$'] || record.warehouseName || '-',
+    areaLabel: record['areaId$'] || record.areaName || '-',
+    locCode: record.code || record.locCode || '-',
+    typeLabel: record['typeIds$'] || record['type$'] || record.type || '-',
+    useStatusLabel: record['useStatus$'] || '-',
+    barcode: record.barcode || '-',
+    row: normalizeNumber(record.row),
+    col: normalizeNumber(record.col),
+    lev: normalizeNumber(record.lev),
+    channel: normalizeNumber(record.channel),
+    statusLabel: record.statusBool ? '鍚敤' : '鍋滅敤',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-'
+  }
+}
+
+export function normalizeLocPreviewDetail(record = {}) {
+  return {
+    ...record,
+    warehouseLabel: record['warehouseId$'] || record.warehouseName || '-',
+    areaLabel: record['areaId$'] || record.areaName || '-',
+    locCode: record.code || record.locCode || '-',
+    typeLabel: record['typeIds$'] || record['type$'] || record.type || '-',
+    useStatusLabel: record['useStatus$'] || '-',
+    barcode: record.barcode || '-',
+    memo: record.memo || '-'
+  }
+}
+
+export function normalizeLocPreviewItemRow(record = {}, enabledFields = []) {
+  return attachLocPreviewDynamicFields(
+    {
+      ...record,
+      locCode: record.locCode || '-',
+      wareArea: record.wareArea || '-',
+      orderCode: record.orderCode || '-',
+      matnrCode: record.matnrCode || '-',
+      maktx: record.maktx || '-',
+      batch: record.batch || '-',
+      trackCode: record.trackCode || '-',
+      unit: record.unit || '-',
+      anfme: normalizeNumber(record.anfme),
+      qty: normalizeNumber(record.qty),
+      workQty: normalizeNumber(record.workQty),
+      createTimeText: record['createTime$'] || record.createTime || '-'
+    },
+    enabledFields
+  )
+}
diff --git a/rsf-design/src/views/manager/loc-preview/locPreviewTable.columns.js b/rsf-design/src/views/manager/loc-preview/locPreviewTable.columns.js
new file mode 100644
index 0000000..7957a17
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-preview/locPreviewTable.columns.js
@@ -0,0 +1,77 @@
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createLocPreviewTableColumns({ handleViewDetail }) {
+  return [
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'warehouseLabel',
+      label: '浠撳簱',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'areaLabel',
+      label: '搴撳尯',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeLabel',
+      label: '搴撲綅绫诲瀷',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鏉$爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'useStatusLabel',
+      label: '浣跨敤鐘舵��',
+      width: 120
+    },
+    {
+      prop: 'row',
+      label: '鎺�',
+      width: 80
+    },
+    {
+      prop: 'col',
+      label: '鍒�',
+      width: 80
+    },
+    {
+      prop: 'lev',
+      label: '灞�',
+      width: 80
+    },
+    {
+      prop: 'channel',
+      label: '宸烽亾',
+      width: 90
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'action',
+      label: '璇︽儏',
+      width: 100,
+      fixed: 'right',
+      useSlot: true,
+      align: 'center'
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/manager/loc-preview/modules/loc-preview-detail-drawer.vue b/rsf-design/src/views/manager/loc-preview/modules/loc-preview-detail-drawer.vue
new file mode 100644
index 0000000..b9c3a65
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-preview/modules/loc-preview-detail-drawer.vue
@@ -0,0 +1,52 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撲綅璇︽儏"
+    size="85%"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="4" border>
+        <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="浠撳簱">{{ detail.warehouseLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撳尯">{{ detail.areaLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="浣跨敤鐘舵��">{{ detail.useStatusLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撲綅绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏉$爜">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎺�">{{ detail.row ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒�">{{ detail.col ?? '--' }}</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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="$emit('size-change', $event)"
+        @pagination:current-change="$emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/loc-revise/index.vue b/rsf-design/src/views/manager/loc-revise/index.vue
new file mode 100644
index 0000000..cea35c7
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-revise/index.vue
@@ -0,0 +1,542 @@
+<template>
+  <div class="loc-revise-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳瓨璋冩暣</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :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"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <LocReviseDialog
+        v-model:visible="dialogVisible"
+        :loc-revise-data="currentLocReviseData"
+        :area-options="areaOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <LocReviseDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :summary="detailData"
+        :log-loading="logLoading"
+        :log-data="logTableData"
+        :log-columns="logColumns"
+        :log-pagination="logPagination"
+        :item-loading="itemLoading"
+        :item-data="itemTableData"
+        :item-columns="itemColumns"
+        :item-pagination="itemPagination"
+        :active-log="activeLog"
+        @log-size-change="handleLogSizeChange"
+        @log-current-change="handleLogCurrentChange"
+        @item-size-change="handleItemSizeChange"
+        @item-current-change="handleItemCurrentChange"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, h, onMounted, reactive, ref } from 'vue'
+  import { ElButton, ElMessage, ElMessageBox, ElTag } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+  import {
+    fetchCompleteLocRevise,
+    fetchExportLocReviseReport,
+    fetchGetLocReviseDetail,
+    fetchGetLocReviseMany,
+    fetchLocRevisePage,
+    fetchReviseLogItemPage,
+    fetchReviseLogPage,
+    fetchSaveLocRevise,
+    fetchUpdateLocRevise,
+    fetchDeleteLocRevise,
+    fetchWarehouseAreasList
+  } from '@/api/loc-revise'
+  import LocReviseDialog from './modules/loc-revise-dialog.vue'
+  import LocReviseDetailDrawer from './modules/loc-revise-detail-drawer.vue'
+  import { createLocReviseTableColumns } from './locReviseTable.columns'
+  import {
+    buildLocReviseDialogModel,
+    buildLocRevisePageQueryParams,
+    buildLocRevisePrintRows,
+    buildLocReviseReportMeta,
+    buildLocReviseSavePayload,
+    buildLocReviseSearchParams,
+    buildReviseLogItemPageQueryParams,
+    buildReviseLogPageQueryParams,
+    createLocReviseFormState,
+    createLocReviseSearchState,
+    getLocReviseExceStatusOptions,
+    getLocReviseTypeOptions,
+    LOC_REVISE_REPORT_STYLE,
+    LOC_REVISE_REPORT_TITLE,
+    normalizeLocReviseRow,
+    normalizeReviseLogItemRow,
+    normalizeReviseLogRow,
+    resolveWarehouseAreaOptions
+  } from './locRevisePage.helpers'
+
+  defineOptions({ name: 'LocRevise' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+  const reportTitle = LOC_REVISE_REPORT_TITLE
+  const loading = ref(false)
+  const tableData = ref([])
+  const searchForm = ref(createLocReviseSearchState())
+  const areaOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const logLoading = ref(false)
+  const logTableData = ref([])
+  const itemLoading = ref(false)
+  const itemTableData = ref([])
+  const activeLog = ref({})
+  let handleDeleteAction = null
+
+  const pagination = reactive({ current: 1, size: 20, total: 0 })
+  const logPagination = reactive({ current: 1, size: 20, total: 0 })
+  const itemPagination = reactive({ current: 1, size: 20, total: 0 })
+  const reportQueryParams = computed(() => buildLocReviseSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鏁村崟鍙�' }
+    },
+    {
+      label: '璋冩暣鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鏁村崟鍙�' }
+    },
+    {
+      label: '璋冩暣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: { clearable: true, options: getLocReviseTypeOptions() }
+    },
+    {
+      label: '搴撳尯鍚嶇О',
+      key: 'areaName',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�' }
+    },
+    {
+      label: '鎵ц鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getLocReviseExceStatusOptions().map((item) => ({
+          label: item.label,
+          value: item.value
+        }))
+      }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'date',
+      props: { type: 'date', valueFormat: 'YYYY-MM-DD', placeholder: '璇烽�夋嫨寮�濮嬫椂闂�' }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'date',
+      props: { type: 'date', valueFormat: 'YYYY-MM-DD', placeholder: '璇烽�夋嫨缁撴潫鏃堕棿' }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    activeLog.value = {}
+    itemTableData.value = []
+    try {
+      detailData.value = normalizeLocReviseRow(await fetchGetLocReviseDetail(row.id))
+      logPagination.current = 1
+      await loadLogData(row.id)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳瓨璋冩暣璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentLocReviseData.value = buildLocReviseDialogModel(await fetchGetLocReviseDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇搴撳瓨璋冩暣璇︽儏澶辫触')
+    }
+  }
+
+  async function handleComplete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸畬鎴愬簱瀛樿皟鏁村崟銆�${row.code || row.id}銆嶅悧锛焋, '瀹屾垚纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchCompleteLocRevise(row.id)
+      await loadPageData()
+      if (detailDrawerVisible.value && Number(detailData.value?.id) === Number(row.id)) {
+        await openDetail(row)
+      }
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '瀹屾垚搴撳瓨璋冩暣澶辫触')
+      }
+    }
+  }
+
+  const { columns, columnChecks } = useTableColumns(() =>
+    createLocReviseTableColumns({
+      handleView: openDetail,
+      handleEdit: hasAuth('update') ? (row) => openEditDialog(row) : null,
+      handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+      handleComplete: hasAuth('update') ? (row) => handleComplete(row) : null
+    })
+  )
+
+  const logColumns = computed(() => [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'reviseCode', label: '璋冩暣鍗曞彿', minWidth: 170, showOverflowTooltip: true },
+    { prop: 'locCode', label: '搴撲綅缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'barcode', label: '搴撲綅鏉$爜', minWidth: 150, showOverflowTooltip: true },
+    { prop: 'typeLabel', label: '搴撲綅绫诲瀷', minWidth: 110 },
+    { prop: 'useStatusLabel', label: '鍗犵敤鐘舵��', minWidth: 110 },
+    { prop: 'updateTimeText', label: '鏇存柊鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          text: '鏌ョ湅鏄庣粏',
+          onClick: () => openLogItems(row)
+        })
+    }
+  ])
+
+  const itemColumns = computed(() => [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'locCode', label: '搴撲綅缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 150, showOverflowTooltip: true },
+    { prop: 'maktx', label: '鐗╂枡鍚嶇О', minWidth: 220, showOverflowTooltip: true },
+    { prop: 'unit', label: '鍗曚綅', width: 90 },
+    { prop: 'anfme', label: '鍘熷簱瀛�', width: 100, align: 'right' },
+    { prop: 'reviseQty', label: '璋冩暣鏁伴噺', width: 100, align: 'right' },
+    {
+      prop: 'diffQty',
+      label: '宸紓鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: Number(row.diffQty) === 0 ? 'info' : Number(row.diffQty) > 0 ? 'success' : 'danger',
+            effect: 'light'
+          },
+          () => String(row.diffQty)
+        )
+    },
+    { prop: 'batch', label: '鎵规', minWidth: 130, showOverflowTooltip: true },
+    { prop: 'spec', label: '瑙勬牸', minWidth: 130, showOverflowTooltip: true },
+    { prop: 'model', label: '鍨嬪彿', minWidth: 130, showOverflowTooltip: true }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadAreaOptions() {
+    const records = await guardRequestWithMessage(fetchWarehouseAreasList(), [], {
+      timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    areaOptions.value = resolveWarehouseAreaOptions(records)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchLocRevisePage(
+          buildLocRevisePageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        { records: [], total: 0, current: pagination.current, size: pagination.size },
+        { timeoutMessage: '搴撳瓨璋冩暣鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeLocReviseRow(record))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadLogData(reviseId = detailData.value?.id) {
+    if (!reviseId) return
+    logLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchReviseLogPage(
+          buildReviseLogPageQueryParams({
+            reviseId,
+            current: logPagination.current,
+            pageSize: logPagination.size
+          })
+        ),
+        { records: [], total: 0, current: logPagination.current, size: logPagination.size },
+        { timeoutMessage: '璋冩暣鏃ュ織鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      logTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeReviseLogRow(record))
+        : []
+      updatePaginationState(logPagination, response, logPagination.current, logPagination.size)
+
+      if (logTableData.value.length > 0) {
+        const nextActiveLog = activeLog.value?.id
+          ? logTableData.value.find((item) => Number(item.id) === Number(activeLog.value.id))
+          : logTableData.value[0]
+        if (nextActiveLog) {
+          await openLogItems(nextActiveLog, { silent: true })
+        }
+      } else {
+        activeLog.value = {}
+        itemTableData.value = []
+        itemPagination.total = 0
+      }
+    } finally {
+      logLoading.value = false
+    }
+  }
+
+  async function openLogItems(row, options = {}) {
+    activeLog.value = row || {}
+    itemPagination.current = 1
+    await loadLogItemsData(options)
+  }
+
+  async function loadLogItemsData(options = {}) {
+    if (!activeLog.value?.id) {
+      itemTableData.value = []
+      return
+    }
+    itemLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchReviseLogItemPage(
+          buildReviseLogItemPageQueryParams({
+            reviseLogId: activeLog.value.id,
+            current: itemPagination.current,
+            pageSize: itemPagination.size
+          })
+        ),
+        { records: [], total: 0, current: itemPagination.current, size: itemPagination.size },
+        { timeoutMessage: options.silent ? '' : '鏃ュ織鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      itemTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeReviseLogItemRow(record))
+        : []
+      updatePaginationState(itemPagination, response, itemPagination.current, itemPagination.size)
+    } finally {
+      itemLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createLocReviseSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleLogSizeChange(size) {
+    logPagination.size = size
+    logPagination.current = 1
+    loadLogData()
+  }
+
+  function handleLogCurrentChange(current) {
+    logPagination.current = current
+    loadLogData()
+  }
+
+  function handleItemSizeChange(size) {
+    itemPagination.size = size
+    itemPagination.current = 1
+    loadLogItemsData()
+  }
+
+  function handleItemCurrentChange(current) {
+    itemPagination.current = current
+    loadLogItemsData()
+  }
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentLocReviseData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => createLocReviseFormState(),
+    buildEditModel: (record) => buildLocReviseDialogModel(record),
+    buildSavePayload: (formData) => buildLocReviseSavePayload(formData),
+    saveRequest: fetchSaveLocRevise,
+    updateRequest: fetchUpdateLocRevise,
+    deleteRequest: fetchDeleteLocRevise,
+    entityName: '搴撳瓨璋冩暣',
+    resolveRecordLabel: (record) => record?.code || record?.id,
+    refreshCreate: loadPageData,
+    refreshUpdate: loadPageData,
+    refreshRemove: loadPageData
+  })
+  handleDeleteAction = handleDelete
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'loc-revise.xlsx',
+    requestExport: (payload) =>
+      fetchExportLocReviseReport(payload, {
+        headers: { Authorization: userStore.accessToken || '' }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetLocReviseMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchLocRevisePage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildLocRevisePrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: { ...LOC_REVISE_REPORT_STYLE }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildLocReviseReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || LOC_REVISE_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(async () => {
+    await loadAreaOptions()
+    await loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/loc-revise/locRevisePage.helpers.js b/rsf-design/src/views/manager/loc-revise/locRevisePage.helpers.js
new file mode 100644
index 0000000..952cf2b
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-revise/locRevisePage.helpers.js
@@ -0,0 +1,233 @@
+export const LOC_REVISE_REPORT_TITLE = '搴撳瓨璋冩暣鎶ヨ〃'
+export const LOC_REVISE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const TYPE_OPTIONS = [
+  { label: '搴撳瓨璋冩暣', value: 0 },
+  { label: '鐩樼偣璋冩暣', value: 1 },
+  { label: '鍏跺畠璋冩暣', value: 2 }
+]
+
+const EXCE_STATUS_OPTIONS = [
+  { label: '鏈墽琛�', value: 0, tagType: 'info' },
+  { label: '鎵ц涓�', value: 1, tagType: 'warning' },
+  { label: '鎵ц瀹屾垚', value: 2, tagType: 'success' }
+]
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function getLocReviseTypeOptions() {
+  return TYPE_OPTIONS
+}
+
+export function getLocReviseExceStatusOptions() {
+  return EXCE_STATUS_OPTIONS
+}
+
+export function createLocReviseSearchState() {
+  return {
+    condition: '',
+    code: '',
+    type: '',
+    areaName: '',
+    exceStatus: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createLocReviseFormState() {
+  return {
+    id: null,
+    code: '',
+    type: 0,
+    areaId: '',
+    exceTime: '',
+    memo: '',
+    status: 1
+  }
+}
+
+export function buildLocReviseDialogModel(record = {}) {
+  const model = createLocReviseFormState()
+  return {
+    ...model,
+    ...record,
+    id: record.id ?? null,
+    code: record.code || '',
+    type: record.type ?? 0,
+    areaId: record.areaId ?? '',
+    exceTime: record.exceTime || '',
+    memo: record.memo || '',
+    status: record.status ?? 1
+  }
+}
+
+export function buildLocReviseSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'areaName', 'timeStart', 'timeEnd'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.type !== '' && params.type !== null && params.type !== undefined) {
+    result.type = normalizeNumber(params.type)
+  }
+  if (params.exceStatus !== '' && params.exceStatus !== null && params.exceStatus !== undefined) {
+    result.exceStatus = normalizeNumber(params.exceStatus)
+  }
+
+  return result
+}
+
+export function buildLocRevisePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildLocReviseSearchParams(params)
+  }
+}
+
+export function buildReviseLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.reviseId !== undefined ? { reviseId: params.reviseId } : {})
+  }
+}
+
+export function buildReviseLogItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.reviseLogId !== undefined ? { reviseLogId: params.reviseLogId } : {})
+  }
+}
+
+export function buildLocReviseSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: formData.id } : {}),
+    ...(formData.code ? { code: formData.code } : {}),
+    type: normalizeNumber(formData.type),
+    areaId: normalizeNumber(formData.areaId, null),
+    ...(normalizeText(formData.exceTime) ? { exceTime: normalizeText(formData.exceTime) } : {}),
+    ...(normalizeText(formData.memo) ? { memo: normalizeText(formData.memo) } : {}),
+    status: formData.status === 0 ? 0 : 1
+  }
+}
+
+export function resolveWarehouseAreaOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => ({
+    label: record.name || record.code || `搴撳尯${record.id}`,
+    value: record.id
+  }))
+}
+
+function resolveOptionLabel(options, value, fallback = '-') {
+  const target = options.find((item) => Number(item.value) === Number(value))
+  return target?.label || fallback
+}
+
+export function getLocReviseExceStatusMeta(value) {
+  const target = EXCE_STATUS_OPTIONS.find((item) => Number(item.value) === Number(value))
+  return target || { label: '-', value, tagType: 'info' }
+}
+
+export function normalizeLocReviseRow(record = {}) {
+  const exceStatusMeta = getLocReviseExceStatusMeta(record.exceStatus)
+  return {
+    ...record,
+    code: record.code || '-',
+    typeLabel: record['type$'] || resolveOptionLabel(TYPE_OPTIONS, record.type),
+    areaLabel: record.areaName || record['areaId$'] || '-',
+    anfme: normalizeNumber(record.anfme),
+    reviseQty: normalizeNumber(record.reviseQty),
+    exceStatusText: record['exceStatus$'] || exceStatusMeta.label,
+    exceStatusTagType: exceStatusMeta.tagType,
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    exceTimeText: record['exceTime$'] || record.exceTime || '-',
+    memo: record.memo || '-'
+  }
+}
+
+export function normalizeReviseLogRow(record = {}) {
+  return {
+    ...record,
+    reviseCode: record.reviseCode || '-',
+    locCode: record.locCode || record.barcode || '-',
+    barcode: record.barcode || '-',
+    typeLabel: record['type$'] || '-',
+    useStatusLabel: record['useStatus$'] || record.useStatus || '-',
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function normalizeReviseLogItemRow(record = {}) {
+  const diffQty = normalizeNumber(
+    record.diffQty,
+    normalizeNumber(record.reviseQty) - normalizeNumber(record.anfme)
+  )
+  return {
+    ...record,
+    locCode: record.locCode || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    unit: record.unit || '-',
+    anfme: normalizeNumber(record.anfme),
+    reviseQty: normalizeNumber(record.reviseQty),
+    diffQty,
+    batch: record.batch || '-',
+    spec: record.spec || '-',
+    model: record.model || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function buildLocRevisePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeLocReviseRow(record))
+}
+
+export function buildLocReviseReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = LOC_REVISE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: LOC_REVISE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...LOC_REVISE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/manager/loc-revise/locReviseTable.columns.js b/rsf-design/src/views/manager/loc-revise/locReviseTable.columns.js
new file mode 100644
index 0000000..3b4f501
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-revise/locReviseTable.columns.js
@@ -0,0 +1,114 @@
+import { h } from 'vue'
+import { ElButton, ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createLocReviseTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  handleComplete
+}) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: '璋冩暣鍗曞彿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeLabel',
+      label: '璋冩暣绫诲瀷',
+      minWidth: 120
+    },
+    {
+      prop: 'areaLabel',
+      label: '搴撳尯鍚嶇О',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '鍘熷簱瀛�',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'reviseQty',
+      label: '璋冩暣鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      width: 110,
+      formatter: (row) =>
+        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText)
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      minWidth: 220,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(
+          'div',
+          { class: 'flex justify-end gap-1' },
+          [
+            h(ArtButtonTable, {
+              type: 'view',
+              onClick: () => handleView(row)
+            }),
+            row.exceStatus < 2 && handleEdit
+              ? h(ArtButtonTable, {
+                  type: 'edit',
+                  onClick: () => handleEdit(row)
+                })
+              : null,
+            row.exceStatus === 0 && handleDelete
+              ? h(ArtButtonTable, {
+                  type: 'delete',
+                  onClick: () => handleDelete(row)
+                })
+              : null,
+            row.exceStatus === 1 && handleComplete
+              ? h(
+                  ElButton,
+                  {
+                    type: 'success',
+                    link: true,
+                    class: '!px-1',
+                    onClick: () => handleComplete(row)
+                  },
+                  () => '瀹屾垚'
+                )
+              : null
+          ].filter(Boolean)
+        )
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/loc-revise/modules/loc-revise-detail-drawer.vue b/rsf-design/src/views/manager/loc-revise/modules/loc-revise-detail-drawer.vue
new file mode 100644
index 0000000..75b6c6d
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-revise/modules/loc-revise-detail-drawer.vue
@@ -0,0 +1,92 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨璋冩暣璇︽儏"
+    size="78%"
+    destroy-on-close
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElSkeleton :loading="loading" animated :rows="8">
+        <ElDescriptions :column="3" border>
+          <ElDescriptionsItem label="璋冩暣鍗曞彿">{{ summary.code || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璋冩暣绫诲瀷">{{ summary.typeLabel || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯鍚嶇О">{{ summary.areaLabel || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍘熷簱瀛�">{{ summary.anfme ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璋冩暣鏁伴噺">{{ summary.reviseQty ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">
+            <ElTag :type="summary.exceStatusTagType || 'info'" effect="light">
+              {{ summary.exceStatusText || '-' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鏃堕棿">{{ summary.exceTimeText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ summary.updateTimeText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞">{{ summary.memo || '-' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </ElSkeleton>
+
+      <div class="flex min-h-0 flex-1 flex-col gap-4">
+        <section class="flex min-h-0 flex-[1.1] flex-col gap-3">
+          <div class="flex items-center justify-between">
+            <h3 class="text-base font-semibold text-g-900">璋冩暣鏃ュ織</h3>
+            <span class="text-sm text-g-500">鍏� {{ logPagination.total || 0 }} 鏉�</span>
+          </div>
+          <ArtTable
+            :loading="logLoading"
+            :data="logData"
+            :columns="logColumns"
+            :pagination="logPagination"
+            @pagination:size-change="emit('log-size-change', $event)"
+            @pagination:current-change="emit('log-current-change', $event)"
+          />
+        </section>
+
+        <section class="flex min-h-0 flex-1 flex-col gap-3">
+          <div class="flex items-center justify-between">
+            <h3 class="text-base font-semibold text-g-900">鏃ュ織鏄庣粏</h3>
+            <span class="text-sm text-g-500">{{ activeLogLabel }}</span>
+          </div>
+          <ArtTable
+            :loading="itemLoading"
+            :data="itemData"
+            :columns="itemColumns"
+            :pagination="itemPagination"
+            @pagination:size-change="emit('item-size-change', $event)"
+            @pagination:current-change="emit('item-current-change', $event)"
+          />
+        </section>
+      </div>
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'LocReviseDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    logLoading: { type: Boolean, default: false },
+    logData: { type: Array, default: () => [] },
+    logColumns: { type: Array, default: () => [] },
+    logPagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) },
+    itemLoading: { type: Boolean, default: false },
+    itemData: { type: Array, default: () => [] },
+    itemColumns: { type: Array, default: () => [] },
+    itemPagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) },
+    activeLog: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits([
+    'update:visible',
+    'log-size-change',
+    'log-current-change',
+    'item-size-change',
+    'item-current-change'
+  ])
+
+  const activeLogLabel = computed(() =>
+    props.activeLog?.locCode ? `褰撳墠搴撲綅锛�${props.activeLog.locCode}` : '璇烽�夋嫨涓�鏉¤皟鏁存棩蹇�'
+  )
+</script>
diff --git a/rsf-design/src/views/manager/loc-revise/modules/loc-revise-dialog.vue b/rsf-design/src/views/manager/loc-revise/modules/loc-revise-dialog.vue
new file mode 100644
index 0000000..ca96962
--- /dev/null
+++ b/rsf-design/src/views/manager/loc-revise/modules/loc-revise-dialog.vue
@@ -0,0 +1,168 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="760px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildLocReviseDialogModel,
+    createLocReviseFormState,
+    getLocReviseTypeOptions
+  } from '../locRevisePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    locReviseData: { type: Object, default: () => ({}) },
+    areaOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createLocReviseFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳瓨璋冩暣' : '鏂板搴撳瓨璋冩暣'))
+
+  const rules = computed(() => ({
+    type: [{ required: true, message: '璇烽�夋嫨璋冩暣绫诲瀷', trigger: 'change' }],
+    areaId: [{ required: true, message: '璇烽�夋嫨搴撳尯', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '璋冩暣鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        disabled: true,
+        placeholder: '鏂板鍚庤嚜鍔ㄧ敓鎴�'
+      }
+    },
+    {
+      label: '璋冩暣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        options: getLocReviseTypeOptions(),
+        placeholder: '璇烽�夋嫨璋冩暣绫诲瀷'
+      }
+    },
+    {
+      label: '搴撳尯',
+      key: 'areaId',
+      type: 'select',
+      props: {
+        options: props.areaOptions,
+        placeholder: '璇烽�夋嫨搴撳尯',
+        filterable: true
+      }
+    },
+    {
+      label: '鎵ц鏃堕棿',
+      key: 'exceTime',
+      type: 'date',
+      props: {
+        type: 'datetime',
+        placeholder: '璇烽�夋嫨鎵ц鏃堕棿',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 4,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createLocReviseFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildLocReviseDialogModel(props.locReviseData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.locReviseData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/manager/menu-pda/index.vue b/rsf-design/src/views/manager/menu-pda/index.vue
new file mode 100644
index 0000000..d40a08f
--- /dev/null
+++ b/rsf-design/src/views/manager/menu-pda/index.vue
@@ -0,0 +1,222 @@
+<template>
+  <div class="menu-pda-page art-full-height">
+    <ArtSearchBar
+      v-model="formFilters"
+      :items="formItems"
+      :showExpand="false"
+      @reset="handleReset"
+      @search="handleSearch"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader
+        :showZebra="false"
+        :loading="loading"
+        v-model:columns="columnChecks"
+        @refresh="handleRefresh"
+      >
+        <template #left>
+          <ElButton v-auth="'add'" @click="handleAddMenu" v-ripple>娣诲姞PDA鑿滃崟</ElButton>
+          <ElButton @click="toggleExpand" v-ripple>
+            {{ isExpanded ? '鏀惰捣' : '灞曞紑' }}
+          </ElButton>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        ref="tableRef"
+        rowKey="id"
+        :loading="loading"
+        :columns="columns"
+        :data="filteredTableData"
+        :stripe="false"
+        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+        :default-expand-all="false"
+      />
+
+      <MenuPdaDialog
+        v-model:visible="dialogVisible"
+        :editData="editData"
+        :lockType="lockMenuType"
+        :menuTreeOptions="menuTreeOptions"
+        @submit="handleSubmit"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import {
+    fetchDeleteMenuPda,
+    fetchGetMenuPdaTree,
+    fetchSaveMenuPda,
+    fetchUpdateMenuPda
+  } from '@/api/system-manage'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import MenuPdaDialog from './modules/menu-pda-dialog.vue'
+  import { createMenuPdaTableColumns } from './menuPdaTable.columns'
+  import {
+    buildMenuPdaSubmitPayload,
+    buildMenuPdaTreeOptions,
+    createMenuPdaSearchState,
+    filterMenuPdaTree,
+    getMenuPdaDisplayTitle
+  } from './menuPdaPage.helpers'
+
+  defineOptions({ name: 'MenuPda' })
+
+  const loading = ref(false)
+  const isExpanded = ref(false)
+  const tableRef = ref()
+  const dialogVisible = ref(false)
+  const editData = ref(null)
+  const lockMenuType = ref(true)
+  const tableData = ref([])
+  const menuTreeOptions = ref([])
+
+  const initialSearchState = createMenuPdaSearchState()
+  const formFilters = reactive({ ...initialSearchState })
+  const appliedFilters = reactive({ ...initialSearchState })
+
+  const formItems = computed(() => [
+    {
+      label: '鑿滃崟鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: { clearable: true }
+    },
+    {
+      label: '璺敱鍦板潃',
+      key: 'route',
+      type: 'input',
+      props: { clearable: true }
+    }
+  ])
+
+  const loadMenuPdaResources = async () => {
+    loading.value = true
+    try {
+      const list = await guardRequestWithMessage(fetchGetMenuPdaTree({ condition: appliedFilters.name || '' }), null, {
+        timeoutMessage: 'PDA鑿滃崟鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      if (list === null) {
+        tableData.value = []
+        menuTreeOptions.value = []
+        return
+      }
+      tableData.value = Array.isArray(list) ? list : []
+      menuTreeOptions.value = buildMenuPdaTreeOptions(tableData.value)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇PDA鑿滃崟澶辫触')
+    } finally {
+      loading.value = false
+    }
+  }
+
+  onMounted(() => {
+    loadMenuPdaResources()
+  })
+
+  const { columnChecks, columns } = useTableColumns(() =>
+    createMenuPdaTableColumns({
+      handleEditMenu,
+      handleDeleteMenu
+    })
+  )
+
+  const filteredTableData = computed(() => filterMenuPdaTree(tableData.value, appliedFilters))
+
+  function closeDialog() {
+    dialogVisible.value = false
+    editData.value = null
+  }
+
+  function handleAddMenu() {
+    editData.value = null
+    lockMenuType.value = false
+    dialogVisible.value = true
+  }
+
+  function handleEditMenu(row) {
+    editData.value = row
+    lockMenuType.value = true
+    dialogVisible.value = true
+  }
+
+  async function handleSubmit(formData) {
+    const payload = buildMenuPdaSubmitPayload(formData)
+    if (payload.id && payload.id === payload.parentId) {
+      ElMessage.error('涓婄骇鑿滃崟涓嶈兘閫夋嫨褰撳墠鑿滃崟')
+      return
+    }
+
+    try {
+      if (payload.id) {
+        await fetchUpdateMenuPda(payload)
+        ElMessage.success('淇敼鎴愬姛')
+      } else {
+        await fetchSaveMenuPda(payload)
+        ElMessage.success('鏂板鎴愬姛')
+      }
+      closeDialog()
+      await loadMenuPdaResources()
+    } catch (error) {
+      ElMessage.error(error?.message || '鎻愪氦澶辫触')
+    }
+  }
+
+  async function handleDeleteMenu(row) {
+    try {
+      await ElMessageBox.confirm(
+        `纭畾瑕佸垹闄DA鑿滃崟銆�${getMenuPdaDisplayTitle(row)}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`,
+        '鍒犻櫎纭',
+        {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        }
+      )
+      await fetchDeleteMenuPda(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await loadMenuPdaResources()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  function handleReset() {
+    Object.assign(formFilters, { ...initialSearchState })
+    Object.assign(appliedFilters, { ...initialSearchState })
+    loadMenuPdaResources()
+  }
+
+  function handleSearch() {
+    Object.assign(appliedFilters, { ...formFilters })
+    loadMenuPdaResources()
+  }
+
+  function handleRefresh() {
+    loadMenuPdaResources()
+  }
+
+  function toggleExpand() {
+    isExpanded.value = !isExpanded.value
+    nextTick(() => {
+      if (tableRef.value?.elTableRef && filteredTableData.value) {
+        const processRows = (rows) => {
+          rows.forEach((row) => {
+            if (row.children?.length) {
+              tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
+              processRows(row.children)
+            }
+          })
+        }
+        processRows(filteredTableData.value)
+      }
+    })
+  }
+</script>
diff --git a/rsf-design/src/views/manager/menu-pda/menuPdaPage.helpers.js b/rsf-design/src/views/manager/menu-pda/menuPdaPage.helpers.js
new file mode 100644
index 0000000..cc7786c
--- /dev/null
+++ b/rsf-design/src/views/manager/menu-pda/menuPdaPage.helpers.js
@@ -0,0 +1,124 @@
+export function createMenuPdaSearchState() {
+  return {
+    name: '',
+    route: ''
+  }
+}
+
+export function normalizeMenuPdaNumber(value, fallback = 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const normalized = Number(value)
+  return Number.isNaN(normalized) ? fallback : normalized
+}
+
+export function getMenuPdaDisplayTitle(row = {}) {
+  return String(row.name || '').trim()
+}
+
+export function getMenuPdaDisplayIcon(row = {}) {
+  return row.icon || ''
+}
+
+export function hasNestedMenuPda(row = {}) {
+  return Array.isArray(row.children) && row.children.some((child) => Number(child.type) !== 1)
+}
+
+export function getMenuPdaTypeTag(row = {}) {
+  if (Number(row.type) === 1) return 'danger'
+  if (hasNestedMenuPda(row)) return 'info'
+  return 'primary'
+}
+
+export function getMenuPdaTypeText(row = {}) {
+  if (Number(row.type) === 1) return '鎸夐挳'
+  if (hasNestedMenuPda(row)) return '鐩綍'
+  return '鑿滃崟'
+}
+
+export function getMenuPdaStatusMeta(status) {
+  return normalizeMenuPdaNumber(status, 1) === 1
+    ? { text: '鍚敤', type: 'success' }
+    : { text: '绂佺敤', type: 'danger' }
+}
+
+export function normalizeMenuPdaTreeOptions(nodes = []) {
+  if (!Array.isArray(nodes)) {
+    return []
+  }
+
+  return nodes.map((node) => ({
+    label: getMenuPdaDisplayTitle(node),
+    value: normalizeMenuPdaNumber(node.id, 0),
+    children: normalizeMenuPdaTreeOptions(node.children)
+  }))
+}
+
+export function buildMenuPdaTreeOptions(tree = []) {
+  return [
+    {
+      label: '椤剁骇鑿滃崟',
+      value: 0,
+      children: normalizeMenuPdaTreeOptions(tree)
+    }
+  ]
+}
+
+export function buildMenuPdaSubmitPayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: normalizeMenuPdaNumber(formData.id, 0) } : {}),
+    parentId: normalizeMenuPdaNumber(formData.parentId, 0),
+    name: String(formData.name || '').trim(),
+    route: String(formData.route || '').trim(),
+    component: String(formData.component || '').trim(),
+    authority: String(formData.authority || '').trim(),
+    icon: String(formData.icon || '').trim(),
+    sort: normalizeMenuPdaNumber(formData.sort, 0),
+    status: normalizeMenuPdaNumber(formData.status, 1),
+    memo: String(formData.memo || '').trim(),
+    type: formData.menuType === 'button' ? 1 : 0
+  }
+}
+
+export function cloneMenuPdaTree(source) {
+  if (source === null || typeof source !== 'object') return source
+  if (source instanceof Date) return new Date(source)
+  if (Array.isArray(source)) return source.map((item) => cloneMenuPdaTree(item))
+  const cloned = {}
+  for (const key in source) {
+    if (Object.prototype.hasOwnProperty.call(source, key)) {
+      cloned[key] = cloneMenuPdaTree(source[key])
+    }
+  }
+  return cloned
+}
+
+export function filterMenuPdaTree(items = [], filters = {}) {
+  const results = []
+  const searchName = String(filters.name || '').toLowerCase().trim()
+  const searchRoute = String(filters.route || '').toLowerCase().trim()
+
+  for (const item of items) {
+    const menuTitle = getMenuPdaDisplayTitle(item).toLowerCase()
+    const menuRoute = String(item.route || item.path || item.authority || '').toLowerCase()
+    const nameMatch = !searchName || menuTitle.includes(searchName)
+    const routeMatch = !searchRoute || menuRoute.includes(searchRoute)
+
+    if (item.children?.length) {
+      const matchedChildren = filterMenuPdaTree(item.children, filters)
+      if (matchedChildren.length > 0) {
+        const clonedItem = cloneMenuPdaTree(item)
+        clonedItem.children = matchedChildren
+        results.push(clonedItem)
+        continue
+      }
+    }
+
+    if (nameMatch && routeMatch) {
+      results.push(cloneMenuPdaTree(item))
+    }
+  }
+
+  return results
+}
diff --git a/rsf-design/src/views/manager/menu-pda/menuPdaTable.columns.js b/rsf-design/src/views/manager/menu-pda/menuPdaTable.columns.js
new file mode 100644
index 0000000..3f53904
--- /dev/null
+++ b/rsf-design/src/views/manager/menu-pda/menuPdaTable.columns.js
@@ -0,0 +1,97 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import {
+  getMenuPdaDisplayIcon,
+  getMenuPdaDisplayTitle,
+  getMenuPdaStatusMeta,
+  getMenuPdaTypeTag,
+  getMenuPdaTypeText
+} from './menuPdaPage.helpers'
+
+export function createMenuPdaTableColumns({ handleEditMenu, handleDeleteMenu }) {
+  return [
+    {
+      prop: 'name',
+      label: '鑿滃崟鍚嶇О',
+      minWidth: 180,
+      formatter: (row) => getMenuPdaDisplayTitle(row)
+    },
+    {
+      prop: 'icon',
+      label: '鍥炬爣棰勮',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const icon = getMenuPdaDisplayIcon(row)
+        if (!icon) return h('span', { class: 'text-g-400' }, '-')
+        return h(
+          'div',
+          {
+            class:
+              'mx-auto flex h-8 w-8 items-center justify-center rounded-md border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)]'
+          },
+          [h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })]
+        )
+      }
+    },
+    {
+      prop: 'type',
+      label: '鑿滃崟绫诲瀷',
+      width: 110,
+      formatter: (row) =>
+        h(ElTag, { type: getMenuPdaTypeTag(row), effect: 'light' }, () => getMenuPdaTypeText(row))
+    },
+    {
+      prop: 'route',
+      label: '璺敱',
+      minWidth: 180,
+      formatter: (row) => row.route || ''
+    },
+    {
+      prop: 'authority',
+      label: '鏉冮檺鏍囪瘑',
+      minWidth: 180,
+      formatter: (row) => row.authority || '-'
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭',
+      width: 90
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => {
+        const statusMeta = getMenuPdaStatusMeta(row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 120,
+      align: 'right',
+      formatter: (row) =>
+        h('div', { class: 'flex justify-end' }, [
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEditMenu(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'delete',
+            onClick: () => handleDeleteMenu(row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/menu-pda/modules/menu-pda-dialog.vue b/rsf-design/src/views/manager/menu-pda/modules/menu-pda-dialog.vue
new file mode 100644
index 0000000..05186b8
--- /dev/null
+++ b/rsf-design/src/views/manager/menu-pda/modules/menu-pda-dialog.vue
@@ -0,0 +1,273 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    @update:model-value="handleCancel"
+    width="760px"
+    align-center
+    class="menu-dialog"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="24"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    >
+      <template #menuType>
+        <ElRadioGroup v-model="form.menuType" :disabled="disableMenuType">
+          <ElRadioButton value="menu">鑿滃崟</ElRadioButton>
+          <ElRadioButton value="button">鎸夐挳</ElRadioButton>
+        </ElRadioGroup>
+      </template>
+    </ArtForm>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+
+  const createMenuPdaFormState = () => ({
+    menuType: 'menu',
+    id: null,
+    parentId: 0,
+    name: '',
+    route: '',
+    component: '',
+    authority: '',
+    icon: '',
+    sort: 0,
+    status: 1,
+    memo: ''
+  })
+
+  const props = defineProps({
+    visible: { required: false, default: false },
+    lockType: { required: false, default: false },
+    editData: { required: false, default: null },
+    menuTreeOptions: { required: false, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createMenuPdaFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => `${isEdit.value ? '缂栬緫' : '鏂板缓'}${form.menuType === 'button' ? '鎸夐挳' : '鑿滃崟'}`)
+  const disableMenuType = computed(() => props.lockType || isEdit.value)
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: form.menuType === 'button' ? '璇疯緭鍏ユ潈闄愬悕绉�' : '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }],
+    route: form.menuType === 'menu' ? [{ required: true, message: '璇疯緭鍏ヨ矾鐢卞湴鍧�', trigger: 'blur' }] : [],
+    authority:
+      form.menuType === 'button'
+        ? [{ required: true, message: '璇疯緭鍏ユ潈闄愭爣璇�', trigger: 'blur' }]
+        : []
+  }))
+
+  const formItems = computed(() => {
+    const items = [
+      { label: '鑿滃崟绫诲瀷', key: 'menuType', span: 24 },
+      {
+        label: '涓婄骇鑿滃崟',
+        key: 'parentId',
+        type: 'treeselect',
+        span: 24,
+        props: {
+          data: props.menuTreeOptions,
+          props: {
+            label: 'label',
+            value: 'value',
+            children: 'children'
+          },
+          placeholder: '璇烽�夋嫨涓婄骇鑿滃崟',
+          checkStrictly: true,
+          clearable: false,
+          defaultExpandAll: true
+        }
+      },
+      {
+        label: form.menuType === 'button' ? '鏉冮檺鍚嶇О' : '鑿滃崟鍚嶇О',
+        key: 'name',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: form.menuType === 'button' ? '璇疯緭鍏ユ潈闄愬悕绉�' : '璇疯緭鍏ヨ彍鍗曞悕绉�',
+          clearable: true
+        }
+      }
+    ]
+
+    if (form.menuType === 'menu') {
+      items.push(
+        {
+          label: '璺敱鍦板潃',
+          key: 'route',
+          type: 'input',
+          span: 24,
+          props: {
+            placeholder: '璇疯緭鍏ヨ矾鐢卞湴鍧�',
+            clearable: true
+          }
+        },
+        {
+          label: '缁勪欢鏍囪瘑',
+          key: 'component',
+          type: 'input',
+          span: 24,
+          props: {
+            placeholder: '璇疯緭鍏ョ粍浠舵爣璇�',
+            clearable: true
+          }
+        }
+      )
+    }
+
+    items.push(
+      {
+        label: '鏉冮檺鏍囪瘑',
+        key: 'authority',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: '璇疯緭鍏ユ潈闄愭爣璇�',
+          clearable: true
+        }
+      },
+      {
+        label: '鍥炬爣',
+        key: 'icon',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: '璇疯緭鍏ュ浘鏍囧悕绉�',
+          clearable: true
+        }
+      },
+      {
+        label: '鎺掑簭',
+        key: 'sort',
+        type: 'number',
+        span: 24,
+        props: {
+          min: 0,
+          controlsPosition: 'right',
+          style: { width: '100%' }
+        }
+      },
+      {
+        label: '鐘舵��',
+        key: 'status',
+        type: 'select',
+        span: 24,
+        props: {
+          placeholder: '璇烽�夋嫨鐘舵��',
+          options: [
+            { label: '鍚敤', value: 1 },
+            { label: '绂佺敤', value: 0 }
+          ]
+        }
+      },
+      {
+        label: '澶囨敞',
+        key: 'memo',
+        type: 'input',
+        span: 24,
+        props: {
+          type: 'textarea',
+          rows: 3,
+          placeholder: '璇疯緭鍏ュ娉�',
+          clearable: true
+        }
+      }
+    )
+
+    return items
+  })
+
+  const normalizeNumber = (value, fallback = 0) => {
+    if (value === '' || value === null || value === undefined) {
+      return fallback
+    }
+    const normalized = Number(value)
+    return Number.isNaN(normalized) ? fallback : normalized
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createMenuPdaFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    resetForm()
+    const row = props.editData
+    if (!row || typeof row !== 'object') {
+      return
+    }
+    form.menuType = Number(row.type) === 1 ? 'button' : 'menu'
+    form.id = row.id ?? null
+    form.parentId = normalizeNumber(row.parentId, 0)
+    form.name = row.name || ''
+    form.route = row.route || ''
+    form.component = row.component || ''
+    form.authority = row.authority || ''
+    form.icon = row.icon || ''
+    form.sort = normalizeNumber(row.sort, 0)
+    form.status = normalizeNumber(row.status, 1)
+    form.memo = row.memo || ''
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', {
+        ...form,
+        type: form.menuType === 'button' ? 1 : 0
+      })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.editData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/manager/qly-inspect/index.vue b/rsf-design/src/views/manager/qly-inspect/index.vue
new file mode 100644
index 0000000..405cad4
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-inspect/index.vue
@@ -0,0 +1,416 @@
+<template>
+  <div class="qly-inspect-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData">
+        <template #left>
+          <ElSpace wrap>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :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"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <QlyInspectItemsDrawer
+      v-model:visible="itemsDrawerVisible"
+      :loading="itemsLoading"
+      :summary="activeRow"
+      :data="itemsTableData"
+      :columns="itemColumns"
+      :pagination="itemsPagination"
+      @size-change="handleItemsSizeChange"
+      @current-change="handleItemsCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportQlyInspectReport,
+    fetchGetQlyInspectMany,
+    fetchQlyInspectItemPage,
+    fetchQlyInspectPage
+  } from '@/api/qly-inspect'
+  import QlyInspectItemsDrawer from './modules/qly-inspect-items-drawer.vue'
+  import { createQlyInspectTableColumns } from './qlyInspectTable.columns'
+  import {
+    buildQlyInspectItemsQueryParams,
+    buildQlyInspectPageQueryParams,
+    buildQlyInspectPrintRows,
+    buildQlyInspectReportMeta,
+    buildQlyInspectSearchParams,
+    createQlyInspectSearchState,
+    normalizeQlyInspectItemRow,
+    normalizeQlyInspectRow,
+    QLY_INSPECT_REPORT_STYLE,
+    QLY_INSPECT_REPORT_TITLE
+  } from './qlyInspectPage.helpers'
+
+  defineOptions({ name: 'QlyInspect' })
+
+  const userStore = useUserStore()
+  const reportTitle = QLY_INSPECT_REPORT_TITLE
+  const loading = ref(false)
+  const tableData = ref([])
+  const selectedRows = ref([])
+  const searchForm = ref(createQlyInspectSearchState())
+
+  const itemsDrawerVisible = ref(false)
+  const itemsLoading = ref(false)
+  const itemsTableData = ref([])
+  const activeRow = ref({})
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const itemsPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildQlyInspectSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ川妫�鍗曞彿/鏉ユ簮鍗曞彿'
+      }
+    },
+    {
+      label: '璐ㄦ鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ川妫�鍗曞彿'
+      }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'wkType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鎹被鍨�'
+      }
+    },
+    {
+      label: '鏉ユ簮鍗曞彿',
+      key: 'asnCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鍙�'
+      }
+    },
+    {
+      label: '璐ㄦ鐘舵��',
+      key: 'isptStatus',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ川妫�鐘舵��'
+      }
+    }
+  ])
+
+  const { columns, columnChecks } = useTableColumns(() =>
+    createQlyInspectTableColumns({
+      handleViewItems: openItemsDrawer
+    })
+  )
+
+  const itemColumns = computed(() => [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'ispectId',
+      label: '璐ㄦ鍗旾D',
+      minWidth: 110
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'label',
+      label: '鏍囩',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockBatch',
+      label: '搴撳瓨鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'rcptQty',
+      label: '鏀惰揣鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'dlyQty',
+      label: '閫佹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'anfme',
+      label: '纭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'isptResultText',
+      label: '璐ㄦ缁撴灉',
+      minWidth: 120
+    }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchQlyInspectPage(
+          buildQlyInspectPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        {
+          timeoutMessage: '璐ㄦ淇℃伅鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeQlyInspectRow(record))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadItemsData() {
+    if (!activeRow.value?.id) {
+      return
+    }
+
+    itemsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchQlyInspectItemPage(
+          buildQlyInspectItemsQueryParams({
+            ispectId: activeRow.value.id,
+            current: itemsPagination.current,
+            pageSize: itemsPagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: itemsPagination.current,
+          size: itemsPagination.size
+        },
+        {
+          timeoutMessage: '璐ㄦ淇℃伅鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      itemsTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeQlyInspectItemRow(record))
+        : []
+      updatePaginationState(itemsPagination, response, itemsPagination.current, itemsPagination.size)
+    } finally {
+      itemsLoading.value = false
+    }
+  }
+
+  function openItemsDrawer(row) {
+    activeRow.value = row
+    itemsPagination.current = 1
+    itemsDrawerVisible.value = true
+    loadItemsData()
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createQlyInspectSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleItemsSizeChange(size) {
+    itemsPagination.size = size
+    itemsPagination.current = 1
+    loadItemsData()
+  }
+
+  function handleItemsCurrentChange(current) {
+    itemsPagination.current = current
+    loadItemsData()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'qly-inspect.xlsx',
+    requestExport: (payload) =>
+      fetchExportQlyInspectReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetQlyInspectMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchQlyInspectPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildQlyInspectPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...QLY_INSPECT_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildQlyInspectReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || QLY_INSPECT_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(() => {
+    loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/qly-inspect/modules/qly-inspect-items-drawer.vue b/rsf-design/src/views/manager/qly-inspect/modules/qly-inspect-items-drawer.vue
new file mode 100644
index 0000000..99797ea
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-inspect/modules/qly-inspect-items-drawer.vue
@@ -0,0 +1,44 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璐ㄦ淇℃伅鏄庣粏"
+    size="72%"
+    destroy-on-close
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="璐ㄦ鍗曞彿">{{ summary.code || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ summary.wkTypeLabel || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏉ユ簮鍗曞彿">{{ summary.asnCode || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璐ㄦ鏁伴噺">{{ summary.isptQty ?? '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璐ㄦ鐘舵��">{{ summary.isptStatusText || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ summary.updateByText || '-' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="emit('size-change', $event)"
+        @pagination:current-change="emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'QlyInspectItemsDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+</script>
diff --git a/rsf-design/src/views/manager/qly-inspect/qlyInspectPage.helpers.js b/rsf-design/src/views/manager/qly-inspect/qlyInspectPage.helpers.js
new file mode 100644
index 0000000..432efc5
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-inspect/qlyInspectPage.helpers.js
@@ -0,0 +1,125 @@
+export const QLY_INSPECT_REPORT_TITLE = '璐ㄦ淇℃伅鎶ヨ〃'
+export const QLY_INSPECT_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createQlyInspectSearchState() {
+  return {
+    condition: '',
+    code: '',
+    wkType: '',
+    asnCode: '',
+    isptStatus: '',
+    isptQty: ''
+  }
+}
+
+export function buildQlyInspectSearchParams(params = {}) {
+  const result = {}
+
+  ;['condition', 'code', 'wkType', 'asnCode', 'isptStatus', 'isptQty'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildQlyInspectPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildQlyInspectSearchParams(params)
+  }
+}
+
+export function buildQlyInspectItemsQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.ispectId !== undefined ? { ispectId: params.ispectId } : {})
+  }
+}
+
+export function normalizeQlyInspectRow(record = {}) {
+  return {
+    ...record,
+    code: record.code || '-',
+    wkTypeLabel: record['wkType$'] || record.wkType || '-',
+    asnId: record.asnId ?? '-',
+    asnCode: record.asnCode || '-',
+    isptQty: normalizeNumber(record.isptQty),
+    isptStatusText: record['isptStatus$'] || '-',
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    statusText:
+      record.statusBool === true || Number(record.status) === 1
+        ? '鍚敤'
+        : record.statusBool === false || Number(record.status) === 0
+          ? '鍋滅敤'
+          : '-',
+    memo: record.memo || '-'
+  }
+}
+
+export function normalizeQlyInspectItemRow(record = {}) {
+  return {
+    ...record,
+    ispectId: record.ispectId ?? '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    label: record.label || '-',
+    splrBatch: record.splrBatch || '-',
+    stockBatch: record.stockBatch || '-',
+    rcptQty: normalizeNumber(record.rcptQty),
+    dlyQty: normalizeNumber(record.dlyQty),
+    anfme: normalizeNumber(record.anfme),
+    splrName: record.splrName || '-',
+    isptResultText: record['isptResult$'] || '-'
+  }
+}
+
+export function buildQlyInspectPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeQlyInspectRow(record))
+}
+
+export function buildQlyInspectReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = QLY_INSPECT_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: QLY_INSPECT_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...QLY_INSPECT_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/manager/qly-inspect/qlyInspectTable.columns.js b/rsf-design/src/views/manager/qly-inspect/qlyInspectTable.columns.js
new file mode 100644
index 0000000..73354f2
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-inspect/qlyInspectTable.columns.js
@@ -0,0 +1,77 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createQlyInspectTableColumns({ handleViewItems }) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: '璐ㄦ鍗曞彿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'asnId',
+      label: '鏉ユ簮鍗旾D',
+      minWidth: 110
+    },
+    {
+      prop: 'asnCode',
+      label: '鏉ユ簮鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'isptQty',
+      label: '璐ㄦ鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'isptStatusText',
+      label: '璐ㄦ鐘舵��',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          text: '鏌ョ湅鏄庣粏',
+          onClick: () => handleViewItems(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/qly-ispt-item-result/index.vue b/rsf-design/src/views/manager/qly-ispt-item-result/index.vue
new file mode 100644
index 0000000..9bedef1
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-ispt-item-result/index.vue
@@ -0,0 +1,74 @@
+<template>
+  <div class="qly-ispt-item-result-page art-full-height">
+    <ElCard class="art-table-card">
+      <template #header>
+        <div class="flex items-center justify-between">
+          <div>
+            <div class="text-base font-semibold text-gray-900">璐ㄦ缁撴灉鏄庣粏</div>
+            <div class="mt-1 text-sm text-gray-500">鎸夎川妫�鍗曠淮搴︽煡鐪嬭川妫�缁撴灉鏄庣粏</div>
+          </div>
+          <ElButton @click="refreshData" :loading="loading">鍒锋柊</ElButton>
+        </div>
+      </template>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <QlyIsptItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useRoute } from 'vue-router'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchGetQlyIsptItemDetail, fetchQlyIsptItemResultPage } from '@/api/qly-ispt-item'
+  import {
+    buildQlyIsptItemResultPageQueryParams,
+    getQlyIsptItemPaginationKey,
+    normalizeQlyIsptItemRow
+  } from '../qly-ispt-item/qlyIsptItemPage.helpers'
+  import { createQlyIsptItemTableColumns } from '../qly-ispt-item/qlyIsptItemTable.columns'
+  import QlyIsptItemDetailDrawer from '../qly-ispt-item/modules/qly-ispt-item-detail-drawer.vue'
+
+  defineOptions({ name: 'QlyIsptItemResult' })
+
+  const route = useRoute()
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const inspectId = computed(() => route.query.id || route.query.ispectId || '')
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const { columns, data, loading, pagination, handleSizeChange, handleCurrentChange, refreshData } = useTable({
+    core: {
+      apiFn: fetchQlyIsptItemResultPage,
+      apiParams: buildQlyIsptItemResultPageQueryParams({ id: inspectId.value }),
+      paginationKey: getQlyIsptItemPaginationKey(),
+      columnsFactory: () => createQlyIsptItemTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeQlyIsptItemRow(item)) : []
+    }
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetQlyIsptItemDetail(id)
+    detailData.value = normalizeQlyIsptItemRow({
+      ...fallback,
+      ...detail
+    })
+  }
+</script>
diff --git a/rsf-design/src/views/manager/qly-ispt-item/index.vue b/rsf-design/src/views/manager/qly-ispt-item/index.vue
new file mode 100644
index 0000000..c729b59
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-ispt-item/index.vue
@@ -0,0 +1,245 @@
+<template>
+  <div class="qly-ispt-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <QlyIsptItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useRoute } 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 {
+    fetchExportQlyIsptItemReport,
+    fetchGetQlyIsptItemDetail,
+    fetchGetQlyIsptItemMany,
+    fetchQlyIsptItemPage
+  } from '@/api/qly-ispt-item'
+  import {
+    buildQlyIsptItemPageQueryParams,
+    buildQlyIsptItemPrintRows,
+    buildQlyIsptItemSearchParams,
+    createQlyIsptItemSearchState,
+    getQlyIsptItemPaginationKey,
+    getQlyIsptItemReportColumns,
+    normalizeQlyIsptItemRow,
+    QLY_ISPT_ITEM_REPORT_TITLE
+  } from './qlyIsptItemPage.helpers'
+  import { createQlyIsptItemTableColumns } from './qlyIsptItemTable.columns'
+  import QlyIsptItemDetailDrawer from './modules/qly-ispt-item-detail-drawer.vue'
+
+  defineOptions({ name: 'QlyIsptItem' })
+
+  const route = useRoute()
+  const userStore = useUserStore()
+  const initialInspectId = route.query.ispectId || route.query.id || ''
+  const searchForm = ref(createQlyIsptItemSearchState({ ispectId: initialInspectId }))
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = QLY_ISPT_ITEM_REPORT_TITLE
+  const reportQueryParams = computed(() => buildQlyIsptItemSearchParams(searchForm.value))
+  const reportColumns = getQlyIsptItemReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�/鍚嶇О/渚涘簲鍟�'
+      }
+    },
+    {
+      label: '璐ㄦ鍗旾D',
+      key: 'ispectId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ川妫�鍗旾D'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '渚涘簲鍟�',
+      key: 'splrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢'
+      }
+    },
+    {
+      label: '瀹㈡埛璁㈠崟鍙�',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴疯鍗曞彿'
+      }
+    },
+    {
+      label: '宸ュ崟鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchQlyIsptItemPage,
+      apiParams: buildQlyIsptItemPageQueryParams(searchForm.value),
+      paginationKey: getQlyIsptItemPaginationKey(),
+      columnsFactory: () => createQlyIsptItemTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeQlyIsptItemRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetQlyIsptItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchQlyIsptItemPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'qly-ispt-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportQlyIsptItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildQlyIsptItemPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetQlyIsptItemDetail(id)
+    detailData.value = normalizeQlyIsptItemRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildQlyIsptItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    const resetSeed = route.query.ispectId || route.query.id ? { ispectId: route.query.ispectId || route.query.id } : {}
+    Object.assign(searchForm.value, createQlyIsptItemSearchState(resetSeed))
+    resetSearchParams(buildQlyIsptItemPageQueryParams(createQlyIsptItemSearchState(resetSeed)))
+  }
+</script>
diff --git a/rsf-design/src/views/manager/qly-ispt-item/modules/qly-ispt-item-detail-drawer.vue b/rsf-design/src/views/manager/qly-ispt-item/modules/qly-ispt-item-detail-drawer.vue
new file mode 100644
index 0000000..868b7b5
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-ispt-item/modules/qly-ispt-item-detail-drawer.vue
@@ -0,0 +1,49 @@
+<template>
+  <ElDrawer :model-value="visible" title="璐ㄦ鏄庣粏璇︽儏" size="72%" @update:model-value="handleVisibleChange">
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElDescriptions :column="2" border>
+      <ElDescriptionsItem label="鏄庣粏ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="璐ㄦ鍗旾D">{{ detail.ispectId ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏍囩">{{ detail.label ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.splrName ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="搴撳瓨鎵规">{{ detail.stockBatch ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ detail.platOrderCode ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="宸ュ崟鍙�">{{ detail.platWorkCode ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="椤圭洰鍙�">{{ detail.projectCode ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="璐ㄦ缁撴灉">{{ detail.isptResultText ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏀惰揣鏁伴噺">{{ detail.rcptQty ?? 0 }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="閫佹鏁伴噺">{{ detail.dlyQty ?? 0 }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="涓嶅悎鏍兼暟閲�">{{ detail.disQty ?? 0 }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍚堟牸鏁伴噺">{{ detail.safeQty ?? 0 }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="纭鏁伴噺">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐘舵��">{{ detail.statusText ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem :span="2" label="澶囨敞">{{ detail.memo ?? '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'QlyIsptItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemPage.helpers.js b/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemPage.helpers.js
new file mode 100644
index 0000000..f149070
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemPage.helpers.js
@@ -0,0 +1,167 @@
+export const QLY_ISPT_ITEM_REPORT_TITLE = '璐ㄦ淇℃伅鏄庣粏鎶ヨ〃'
+
+export function createQlyIsptItemSearchState(seed = {}) {
+  return {
+    condition: '',
+    ispectId: '',
+    matnrCode: '',
+    maktx: '',
+    label: '',
+    splrName: '',
+    splrBatch: '',
+    stockBatch: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    rcptQty: '',
+    dlyQty: '',
+    disQty: '',
+    safeQty: '',
+    picPath: '',
+    memo: '',
+    status: '',
+    ...seed
+  }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return undefined
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : undefined
+}
+
+export function getQlyIsptItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildQlyIsptItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'matnrCode',
+    'maktx',
+    'label',
+    'splrName',
+    'splrBatch',
+    'stockBatch',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'picPath',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['ispectId', 'rcptQty', 'dlyQty', 'disQty', 'safeQty', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== undefined) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildQlyIsptItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildQlyIsptItemSearchParams(params)
+  }
+}
+
+export function buildQlyIsptItemResultPageQueryParams(params = {}) {
+  const id = normalizeNumber(params.id ?? params.ispectId)
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(id !== undefined ? { id } : {})
+  }
+}
+
+function resolveStatusText(record) {
+  if (record.statusBool === true || Number(record.status) === 1) {
+    return '姝e父'
+  }
+  if (record.statusBool === false || Number(record.status) === 0) {
+    return '鍋滅敤'
+  }
+  return '--'
+}
+
+export function normalizeQlyIsptItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? '--',
+    ispectId: record.ispectId ?? '--',
+    matnrCode: record.matnrCode || '--',
+    maktx: record.maktx || '--',
+    label: record.label || '--',
+    splrName: record.splrName || '--',
+    splrBatch: record.splrBatch || '--',
+    stockBatch: record.stockBatch || '--',
+    platOrderCode: record.platOrderCode || '--',
+    platWorkCode: record.platWorkCode || '--',
+    projectCode: record.projectCode || '--',
+    rcptQty: record.rcptQty ?? 0,
+    dlyQty: record.dlyQty ?? 0,
+    disQty: record.disQty ?? 0,
+    safeQty: record.safeQty ?? 0,
+    anfme: record.anfme ?? 0,
+    isptResultText: record['isptResult$'] || '--',
+    statusText: resolveStatusText(record),
+    createByText: record['createBy$'] || '--',
+    createTimeText: record['createTime$'] || record.createTime || '--',
+    updateByText: record['updateBy$'] || '--',
+    updateTimeText: record['updateTime$'] || record.updateTime || '--',
+    memo: record.memo || '--'
+  }
+}
+
+export function getQlyIsptItemReportColumns() {
+  return [
+    { prop: 'ispectId', label: '璐ㄦ鍗旾D' },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { prop: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { prop: 'label', label: '鏍囩' },
+    { prop: 'splrName', label: '渚涘簲鍟�' },
+    { prop: 'splrBatch', label: '渚涘簲鍟嗘壒娆�' },
+    { prop: 'stockBatch', label: '搴撳瓨鎵规' },
+    { prop: 'platOrderCode', label: '瀹㈡埛璁㈠崟鍙�' },
+    { prop: 'platWorkCode', label: '宸ュ崟鍙�' },
+    { prop: 'projectCode', label: '椤圭洰鍙�' },
+    { prop: 'rcptQty', label: '鏀惰揣鏁伴噺' },
+    { prop: 'dlyQty', label: '閫佹鏁伴噺' },
+    { prop: 'disQty', label: '涓嶅悎鏍兼暟閲�' },
+    { prop: 'safeQty', label: '鍚堟牸鏁伴噺' },
+    { prop: 'anfme', label: '纭鏁伴噺' },
+    { prop: 'isptResultText', label: '璐ㄦ缁撴灉' },
+    { prop: 'createByText', label: '鍒涘缓浜�' },
+    { prop: 'createTimeText', label: '鍒涘缓鏃堕棿' },
+    { prop: 'updateByText', label: '鏇存柊浜�' },
+    { prop: 'updateTimeText', label: '鏇存柊鏃堕棿' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildQlyIsptItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeQlyIsptItemRow(record))
+}
diff --git a/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemTable.columns.js b/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemTable.columns.js
new file mode 100644
index 0000000..a2184c3
--- /dev/null
+++ b/rsf-design/src/views/manager/qly-ispt-item/qlyIsptItemTable.columns.js
@@ -0,0 +1,150 @@
+import { h } from 'vue'
+import { ElButton, ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createQlyIsptItemTableColumns({ handleView }) {
+  return [
+    {
+      type: 'selection',
+      width: 52,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'ispectId',
+      label: '璐ㄦ鍗旾D',
+      minWidth: 110
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'label',
+      label: '鏍囩',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockBatch',
+      label: '搴撳瓨鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platOrderCode',
+      label: '瀹㈡埛璁㈠崟鍙�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platWorkCode',
+      label: '宸ュ崟鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'projectCode',
+      label: '椤圭洰鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'rcptQty',
+      label: '鏀惰揣鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'dlyQty',
+      label: '閫佹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'disQty',
+      label: '涓嶅悎鏍兼暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'safeQty',
+      label: '鍚堟牸鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'anfme',
+      label: '纭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'isptResultText',
+      label: '璐ㄦ缁撴灉',
+      minWidth: 120
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.statusText === '姝e父' ? 'success' : 'info' },
+          () => row.statusText || '--'
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170
+    },
+    {
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(
+          ArtButtonTable,
+          {},
+          () =>
+            h(
+              ElButton,
+              {
+                link: true,
+                type: 'primary',
+                onClick: () => handleView(row)
+              },
+              () => '璇︽儏'
+            )
+        )
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/revise-log-item/index.vue b/rsf-design/src/views/manager/revise-log-item/index.vue
new file mode 100644
index 0000000..bd361b2
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log-item/index.vue
@@ -0,0 +1,174 @@
+<template>
+  <div class="revise-log-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <ReviseLogItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchGetReviseLogItemDetail, fetchReviseLogItemPage } from '@/api/revise-log-item'
+  import ReviseLogItemDetailDrawer from './modules/revise-log-item-detail-drawer.vue'
+  import { createReviseLogItemTableColumns } from './reviseLogItemTable.columns'
+  import {
+    buildReviseLogItemPageQueryParams,
+    buildReviseLogItemSearchParams,
+    createReviseLogItemSearchState,
+    getReviseLogItemPaginationKey,
+    normalizeReviseLogItemRow
+  } from './reviseLogItemPage.helpers'
+
+  defineOptions({ name: 'ReviseLogItem' })
+
+  const searchForm = ref(createReviseLogItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱浣�/鐗╂枡/鎵规' }
+    },
+    {
+      label: '鏃ュ織ID',
+      key: 'reviseLogId',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ棩蹇桰D' }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'locCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�' }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' }
+    },
+    {
+      label: '鎵╁睍瀛楁',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ墿灞曞瓧娈�' }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetailData(row.id)
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData } =
+    useTable({
+      core: {
+        apiFn: fetchReviseLogItemPage,
+        apiParams: buildReviseLogItemPageQueryParams(searchForm.value),
+        paginationKey: getReviseLogItemPaginationKey(),
+        columnsFactory: () => createReviseLogItemTableColumns({ handleView: openDetail })
+      },
+      transform: {
+        dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeReviseLogItemRow(item)) : [])
+      }
+    })
+
+  async function loadDetailData(id) {
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetReviseLogItemDetail(id), {}, {
+        timeoutMessage: '搴撲綅璋冩暣鏃ュ織鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeReviseLogItemRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撲綅璋冩暣鏃ュ織鏄庣粏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildReviseLogItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createReviseLogItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/revise-log-item/modules/revise-log-item-detail-drawer.vue b/rsf-design/src/views/manager/revise-log-item/modules/revise-log-item-detail-drawer.vue
new file mode 100644
index 0000000..b428d47
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log-item/modules/revise-log-item-detail-drawer.vue
@@ -0,0 +1,48 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撲綅璋冩暣鏃ュ織鏄庣粏璇︽儏"
+    size="66%"
+    destroy-on-close
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <ElSkeleton :loading="loading" animated :rows="8">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="鏃ュ織ID">{{ detail.reviseLogId ?? '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍘熷簱瀛�">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璋冩暣鏁伴噺">{{ detail.reviseQty ?? 0 }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="宸紓鏁伴噺">{{ detail.diffQty ?? 0 }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ detail.batch || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵╁睍瀛楁">{{ detail.fieldsIndex || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="detail.statusTagType || 'info'" effect="light">
+            {{ detail.statusText || '-' }}
+          </ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '-' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'ReviseLogItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+</script>
diff --git a/rsf-design/src/views/manager/revise-log-item/reviseLogItemPage.helpers.js b/rsf-design/src/views/manager/revise-log-item/reviseLogItemPage.helpers.js
new file mode 100644
index 0000000..e9a416f
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log-item/reviseLogItemPage.helpers.js
@@ -0,0 +1,128 @@
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1, tagType: 'success' },
+  { label: '鍐荤粨', value: 0, tagType: 'danger' }
+]
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function pickText(record, keys, fallback = '-') {
+  for (const key of keys) {
+    const value = record?.[key]
+    if (typeof value === 'string' && value.trim()) {
+      return value.trim()
+    }
+  }
+  return fallback
+}
+
+export const REVISE_LOG_ITEM_REPORT_TITLE = '搴撲綅璋冩暣鏃ュ織鏄庣粏'
+export const REVISE_LOG_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+export function getReviseLogItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function createReviseLogItemSearchState() {
+  return {
+    condition: '',
+    reviseLogId: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    fieldsIndex: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildReviseLogItemSearchParams(params = {}) {
+  const result = {}
+  ;[
+    'condition',
+    'locCode',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'fieldsIndex',
+    'memo',
+    'timeStart',
+    'timeEnd'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['reviseLogId', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key], void 0)
+    if (value !== void 0) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildReviseLogItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildReviseLogItemSearchParams(params)
+  }
+}
+
+export function getReviseLogItemStatusMeta(value) {
+  const target = STATUS_OPTIONS.find((item) => Number(item.value) === Number(value))
+  return target || { label: '-', value, tagType: 'info' }
+}
+
+export function normalizeReviseLogItemRow(record = {}) {
+  const statusMeta = getReviseLogItemStatusMeta(record.status)
+  const reviseQty = normalizeNumber(record.reviseQty, 0)
+  const anfme = normalizeNumber(record.anfme, 0)
+  return {
+    ...record,
+    reviseLogId: normalizeNumber(record.reviseLogId, record.reviseLogId),
+    locCode: pickText(record, ['locCode']),
+    matnrCode: pickText(record, ['matnrCode']),
+    maktx: pickText(record, ['maktx']),
+    unit: pickText(record, ['unit']),
+    anfme,
+    reviseQty,
+    diffQty: Math.round((reviseQty - anfme) * 10000) / 10000,
+    batch: pickText(record, ['batch']),
+    spec: pickText(record, ['spec']),
+    model: pickText(record, ['model']),
+    fieldsIndex: pickText(record, ['fieldsIndex']),
+    statusText: pickText(record, ['status$'], statusMeta.label),
+    statusTagType: statusMeta.tagType,
+    createByText: pickText(record, ['createBy$']),
+    createTimeText: pickText(record, ['createTime$'], record.createTime || '-'),
+    updateByText: pickText(record, ['updateBy$']),
+    updateTimeText: pickText(record, ['updateTime$'], record.updateTime || '-'),
+    memo: pickText(record, ['memo'], '-')
+  }
+}
diff --git a/rsf-design/src/views/manager/revise-log-item/reviseLogItemTable.columns.js b/rsf-design/src/views/manager/revise-log-item/reviseLogItemTable.columns.js
new file mode 100644
index 0000000..2c495b5
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log-item/reviseLogItemTable.columns.js
@@ -0,0 +1,260 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createReviseLogItemTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'reviseLogId',
+      label: '鏃ュ織ID',
+      width: 92,
+      align: 'right'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '鍘熷簱瀛�',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'reviseQty',
+      label: '璋冩暣鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'diffQty',
+      label: '宸紓鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: Number(row.diffQty) === 0 ? 'info' : Number(row.diffQty) > 0 ? 'success' : 'danger',
+            effect: 'light'
+          },
+          () => String(row.diffQty)
+        )
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鎵╁睍瀛楁',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(ElTag, { type: row.statusTagType || 'info', effect: 'light' }, () => row.statusText)
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 96,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
+
+export function createReviseLogItemDetailColumns() {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'reviseLogId',
+      label: '鏃ュ織ID',
+      width: 92,
+      align: 'right'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '鍘熷簱瀛�',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'reviseQty',
+      label: '璋冩暣鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'diffQty',
+      label: '宸紓鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鎵╁睍瀛楁',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/revise-log/index.vue b/rsf-design/src/views/manager/revise-log/index.vue
new file mode 100644
index 0000000..bb674ee
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log/index.vue
@@ -0,0 +1,266 @@
+<template>
+  <div class="revise-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <ReviseLogDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :summary="detailData"
+      :item-loading="itemLoading"
+      :item-data="itemTableData"
+      :item-columns="itemColumns"
+      :item-pagination="itemPagination"
+      @item-size-change="handleItemSizeChange"
+      @item-current-change="handleItemCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchGetReviseLogDetail, fetchReviseLogPage } from '@/api/revise-log'
+  import { fetchReviseLogItemPage } from '@/api/revise-log-item'
+  import ReviseLogDetailDrawer from './modules/revise-log-detail-drawer.vue'
+  import { createReviseLogTableColumns } from './reviseLogTable.columns'
+  import {
+    buildReviseLogPageQueryParams,
+    buildReviseLogSearchParams,
+    createReviseLogSearchState,
+    getReviseLogPaginationKey,
+    getReviseLogStatusOptions,
+    getReviseLogTypeOptions,
+    normalizeReviseLogRow
+  } from './reviseLogPage.helpers'
+  import { createReviseLogItemDetailColumns } from '../revise-log-item/reviseLogItemTable.columns'
+  import { normalizeReviseLogItemRow } from '../revise-log-item/reviseLogItemPage.helpers'
+
+  defineOptions({ name: 'ReviseLog' })
+
+  const searchForm = ref(createReviseLogSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const itemLoading = ref(false)
+  const itemTableData = ref([])
+  const activeDetail = ref({})
+  const itemPagination = reactive({ current: 1, size: 20, total: 0 })
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鏁村崟鍙�/搴撲綅/瀹瑰櫒鐮�' }
+    },
+    {
+      label: '璋冩暣鍗曞彿',
+      key: 'reviseId',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鏁村崟ID' }
+    },
+    {
+      label: '璋冩暣缂栫爜',
+      key: 'reviseCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鏁村崟缂栫爜' }
+    },
+    {
+      label: '浠撳簱ID',
+      key: 'warehouseId',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ粨搴揑D' }
+    },
+    {
+      label: '搴撳尯ID',
+      key: 'areaId',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱鍖篒D' }
+    },
+    {
+      label: '璋冩暣绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getReviseLogTypeOptions().map((item) => ({ label: item.label, value: item.value }))
+      }
+    },
+    {
+      label: '瀹瑰櫒鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ鍣ㄧ爜' }
+    },
+    {
+      label: '搴撲綅鐘舵��',
+      key: 'useStatus',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ簱浣嶇姸鎬�' }
+    },
+    {
+      label: '宸烽亾',
+      key: 'channel',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ贩閬�' }
+    },
+    {
+      label: '鎺�',
+      key: 'row',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ帓' }
+    },
+    {
+      label: '鍒�',
+      key: 'col',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ垪' }
+    },
+    {
+      label: '灞�',
+      key: 'lev',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ眰' }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getReviseLogStatusOptions().map((item) => ({ label: item.label, value: item.value }))
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'date',
+      props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' }
+    }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    activeDetail.value = row || {}
+    itemTableData.value = []
+    itemPagination.current = 1
+    try {
+      detailData.value = normalizeReviseLogRow(
+        await guardRequestWithMessage(fetchGetReviseLogDetail(row.id), {}, {
+          timeoutMessage: '搴撲綅璋冩暣鏃ュ織璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        })
+      )
+      await loadItemData(row.id)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撲綅璋冩暣鏃ュ織璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function loadItemData(reviseLogId = activeDetail.value?.id) {
+    if (!reviseLogId) {
+      itemTableData.value = []
+      return
+    }
+    itemLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchReviseLogItemPage({
+          reviseLogId,
+          current: itemPagination.current,
+          pageSize: itemPagination.size
+        }),
+        { records: [], total: 0, current: itemPagination.current, size: itemPagination.size },
+        { timeoutMessage: '搴撲綅璋冩暣鏃ュ織鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      const normalized = defaultResponseAdapter(response)
+      itemTableData.value = Array.isArray(normalized?.records)
+        ? normalized.records.map((record) => normalizeReviseLogItemRow(record))
+        : []
+      updatePaginationState(itemPagination, normalized, itemPagination.current, itemPagination.size)
+    } finally {
+      itemLoading.value = false
+    }
+  }
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData } =
+    useTable({
+      core: {
+        apiFn: fetchReviseLogPage,
+        apiParams: buildReviseLogPageQueryParams(searchForm.value),
+        paginationKey: getReviseLogPaginationKey(),
+        columnsFactory: () => createReviseLogTableColumns({ handleView: openDetail })
+      },
+      transform: {
+        dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeReviseLogRow(item)) : [])
+      }
+    })
+
+  const itemColumns = computed(() => createReviseLogItemDetailColumns())
+
+  function handleItemSizeChange(size) {
+    itemPagination.size = size
+    itemPagination.current = 1
+    loadItemData()
+  }
+
+  function handleItemCurrentChange(current) {
+    itemPagination.current = current
+    loadItemData()
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildReviseLogSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createReviseLogSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/revise-log/modules/revise-log-detail-drawer.vue b/rsf-design/src/views/manager/revise-log/modules/revise-log-detail-drawer.vue
new file mode 100644
index 0000000..1cf41bd
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log/modules/revise-log-detail-drawer.vue
@@ -0,0 +1,74 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撲綅璋冩暣鏃ュ織璇︽儏"
+    size="82%"
+    destroy-on-close
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElSkeleton :loading="loading" animated :rows="8">
+        <ElDescriptions :column="3" border>
+          <ElDescriptionsItem label="璋冩暣鍗旾D">{{ summary.reviseId ?? '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璋冩暣鍗曠紪鐮�">{{ summary.reviseCode || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠撳簱">{{ summary.warehouseLabel || summary.warehouseId || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯">{{ summary.areaLabel || summary.areaId || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅缂栫爜">{{ summary.locCode || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璋冩暣绫诲瀷">{{ summary.typeLabel || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹瑰櫒鐮�">{{ summary.barcode || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅鐘舵��">{{ summary.useStatusText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="summary.statusTagType || 'info'" effect="light">
+              {{ summary.statusText || '-' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="宸烽亾">{{ summary.channelText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎺�">{{ summary.rowText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒�">{{ summary.colText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="灞�">{{ summary.levText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ summary.createByText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ summary.createTimeText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ summary.updateByText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ summary.updateTimeText || '-' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="3">{{ summary.memo || '-' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </ElSkeleton>
+
+      <div class="flex min-h-0 flex-1 flex-col gap-3">
+        <div class="flex items-center justify-between">
+          <h3 class="text-base font-semibold text-g-900">璋冩暣鏃ュ織鏄庣粏</h3>
+          <span class="text-sm text-g-500">鍏� {{ itemPagination.total || 0 }} 鏉�</span>
+        </div>
+
+        <ArtTable
+          :loading="itemLoading"
+          :data="itemData"
+          :columns="itemColumns"
+          :pagination="itemPagination"
+          @pagination:size-change="emit('item-size-change', $event)"
+          @pagination:current-change="emit('item-current-change', $event)"
+        />
+      </div>
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'ReviseLogDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    itemLoading: { type: Boolean, default: false },
+    itemData: { type: Array, default: () => [] },
+    itemColumns: { type: Array, default: () => [] },
+    itemPagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits([
+    'update:visible',
+    'item-size-change',
+    'item-current-change'
+  ])
+</script>
diff --git a/rsf-design/src/views/manager/revise-log/reviseLogPage.helpers.js b/rsf-design/src/views/manager/revise-log/reviseLogPage.helpers.js
new file mode 100644
index 0000000..6d57d46
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log/reviseLogPage.helpers.js
@@ -0,0 +1,151 @@
+const TYPE_OPTIONS = [
+  { label: '搴撳瓨璋冩暣', value: 0, tagType: 'primary' },
+  { label: '鐩樼偣璋冩暣', value: 1, tagType: 'warning' },
+  { label: '鍏跺畠璋冩暣', value: 2, tagType: 'info' }
+]
+
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1, tagType: 'success' },
+  { label: '鍐荤粨', value: 0, tagType: 'danger' }
+]
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+function pickText(record, keys, fallback = '-') {
+  for (const key of keys) {
+    const value = record?.[key]
+    if (typeof value === 'string' && value.trim()) {
+      return value.trim()
+    }
+  }
+  return fallback
+}
+
+export const REVISE_LOG_REPORT_TITLE = '搴撲綅璋冩暣鏃ュ織'
+export const REVISE_LOG_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+export function getReviseLogTypeOptions() {
+  return TYPE_OPTIONS
+}
+
+export function getReviseLogStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function getReviseLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function createReviseLogSearchState() {
+  return {
+    condition: '',
+    reviseId: '',
+    reviseCode: '',
+    warehouseId: '',
+    areaId: '',
+    type: '',
+    barcode: '',
+    useStatus: '',
+    channel: '',
+    row: '',
+    col: '',
+    lev: '',
+    memo: '',
+    status: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildReviseLogSearchParams(params = {}) {
+  const result = {}
+  ;[
+    'condition',
+    'reviseCode',
+    'barcode',
+    'useStatus',
+    'memo',
+    'timeStart',
+    'timeEnd'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['reviseId', 'warehouseId', 'areaId', 'type', 'channel', 'row', 'col', 'lev', 'status'].forEach(
+    (key) => {
+      const value = normalizeNumber(params[key], void 0)
+      if (value !== void 0) {
+        result[key] = value
+      }
+    }
+  )
+
+  return result
+}
+
+export function buildReviseLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildReviseLogSearchParams(params)
+  }
+}
+
+export function getReviseLogTypeMeta(value) {
+  const target = TYPE_OPTIONS.find((item) => Number(item.value) === Number(value))
+  return target || { label: '-', value, tagType: 'info' }
+}
+
+export function getReviseLogStatusMeta(value) {
+  const target = STATUS_OPTIONS.find((item) => Number(item.value) === Number(value))
+  return target || { label: '-', value, tagType: 'info' }
+}
+
+export function normalizeReviseLogRow(record = {}) {
+  const typeMeta = getReviseLogTypeMeta(record.type)
+  const statusMeta = getReviseLogStatusMeta(record.status)
+  return {
+    ...record,
+    reviseId: normalizeNumber(record.reviseId, record.reviseId),
+    reviseCode: pickText(record, ['reviseCode', 'code']),
+    warehouseLabel: pickText(record, ['warehouseId$', 'warehouseName', 'warehouseLabel']),
+    areaLabel: pickText(record, ['areaId$', 'areaName', 'areaLabel']),
+    locCode: pickText(record, ['locCode']),
+    typeLabel: pickText(record, ['type$'], typeMeta.label),
+    barcode: pickText(record, ['barcode']),
+    useStatusText: pickText(record, ['useStatus$'], record.useStatus || '-'),
+    channelText: pickText(record, ['channel$'], record.channel ?? '-'),
+    rowText: pickText(record, ['row$'], record.row ?? '-'),
+    colText: pickText(record, ['col$'], record.col ?? '-'),
+    levText: pickText(record, ['lev$'], record.lev ?? '-'),
+    statusText: pickText(record, ['status$'], statusMeta.label),
+    statusTagType: statusMeta.tagType,
+    createByText: pickText(record, ['createBy$']),
+    createTimeText: pickText(record, ['createTime$'], record.createTime || '-'),
+    updateByText: pickText(record, ['updateBy$']),
+    updateTimeText: pickText(record, ['updateTime$'], record.updateTime || '-'),
+    memo: pickText(record, ['memo'], '-')
+  }
+}
diff --git a/rsf-design/src/views/manager/revise-log/reviseLogTable.columns.js b/rsf-design/src/views/manager/revise-log/reviseLogTable.columns.js
new file mode 100644
index 0000000..bc41d5f
--- /dev/null
+++ b/rsf-design/src/views/manager/revise-log/reviseLogTable.columns.js
@@ -0,0 +1,127 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createReviseLogTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'reviseCode',
+      label: '璋冩暣鍗曞彿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'warehouseLabel',
+      label: '浠撳簱',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'areaLabel',
+      label: '搴撳尯',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeLabel',
+      label: '璋冩暣绫诲瀷',
+      minWidth: 110
+    },
+    {
+      prop: 'barcode',
+      label: '瀹瑰櫒鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'useStatusText',
+      label: '搴撲綅鐘舵��',
+      minWidth: 110
+    },
+    {
+      prop: 'channelText',
+      label: '宸烽亾',
+      width: 90,
+      align: 'right'
+    },
+    {
+      prop: 'rowText',
+      label: '鎺�',
+      width: 80,
+      align: 'right'
+    },
+    {
+      prop: 'colText',
+      label: '鍒�',
+      width: 80,
+      align: 'right'
+    },
+    {
+      prop: 'levText',
+      label: '灞�',
+      width: 80,
+      align: 'right'
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(ElTag, { type: row.statusTagType || 'info', effect: 'light' }, () => row.statusText)
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 96,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/stock-item/index.vue b/rsf-design/src/views/manager/stock-item/index.vue
new file mode 100644
index 0000000..163acd7
--- /dev/null
+++ b/rsf-design/src/views/manager/stock-item/index.vue
@@ -0,0 +1,492 @@
+<template>
+  <div class="stock-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板搴撳瓨鏄庣粏</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :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"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      >
+        <template #action="{ row }">
+          <ArtButtonTable icon="ri:eye-line" @click="openDetail(row)" />
+          <ArtButtonTable v-auth="'edit'" icon="ri:pencil-line" @click="openEditDialog(row)" />
+          <ArtButtonTable v-auth="'delete'" icon="ri:delete-bin-5-line" @click="handleDeleteAction(row)" />
+        </template>
+      </ArtTable>
+    </ElCard>
+
+    <StockItemDialog
+      v-model:visible="dialogVisible"
+      :stock-item-data="currentStockItemData"
+      :loading="dialogSubmitting"
+      @submit="handleDialogSubmit"
+    />
+
+    <StockItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail-data="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+  import {
+    fetchDeleteStockItem,
+    fetchExportStockItemReport,
+    fetchStockItemDetail,
+    fetchStockItemMany,
+    fetchSaveStockItem,
+    fetchStockItemPage,
+    fetchUpdateStockItem
+  } from '@/api/stock-item'
+  import StockItemDetailDrawer from './modules/stock-item-detail-drawer.vue'
+  import StockItemDialog from './modules/stock-item-dialog.vue'
+  import { createStockItemTableColumns } from './stockItemTable.columns.js'
+  import {
+    buildStockItemDialogModel,
+    buildStockItemPageQueryParams,
+    buildStockItemPrintRows,
+    buildStockItemReportMeta,
+    buildStockItemSavePayload,
+    buildStockItemSearchParams,
+    createStockItemSearchState,
+    getStockItemPaginationKey,
+    normalizeStockItemRow,
+    STOCK_ITEM_REPORT_STYLE,
+    STOCK_ITEM_REPORT_TITLE
+  } from './stockItemPage.helpers.js'
+
+  defineOptions({ name: 'StockItem' })
+
+  const userStore = useUserStore()
+  const reportTitle = STOCK_ITEM_REPORT_TITLE
+  const searchForm = ref(createStockItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const dialogSubmitting = ref(false)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ富鍗曠紪鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О'
+      }
+    },
+    {
+      label: '涓诲崟缂栧彿',
+      key: 'stockCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ富鍗曠紪鍙�'
+      }
+    },
+    {
+      label: '涓诲崟ID',
+      key: 'stockId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ富鍗旾D'
+      }
+    },
+    {
+      label: '鏄庣粏ID',
+      key: 'sourceItemId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ槑缁咺D'
+      }
+    },
+    {
+      label: '鐗╂枡ID',
+      key: 'matnrId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ墿鏂橧D'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '閫佽揣鏁伴噺',
+      key: 'anfme',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ�佽揣鏁伴噺'
+      }
+    },
+    {
+      label: '搴撳瓨鍗曚綅',
+      key: 'stockUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�'
+      }
+    },
+    {
+      label: '鎵ц涓暟閲�',
+      key: 'workQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ墽琛屼腑鏁伴噺'
+      }
+    },
+    {
+      label: '閲囪喘鏁伴噺',
+      key: 'purQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ噰璐暟閲�'
+      }
+    },
+    {
+      label: '閲囪喘鍗曚綅',
+      key: 'purUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ噰璐崟浣�'
+      }
+    },
+    {
+      label: '宸叉敹鏁伴噺',
+      key: 'qty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ凡鏀舵暟閲�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗙紪鐮�',
+      key: 'splrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜'
+      }
+    },
+    {
+      label: '搴撳瓨鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樻壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗗悕绉�',
+      key: 'splrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О'
+      }
+    },
+    {
+      label: '璺熻釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ窡韪爜'
+      }
+    },
+    {
+      label: '鏉″舰鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯褰㈢爜'
+      }
+    },
+    {
+      label: '鐢熶骇鏃ユ湡',
+      key: 'prodTime',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ敓浜ф棩鏈�'
+      }
+    },
+    {
+      label: '鍖呰鍚嶇О',
+      key: 'packName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ寘瑁呭悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    }
+  ])
+
+  const reportQueryParams = computed(() => buildStockItemSearchParams(searchForm.value))
+
+  let handleDeleteActionImpl = null
+  let openEditDialogActionImpl = null
+
+  const handleDeleteAction = (row) => handleDeleteActionImpl?.(row)
+  const openEditDialog = (row) => openEditDialogActionImpl?.(row)
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData, refreshCreate, refreshUpdate, refreshRemove } =
+    useTable({
+      core: {
+        apiFn: fetchStockItemPage,
+        apiParams: buildStockItemPageQueryParams(searchForm.value),
+        paginationKey: getStockItemPaginationKey(),
+        columnsFactory: () =>
+          createStockItemTableColumns({
+            handleView: openDetail,
+            handleEdit: openEditDialog,
+            handleDelete: handleDeleteAction
+          })
+      },
+      transform: {
+        dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeStockItemRow(item)) : [])
+      }
+    })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentStockItemData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDelete,
+    handleBatchDelete,
+    handleDialogSubmit: crudHandleDialogSubmit
+  } = useCrudPage({
+    createEmptyModel: () => buildStockItemDialogModel(),
+    buildEditModel: (record) => buildStockItemDialogModel(record),
+    buildSavePayload: (formData) => buildStockItemSavePayload(formData),
+    saveRequest: fetchSaveStockItem,
+    updateRequest: fetchUpdateStockItem,
+    deleteRequest: fetchDeleteStockItem,
+    entityName: '搴撳瓨鏄庣粏',
+    resolveRecordLabel: (record) => record?.stockCode || record?.maktx || record?.matnrCode || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+
+  handleDeleteActionImpl = handleDelete
+  openEditDialogActionImpl = (row) => showDialog('edit', row)
+
+  async function handleDialogSubmit(formData) {
+    dialogSubmitting.value = true
+    try {
+      await crudHandleDialogSubmit(formData)
+    } finally {
+      dialogSubmitting.value = false
+    }
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeStockItemRow(
+        await guardRequestWithMessage(fetchStockItemDetail(row.id), {}, {
+          timeoutMessage: '搴撳瓨鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        })
+      )
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳瓨鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildStockItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createStockItemSearchState())
+    resetSearchParams()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'stock-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportStockItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchStockItemMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchStockItemPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildStockItemPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...STOCK_ITEM_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildStockItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || STOCK_ITEM_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(() => {
+    getData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/stock-item/modules/stock-item-detail-drawer.vue b/rsf-design/src/views/manager/stock-item/modules/stock-item-detail-drawer.vue
new file mode 100644
index 0000000..a597ddb
--- /dev/null
+++ b/rsf-design/src/views/manager/stock-item/modules/stock-item-detail-drawer.vue
@@ -0,0 +1,82 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨鏄庣粏璇︽儏"
+    size="72%"
+    destroy-on-close
+    append-to-body
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElSkeleton :loading="loading" animated :rows="10">
+        <div class="flex min-h-full flex-col gap-4 pr-2">
+          <ElDescriptions :column="3" border>
+            <ElDescriptionsItem label="涓诲崟ID">{{ displayData.stockId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="涓诲崟缂栧彿">{{ displayData.stockCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏄庣粏ID">{{ displayData.sourceItemId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡ID">{{ displayData.matnrId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ displayData.matnrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ displayData.maktx || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閫佽揣鏁伴噺">{{ displayData.anfme ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="搴撳瓨鍗曚綅">{{ displayData.stockUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵ц涓暟閲�">{{ displayData.workQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閲囪喘鏁伴噺">{{ displayData.purQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閲囪喘鍗曚綅">{{ displayData.purUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ displayData.qty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ displayData.splrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟咺D">{{ displayData.splrId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ displayData.splrName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="搴撳瓨鎵规">{{ displayData.batch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ displayData.splrBatch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璺熻釜鐮�">{{ displayData.trackCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉″舰鐮�">{{ displayData.barcode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐢熶骇鏃ユ湡">{{ displayData.prodTime || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍖呰鍚嶇О">{{ displayData.packName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瀛楁绱㈠紩">{{ displayData.fieldsIndex || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="骞冲彴琛屽彿">{{ displayData.platItemId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ displayData.platOrderCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸ュ崟鍙�">{{ displayData.platWorkCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="椤圭洰鍙�">{{ displayData.projectCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍩烘湰鍗曚綅">{{ displayData.baseUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浣跨敤缁勭粐ID">{{ displayData.useOrgId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浣跨敤缁勭粐鍚嶇О">{{ displayData.useOrgName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏁伴噺灞炴��">{{ displayData.erpClsId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璁′环鍗曚綅">{{ displayData.priceUnitId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍏ュ簱绫诲瀷">{{ displayData.inStockType || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璐т富绫诲瀷">{{ displayData.ownerType || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">
+              <ElTag :type="displayData.statusTagType || 'info'" effect="light">
+                {{ displayData.statusText || '--' }}
+              </ElTag>
+            </ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="淇敼浜�">{{ displayData.updateByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="淇敼鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="3">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </div>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import { normalizeStockItemRow } from '../stockItemPage.helpers.js'
+
+  defineOptions({ name: 'StockItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeStockItemRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/stock-item/modules/stock-item-dialog.vue b/rsf-design/src/views/manager/stock-item/modules/stock-item-dialog.vue
new file mode 100644
index 0000000..8630afd
--- /dev/null
+++ b/rsf-design/src/views/manager/stock-item/modules/stock-item-dialog.vue
@@ -0,0 +1,450 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="1040px"
+    align-center
+    destroy-on-close
+    append-to-body
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ElScrollbar class="max-h-[calc(100vh-220px)] pr-2">
+      <ArtForm
+        ref="formRef"
+        v-model="form"
+        :items="formItems"
+        :rules="rules"
+        :span="12"
+        :gutter="20"
+        label-width="110px"
+        :show-reset="false"
+        :show-submit="false"
+      />
+    </ElScrollbar>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" :loading="loading" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildStockItemDialogModel,
+    createStockItemFormState,
+    getStockItemStatusOptions
+  } from '../stockItemPage.helpers.js'
+
+  defineOptions({ name: 'StockItemDialog' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    stockItemData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createStockItemFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫搴撳瓨鏄庣粏' : '鏂板搴撳瓨鏄庣粏'))
+
+  const rules = computed(() => ({
+    stockCode: [{ required: true, message: '璇疯緭鍏ヤ富鍗曠紪鍙�', trigger: 'blur' }],
+    matnrCode: [{ required: true, message: '璇疯緭鍏ョ墿鏂欑紪鐮�', trigger: 'blur' }],
+    maktx: [{ required: true, message: '璇疯緭鍏ョ墿鏂欏悕绉�', trigger: 'blur' }],
+    status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: 'ID',
+      key: 'id',
+      type: 'input',
+      props: {
+        disabled: true,
+        placeholder: '鏂板鍚庤嚜鍔ㄧ敓鎴�'
+      }
+    },
+    {
+      label: '涓诲崟ID',
+      key: 'stockId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ富鍗旾D'
+      }
+    },
+    {
+      label: '涓诲崟缂栧彿',
+      key: 'stockCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ富鍗曠紪鍙�'
+      }
+    },
+    {
+      label: '鏄庣粏ID',
+      key: 'sourceItemId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ槑缁咺D'
+      }
+    },
+    {
+      label: '鐗╂枡ID',
+      key: 'matnrId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ墿鏂橧D'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '閫佽揣鏁伴噺',
+      key: 'anfme',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ�佽揣鏁伴噺'
+      }
+    },
+    {
+      label: '搴撳瓨鍗曚綅',
+      key: 'stockUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�'
+      }
+    },
+    {
+      label: '鎵ц涓暟閲�',
+      key: 'workQty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ墽琛屼腑鏁伴噺'
+      }
+    },
+    {
+      label: '閲囪喘鏁伴噺',
+      key: 'purQty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ラ噰璐暟閲�'
+      }
+    },
+    {
+      label: '閲囪喘鍗曚綅',
+      key: 'purUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ噰璐崟浣�'
+      }
+    },
+    {
+      label: '宸叉敹鏁伴噺',
+      key: 'qty',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ凡鏀舵暟閲�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗙紪鐮�',
+      key: 'splrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜'
+      }
+    },
+    {
+      label: '渚涘簲鍟咺D',
+      key: 'splrId',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢ID'
+      }
+    },
+    {
+      label: '骞冲彴琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙拌鍙�'
+      }
+    },
+    {
+      label: '瀹㈡埛璁㈠崟鍙�',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴疯鍗曞彿'
+      }
+    },
+    {
+      label: '宸ュ崟鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿'
+      }
+    },
+    {
+      label: '椤圭洰鍙�',
+      key: 'projectCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩彿'
+      }
+    },
+    {
+      label: '搴撳瓨鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樻壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗗悕绉�',
+      key: 'splrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О'
+      }
+    },
+    {
+      label: '璺熻釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ窡韪爜'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    },
+    {
+      label: '鏉″舰鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯褰㈢爜'
+      }
+    },
+    {
+      label: '鐢熶骇鏃ユ湡',
+      key: 'prodTime',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ敓浜ф棩鏈�'
+      }
+    },
+    {
+      label: '鍖呰鍚嶇О',
+      key: 'packName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ寘瑁呭悕绉�'
+      }
+    },
+    {
+      label: '鍩烘湰鍗曚綅',
+      key: 'baseUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ熀鏈崟浣�'
+      }
+    },
+    {
+      label: '浣跨敤缁勭粐ID',
+      key: 'useOrgId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ娇鐢ㄧ粍缁嘔D'
+      }
+    },
+    {
+      label: '浣跨敤缁勭粐鍚嶇О',
+      key: 'useOrgName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ娇鐢ㄧ粍缁囧悕绉�'
+      }
+    },
+    {
+      label: '鏁伴噺灞炴��',
+      key: 'erpClsId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ暟閲忓睘鎬�'
+      }
+    },
+    {
+      label: '璁′环鍗曚綅',
+      key: 'priceUnitId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ浠峰崟浣�'
+      }
+    },
+    {
+      label: '鍏ュ簱绫诲瀷',
+      key: 'inStockType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ叆搴撶被鍨�'
+      }
+    },
+    {
+      label: '璐т富绫诲瀷',
+      key: 'ownerType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ揣涓荤被鍨�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getStockItemStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createStockItemFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildStockItemDialogModel(props.stockItemData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.stockItemData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/manager/stock-item/stockItemPage.helpers.js b/rsf-design/src/views/manager/stock-item/stockItemPage.helpers.js
new file mode 100644
index 0000000..6060078
--- /dev/null
+++ b/rsf-design/src/views/manager/stock-item/stockItemPage.helpers.js
@@ -0,0 +1,342 @@
+export const STOCK_ITEM_REPORT_TITLE = '搴撳瓨鏄庣粏鎶ヨ〃'
+export const STOCK_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const STOCK_ITEM_STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'danger' }
+}
+
+const TEXT_FIELDS = [
+  'condition',
+  'stockCode',
+  'matnrCode',
+  'maktx',
+  'stockUnit',
+  'purUnit',
+  'splrCode',
+  'batch',
+  'splrBatch',
+  'splrName',
+  'trackCode',
+  'barcode',
+  'prodTime',
+  'packName',
+  'memo'
+]
+
+const NUMBER_FIELDS = ['stockId', 'sourceItemId', 'matnrId', 'anfme', 'workQty', 'purQty', 'qty', 'status']
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function pushText(target, key, value) {
+  const normalizedValue = normalizeText(value)
+  if (normalizedValue) {
+    target[key] = normalizedValue
+  }
+}
+
+function pushNumber(target, key, value) {
+  const normalizedValue = normalizeNumber(value)
+  if (normalizedValue !== null) {
+    target[key] = normalizedValue
+  }
+}
+
+function getStatusMeta(status, statusText) {
+  const numericStatus = Number(status)
+  return STOCK_ITEM_STATUS_META[numericStatus] || {
+    text: normalizeText(statusText || status || '--') || '--',
+    type: 'info'
+  }
+}
+
+export function createStockItemSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    stockId: '',
+    stockCode: '',
+    sourceItemId: '',
+    matnrId: '',
+    matnrCode: '',
+    maktx: '',
+    anfme: '',
+    stockUnit: '',
+    workQty: '',
+    purQty: '',
+    purUnit: '',
+    qty: '',
+    splrCode: '',
+    batch: '',
+    splrBatch: '',
+    splrName: '',
+    trackCode: '',
+    barcode: '',
+    prodTime: '',
+    packName: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function createStockItemFormState() {
+  return {
+    id: null,
+    stockId: '',
+    stockCode: '',
+    sourceItemId: '',
+    matnrId: '',
+    matnrCode: '',
+    maktx: '',
+    anfme: '',
+    stockUnit: '',
+    workQty: '',
+    purQty: '',
+    purUnit: '',
+    qty: '',
+    splrCode: '',
+    splrId: '',
+    platItemId: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    batch: '',
+    splrBatch: '',
+    splrName: '',
+    trackCode: '',
+    fieldsIndex: '',
+    barcode: '',
+    prodTime: '',
+    packName: '',
+    baseUnit: '',
+    useOrgId: '',
+    useOrgName: '',
+    erpClsId: '',
+    priceUnitId: '',
+    inStockType: '',
+    ownerType: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getStockItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getStockItemStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function buildStockItemSearchParams(params = {}) {
+  const result = {}
+
+  TEXT_FIELDS.forEach((key) => pushText(result, key, params[key]))
+  NUMBER_FIELDS.forEach((key) => pushNumber(result, key, params[key]))
+
+  if (params.timeStart) {
+    result.timeStart = normalizeText(params.timeStart)
+  }
+  if (params.timeEnd) {
+    result.timeEnd = normalizeText(params.timeEnd)
+  }
+
+  return result
+}
+
+export function buildStockItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildStockItemSearchParams(params)
+  }
+}
+
+export function buildStockItemSavePayload(formData = {}) {
+  const payload = {}
+
+  pushNumber(payload, 'id', formData.id)
+  pushNumber(payload, 'stockId', formData.stockId)
+  pushNumber(payload, 'sourceItemId', formData.sourceItemId)
+  pushNumber(payload, 'matnrId', formData.matnrId)
+  pushNumber(payload, 'anfme', formData.anfme)
+  pushNumber(payload, 'workQty', formData.workQty)
+  pushNumber(payload, 'purQty', formData.purQty)
+  pushNumber(payload, 'qty', formData.qty)
+  pushNumber(payload, 'splrId', formData.splrId)
+  pushNumber(payload, 'status', formData.status ?? 1)
+
+  ;[
+    'stockCode',
+    'matnrCode',
+    'maktx',
+    'stockUnit',
+    'purUnit',
+    'splrCode',
+    'platItemId',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'batch',
+    'splrBatch',
+    'splrName',
+    'trackCode',
+    'fieldsIndex',
+    'barcode',
+    'prodTime',
+    'packName',
+    'baseUnit',
+    'useOrgId',
+    'useOrgName',
+    'erpClsId',
+    'priceUnitId',
+    'inStockType',
+    'ownerType',
+    'memo'
+  ].forEach((key) => pushText(payload, key, formData[key]))
+
+  return payload
+}
+
+export function buildStockItemDialogModel(record = {}) {
+  return {
+    ...createStockItemFormState(),
+    id: record.id ?? null,
+    stockId: record.stockId ?? '',
+    stockCode: normalizeText(record.stockCode),
+    sourceItemId: record.sourceItemId ?? '',
+    matnrId: record.matnrId ?? '',
+    matnrCode: normalizeText(record.matnrCode),
+    maktx: normalizeText(record.maktx),
+    anfme: record.anfme ?? '',
+    stockUnit: normalizeText(record.stockUnit),
+    workQty: record.workQty ?? '',
+    purQty: record.purQty ?? '',
+    purUnit: normalizeText(record.purUnit),
+    qty: record.qty ?? '',
+    splrCode: normalizeText(record.splrCode),
+    splrId: record.splrId ?? '',
+    platItemId: normalizeText(record.platItemId),
+    platOrderCode: normalizeText(record.platOrderCode),
+    platWorkCode: normalizeText(record.platWorkCode),
+    projectCode: normalizeText(record.projectCode),
+    batch: normalizeText(record.batch),
+    splrBatch: normalizeText(record.splrBatch),
+    splrName: normalizeText(record.splrName),
+    trackCode: normalizeText(record.trackCode),
+    fieldsIndex: normalizeText(record.fieldsIndex),
+    barcode: normalizeText(record.barcode),
+    prodTime: normalizeText(record.prodTime),
+    packName: normalizeText(record.packName),
+    baseUnit: normalizeText(record.baseUnit),
+    useOrgId: normalizeText(record.useOrgId),
+    useOrgName: normalizeText(record.useOrgName),
+    erpClsId: normalizeText(record.erpClsId),
+    priceUnitId: normalizeText(record.priceUnitId),
+    inStockType: normalizeText(record.inStockType),
+    ownerType: normalizeText(record.ownerType),
+    status: record.status ?? 1,
+    memo: normalizeText(record.memo)
+  }
+}
+
+export function getStockItemStatusMeta(status, statusText) {
+  return getStatusMeta(status, statusText)
+}
+
+export function normalizeStockItemRow(record = {}) {
+  const statusMeta = getStatusMeta(record.status, record['status$'] || record.statusText)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    stockId: record.stockId ?? '--',
+    stockCode: normalizeText(record.stockCode) || '--',
+    sourceItemId: record.sourceItemId ?? '--',
+    matnrId: record.matnrId ?? '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    maktx: normalizeText(record.maktx) || '--',
+    anfme: Number.isFinite(Number(record.anfme)) ? Number(record.anfme) : 0,
+    stockUnit: normalizeText(record.stockUnit) || '--',
+    workQty: Number.isFinite(Number(record.workQty)) ? Number(record.workQty) : 0,
+    purQty: Number.isFinite(Number(record.purQty)) ? Number(record.purQty) : 0,
+    purUnit: normalizeText(record.purUnit) || '--',
+    qty: Number.isFinite(Number(record.qty)) ? Number(record.qty) : 0,
+    splrCode: normalizeText(record.splrCode) || '--',
+    splrId: record.splrId ?? '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    platOrderCode: normalizeText(record.platOrderCode) || '--',
+    platWorkCode: normalizeText(record.platWorkCode) || '--',
+    projectCode: normalizeText(record.projectCode) || '--',
+    batch: normalizeText(record.batch) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    splrName: normalizeText(record.splrName) || '--',
+    trackCode: normalizeText(record.trackCode) || '--',
+    fieldsIndex: normalizeText(record.fieldsIndex) || '--',
+    barcode: normalizeText(record.barcode) || '--',
+    prodTime: normalizeText(record.prodTime) || '--',
+    packName: normalizeText(record.packName) || '--',
+    baseUnit: normalizeText(record.baseUnit) || '--',
+    useOrgId: normalizeText(record.useOrgId) || '--',
+    useOrgName: normalizeText(record.useOrgName) || '--',
+    erpClsId: normalizeText(record.erpClsId) || '--',
+    priceUnitId: normalizeText(record.priceUnitId) || '--',
+    inStockType: normalizeText(record.inStockType) || '--',
+    ownerType: normalizeText(record.ownerType) || '--',
+    statusText: statusMeta.text,
+    statusTagType: statusMeta.type,
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildStockItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeStockItemRow(record))
+}
+
+export function buildStockItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = STOCK_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: STOCK_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...STOCK_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/manager/stock-item/stockItemTable.columns.js b/rsf-design/src/views/manager/stock-item/stockItemTable.columns.js
new file mode 100644
index 0000000..4f5dca9
--- /dev/null
+++ b/rsf-design/src/views/manager/stock-item/stockItemTable.columns.js
@@ -0,0 +1,177 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+function renderStatus(row) {
+  return h(
+    ElTag,
+    {
+      type: row.statusTagType || 'info',
+      effect: 'light'
+    },
+    () => row.statusText || '--'
+  )
+}
+
+export function createStockItemTableColumns({ handleView, handleEdit, handleDelete } = {}) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'stockCode',
+      label: '涓诲崟缂栧彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockId',
+      label: '涓诲崟ID',
+      width: 100
+    },
+    {
+      prop: 'sourceItemId',
+      label: '鏄庣粏ID',
+      width: 100
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '閫佽揣鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'stockUnit',
+      label: '搴撳瓨鍗曚綅',
+      width: 100,
+      align: 'center'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'purQty',
+      label: '閲囪喘鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'purUnit',
+      label: '閲囪喘鍗曚綅',
+      width: 100,
+      align: 'center'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '搴撳瓨鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鏉″舰鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'prodTime',
+      label: '鐢熶骇鏃ユ湡',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'packName',
+      label: '鍖呰鍚嶇О',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 92,
+      align: 'center',
+      formatter: renderStatus
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'action',
+      label: '鎿嶄綔',
+      width: 132,
+      fixed: 'right',
+      align: 'center',
+      useSlot: true
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/manager/stock/index.vue b/rsf-design/src/views/manager/stock/index.vue
new file mode 100644
index 0000000..8639bed
--- /dev/null
+++ b/rsf-design/src/views/manager/stock/index.vue
@@ -0,0 +1,278 @@
+<template>
+  <div class="stock-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData">
+        <template #left>
+          <ElSpace wrap>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :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"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <StockDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref, reactive } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { fetchExportStockReport, fetchGetStockDetail, fetchGetStockMany, fetchStockPage } from '@/api/stock'
+  import StockDetailDrawer from './modules/stock-detail-drawer.vue'
+  import { createStockTableColumns } from './stockTable.columns'
+  import {
+    buildStockPageQueryParams,
+    buildStockPrintRows,
+    buildStockReportMeta,
+    buildStockSearchParams,
+    createStockSearchState,
+    normalizeStockRow,
+    STOCK_REPORT_STYLE,
+    STOCK_REPORT_TITLE
+  } from './stockPage.helpers'
+
+  defineOptions({ name: 'Stock' })
+
+  const userStore = useUserStore()
+  const reportTitle = STOCK_REPORT_TITLE
+  const loading = ref(false)
+  const tableData = ref([])
+  const selectedRows = ref([])
+  const searchForm = ref(createStockSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildStockSearchParams(searchForm.value))
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ崟鎹紪鍙�/鏉ユ簮鍗曞彿' }
+    },
+    {
+      label: '鍗曟嵁缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ崟鎹紪鍙�' }
+    },
+    {
+      label: '鏉ユ簮鍗曞彿',
+      key: 'sourceCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ潵婧愬崟鍙�' }
+    },
+    {
+      label: '鏉ユ簮鍗旾D',
+      key: 'sourceId',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ潵婧愬崟ID' }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'type',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�' }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'wkType',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ崟鎹被鍨�' }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'date',
+      props: { type: 'date', valueFormat: 'YYYY-MM-DD', placeholder: '璇烽�夋嫨寮�濮嬫椂闂�' }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'date',
+      props: { type: 'date', valueFormat: 'YYYY-MM-DD', placeholder: '璇烽�夋嫨缁撴潫鏃堕棿' }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeStockRow(await fetchGetStockDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鍏ュ嚭搴撳巻鍙茶鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  const { columns, columnChecks } = useTableColumns(() =>
+    createStockTableColumns({
+      handleView: openDetail
+    })
+  )
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchStockPage(
+          buildStockPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        { records: [], total: 0, current: pagination.current, size: pagination.size },
+        { timeoutMessage: '鍏ュ嚭搴撳巻鍙插姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeStockRow(record))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createStockSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'stock-history.xlsx',
+    requestExport: (payload) =>
+      fetchExportStockReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetStockMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchStockPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildStockPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...STOCK_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildStockReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || STOCK_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(() => {
+    loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/manager/stock/modules/stock-detail-drawer.vue b/rsf-design/src/views/manager/stock/modules/stock-detail-drawer.vue
new file mode 100644
index 0000000..1fda184
--- /dev/null
+++ b/rsf-design/src/views/manager/stock/modules/stock-detail-drawer.vue
@@ -0,0 +1,36 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍏ュ嚭搴撳巻鍙茶鎯�"
+    size="560px"
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="鍗曟嵁缂栧彿">{{ detailData.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏉ユ簮鍗曞彿">{{ detailData.sourceCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏉ユ簮鍗旾D">{{ detailData.sourceId ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detailData.locCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏉$爜">{{ detailData.barcode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detailData.typeLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detailData.wkTypeLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏁伴噺">{{ detailData.anfme ?? 0 }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detailData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detailData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ detailData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'StockDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+</script>
diff --git a/rsf-design/src/views/manager/stock/stockPage.helpers.js b/rsf-design/src/views/manager/stock/stockPage.helpers.js
new file mode 100644
index 0000000..e3ab87e
--- /dev/null
+++ b/rsf-design/src/views/manager/stock/stockPage.helpers.js
@@ -0,0 +1,103 @@
+export const STOCK_REPORT_TITLE = '鍏ュ嚭搴撳巻鍙叉姤琛�'
+export const STOCK_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createStockSearchState() {
+  return {
+    condition: '',
+    code: '',
+    sourceCode: '',
+    sourceId: '',
+    type: '',
+    wkType: '',
+    locCode: '',
+    barcode: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildStockSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'sourceCode', 'type', 'wkType', 'locCode', 'barcode', 'timeStart', 'timeEnd'].forEach(
+    (key) => {
+      const value = normalizeText(params[key])
+      if (value) {
+        result[key] = value
+      }
+    }
+  )
+  if (params.sourceId !== '' && params.sourceId !== null && params.sourceId !== undefined) {
+    result.sourceId = normalizeNumber(params.sourceId)
+  }
+  return result
+}
+
+export function buildStockPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildStockSearchParams(params)
+  }
+}
+
+export function normalizeStockRow(record = {}) {
+  return {
+    ...record,
+    code: record.code || '-',
+    sourceCode: record.sourceCode || '-',
+    sourceId: record.sourceId ?? '-',
+    locCode: record.locCode || '-',
+    barcode: record.barcode || '-',
+    typeLabel: record['type$'] || record.type || '-',
+    wkTypeLabel: record['wkType$'] || record.wkType || '-',
+    anfme: normalizeNumber(record.anfme),
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    memo: record.memo || '-'
+  }
+}
+
+export function buildStockPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeStockRow(record))
+}
+
+export function buildStockReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = STOCK_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: STOCK_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...STOCK_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/manager/stock/stockTable.columns.js b/rsf-design/src/views/manager/stock/stockTable.columns.js
new file mode 100644
index 0000000..49a0f70
--- /dev/null
+++ b/rsf-design/src/views/manager/stock/stockTable.columns.js
@@ -0,0 +1,86 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createStockTableColumns({ handleView }) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: '鍗曟嵁缂栧彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'sourceCode',
+      label: '鏉ユ簮鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'sourceId',
+      label: '鏉ユ簮鍗旾D',
+      width: 100
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鏉$爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 110
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      fixed: 'right',
+      align: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/task-item-log/index.vue b/rsf-design/src/views/manager/task-item-log/index.vue
new file mode 100644
index 0000000..9ed97b5
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item-log/index.vue
@@ -0,0 +1,389 @@
+<template>
+  <div class="task-item-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskItemLogDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportTaskItemLogReport,
+    fetchGetTaskItemLogDetail,
+    fetchGetTaskItemLogMany,
+    fetchTaskItemLogPage
+  } from '@/api/task-item-log'
+  import {
+    buildTaskItemLogPageQueryParams,
+    buildTaskItemLogPrintRows,
+    buildTaskItemLogSearchParams,
+    createTaskItemLogSearchState,
+    getTaskItemLogPaginationKey,
+    getTaskItemLogReportColumns,
+    normalizeTaskItemLogRow,
+    TASK_ITEM_LOG_REPORT_TITLE
+  } from './taskItemLogPage.helpers'
+  import { createTaskItemLogTableColumns } from './taskItemLogTable.columns'
+  import TaskItemLogDetailDrawer from './modules/task-item-log-detail-drawer.vue'
+
+  defineOptions({ name: 'TaskItemLog' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createTaskItemLogSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = TASK_ITEM_LOG_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskItemLogSearchParams(searchForm.value))
+  const reportColumns = getTaskItemLogReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂�/浠诲姟/鍗曞彿鍏抽敭璇�'
+      }
+    },
+    {
+      label: '涓诲崟ID',
+      key: 'logId',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ富鍗旾D'
+      }
+    },
+    {
+      label: '浠诲姟ID',
+      key: 'taskId',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔D'
+      }
+    },
+    {
+      label: '浠诲姟鏄庣粏ID',
+      key: 'taskItemId',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔℃槑缁咺D'
+      }
+    },
+    {
+      label: '鐗╂枡ID',
+      key: 'matnrId',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂橧D'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '宸ュ崟鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿'
+      }
+    },
+    {
+      label: '瀹㈡埛璁㈠崟鍙�',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴疯鍗曞彿'
+      }
+    },
+    {
+      label: '椤圭洰鍙�',
+      key: 'projectCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩彿'
+      }
+    },
+    {
+      label: '婧愮紪鐮�',
+      key: 'source',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ簮缂栫爜'
+      }
+    },
+    {
+      label: '婧愬崟鍙�',
+      key: 'sourceCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ簮鍗曞彿'
+      }
+    },
+    {
+      label: '婧愪富鍗旾D',
+      key: 'sourceId',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ簮涓诲崟ID'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '搴撳瓨鍗曚綅',
+      key: 'unit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�'
+      }
+    },
+    {
+      label: '鏁伴噺',
+      key: 'anfme',
+      type: 'number',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ暟閲�'
+      }
+    },
+    {
+      label: '搴撳瓨鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樻壒娆�'
+      }
+    },
+    {
+      label: '瑙勬牸',
+      key: 'spec',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鏍�'
+      }
+    },
+    {
+      label: '鍨嬪彿',
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瀷鍙�'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTaskItemLogPage,
+      apiParams: buildTaskItemLogPageQueryParams(searchForm.value),
+      paginationKey: getTaskItemLogPaginationKey(),
+      columnsFactory: () => createTaskItemLogTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeTaskItemLogRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskItemLogMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskItemLogPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'task-item-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportTaskItemLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTaskItemLogPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetTaskItemLogDetail(id)
+    detailData.value = normalizeTaskItemLogRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskItemLogSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskItemLogSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-item-log/modules/task-item-log-detail-drawer.vue b/rsf-design/src/views/manager/task-item-log/modules/task-item-log-detail-drawer.vue
new file mode 100644
index 0000000..45a7dfa
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item-log/modules/task-item-log-detail-drawer.vue
@@ -0,0 +1,72 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟宸ヤ綔鍘嗗彶妗h鎯�"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="涓诲崟ID">{{ detail.logId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟ID">{{ detail.taskId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鏄庣粏ID">{{ detail.taskItemId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡ID">{{ detail.matnrId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸ュ崟鍙�">{{ detail.platWorkCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ detail.platOrderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="椤圭洰鍙�">{{ detail.projectCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愮紪鐮�">{{ detail.source ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愬崟鍙�">{{ detail.sourceCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愪富鍗旾D">{{ detail.sourceId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀛楁绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍩烘湰鍗曚綅">{{ detail.baseUnit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浣跨敤缁勭粐ID">{{ detail.useOrgId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浣跨敤缁勭粐">{{ detail.useOrgName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺灞炴��">{{ detail.erpClsId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁′环鍗曚綅">{{ detail.priceUnitId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍏ュ簱绫诲瀷">{{ detail.inStockType || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐т富绫诲瀷">{{ detail.ownerTypeId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐т富ID">{{ detail.ownerId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐т富鍚嶇О">{{ detail.ownerName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="淇濈鑰呯被鍨�">{{ detail.keeperTypeId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="淇濈鑰匢D">{{ detail.keeperId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="淇濈鑰呭悕绉�">{{ detail.keeperName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寤鸿鐩爣浠�">{{ detail.targetWarehouseId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寤鸿璋冨嚭浠�">{{ detail.sourceWarehouseId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'TaskItemLogDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-item-log/taskItemLogPage.helpers.js b/rsf-design/src/views/manager/task-item-log/taskItemLogPage.helpers.js
new file mode 100644
index 0000000..6438952
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item-log/taskItemLogPage.helpers.js
@@ -0,0 +1,249 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const TASK_ITEM_LOG_REPORT_TITLE = '浠诲姟宸ヤ綔鍘嗗彶妗f姤琛�'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeReportText(value) {
+  return value === null || value === undefined || value === '' ? '--' : value
+}
+
+export function createTaskItemLogSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    logId: '',
+    taskId: '',
+    taskItemId: '',
+    matnrId: '',
+    maktx: '',
+    platItemId: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    source: '',
+    sourceCode: '',
+    sourceId: '',
+    matnrCode: '',
+    unit: '',
+    anfme: '',
+    batch: '',
+    spec: '',
+    model: '',
+    fieldsIndex: '',
+    memo: '',
+    status: '',
+    baseUnit: '',
+    useOrgId: '',
+    useOrgName: '',
+    erpClsId: '',
+    priceUnitId: '',
+    inStockType: '',
+    ownerTypeId: '',
+    ownerId: '',
+    ownerName: '',
+    keeperTypeId: '',
+    keeperId: '',
+    keeperName: '',
+    targetWarehouseId: '',
+    sourceWarehouseId: ''
+  }
+}
+
+export function getTaskItemLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildTaskItemLogSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'maktx',
+    'platItemId',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'sourceCode',
+    'matnrCode',
+    'unit',
+    'batch',
+    'spec',
+    'model',
+    'fieldsIndex',
+    'memo',
+    'baseUnit',
+    'useOrgId',
+    'useOrgName',
+    'erpClsId',
+    'priceUnitId',
+    'inStockType',
+    'ownerTypeId',
+    'ownerId',
+    'ownerName',
+    'keeperTypeId',
+    'keeperId',
+    'keeperName',
+    'targetWarehouseId',
+    'sourceWarehouseId'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['logId', 'taskId', 'taskItemId', 'matnrId', 'source', 'sourceId', 'anfme', 'status'].forEach(
+    (key) => {
+      const value = normalizeNumber(params[key])
+      if (value !== null) {
+        result[key] = value
+      }
+    }
+  )
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildTaskItemLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskItemLogSearchParams(params)
+  }
+}
+
+export function getTaskItemLogStatusMeta(value) {
+  return STATUS_META[Number(value)] || { text: '-', type: 'info' }
+}
+
+export function normalizeTaskItemLogRow(record = {}) {
+  const statusMeta = getTaskItemLogStatusMeta(record.status)
+  return {
+    ...record,
+    logId: record.logId ?? '--',
+    taskId: record.taskId ?? '--',
+    taskItemId: record.taskItemId ?? '--',
+    matnrId: record.matnrId ?? '--',
+    maktx: normalizeReportText(record.maktx),
+    platItemId: normalizeReportText(record.platItemId),
+    platOrderCode: normalizeReportText(record.platOrderCode),
+    platWorkCode: normalizeReportText(record.platWorkCode),
+    projectCode: normalizeReportText(record.projectCode),
+    source: record.source ?? '--',
+    sourceCode: normalizeReportText(record.sourceCode),
+    sourceId: record.sourceId ?? '--',
+    matnrCode: normalizeReportText(record.matnrCode),
+    unit: normalizeReportText(record.unit),
+    anfme: record.anfme ?? '--',
+    batch: normalizeReportText(record.batch),
+    spec: normalizeReportText(record.spec),
+    model: normalizeReportText(record.model),
+    fieldsIndex: normalizeReportText(record.fieldsIndex),
+    baseUnit: normalizeReportText(record.baseUnit),
+    useOrgId: normalizeReportText(record.useOrgId),
+    useOrgName: normalizeReportText(record.useOrgName),
+    erpClsId: normalizeReportText(record.erpClsId),
+    priceUnitId: normalizeReportText(record.priceUnitId),
+    inStockType: normalizeReportText(record.inStockType),
+    ownerTypeId: normalizeReportText(record.ownerTypeId),
+    ownerId: normalizeReportText(record.ownerId),
+    ownerName: normalizeReportText(record.ownerName),
+    keeperTypeId: normalizeReportText(record.keeperTypeId),
+    keeperId: normalizeReportText(record.keeperId),
+    keeperName: normalizeReportText(record.keeperName),
+    targetWarehouseId: normalizeReportText(record.targetWarehouseId),
+    sourceWarehouseId: normalizeReportText(record.sourceWarehouseId),
+    createByText: record['createBy$'] || record.createByText || '--',
+    createTimeText: record['createTime$'] || record.createTime || '--',
+    updateByText: record['updateBy$'] || record.updateByText || '--',
+    updateTimeText: record['updateTime$'] || record.updateTime || '--',
+    memo: normalizeReportText(record.memo),
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type
+  }
+}
+
+export function getTaskItemLogReportColumns() {
+  return [
+    { source: 'taskId', label: '浠诲姟ID' },
+    { source: 'taskItemId', label: '浠诲姟鏄庣粏ID' },
+    { source: 'matnrId', label: '鐗╂枡ID' },
+    { source: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { source: 'platWorkCode', label: '宸ュ崟鍙�' },
+    { source: 'platOrderCode', label: '瀹㈡埛璁㈠崟鍙�' },
+    { source: 'projectCode', label: '椤圭洰鍙�' },
+    { source: 'source', label: '婧愮紪鐮�' },
+    { source: 'sourceCode', label: '婧愬崟鍙�' },
+    { source: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { source: 'unit', label: '搴撳瓨鍗曚綅' },
+    { source: 'anfme', label: '鏁伴噺' },
+    { source: 'batch', label: '搴撳瓨鎵规' },
+    { source: 'spec', label: '瑙勬牸' },
+    { source: 'model', label: '鍨嬪彿' },
+    { source: 'fieldsIndex', label: '瀛楁绱㈠紩' },
+    { source: 'createByText', label: '鍒涘缓浜�' },
+    { source: 'createTimeText', label: '鍒涘缓鏃堕棿' },
+    { source: 'updateByText', label: '鏇存柊浜�' },
+    { source: 'updateTimeText', label: '鏇存柊鏃堕棿' },
+    { source: 'statusText', label: '鐘舵��' },
+    { source: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildTaskItemLogPrintRows(records = []) {
+  return records.map((record) => {
+    const row = normalizeTaskItemLogRow(record)
+    return {
+      taskId: row.taskId,
+      taskItemId: row.taskItemId,
+      matnrId: row.matnrId,
+      maktx: row.maktx,
+      platWorkCode: row.platWorkCode,
+      platOrderCode: row.platOrderCode,
+      projectCode: row.projectCode,
+      source: row.source,
+      sourceCode: row.sourceCode,
+      matnrCode: row.matnrCode,
+      unit: row.unit,
+      anfme: row.anfme,
+      batch: row.batch,
+      spec: row.spec,
+      model: row.model,
+      fieldsIndex: row.fieldsIndex,
+      createByText: row.createByText,
+      createTimeText: row.createTimeText,
+      updateByText: row.updateByText,
+      updateTimeText: row.updateTimeText,
+      statusText: row.statusText,
+      statusType: row.statusType,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/manager/task-item-log/taskItemLogTable.columns.js b/rsf-design/src/views/manager/task-item-log/taskItemLogTable.columns.js
new file mode 100644
index 0000000..c358b4e
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item-log/taskItemLogTable.columns.js
@@ -0,0 +1,164 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTaskItemLogTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'taskId',
+      label: '浠诲姟ID',
+      width: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'taskItemId',
+      label: '浠诲姟鏄庣粏ID',
+      width: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrId',
+      label: '鐗╂枡ID',
+      width: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platWorkCode',
+      label: '宸ュ崟鍙�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platOrderCode',
+      label: '瀹㈡埛璁㈠崟鍙�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'projectCode',
+      label: '椤圭洰鍙�',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'source',
+      label: '婧愮紪鐮�',
+      width: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'sourceCode',
+      label: '婧愬崟鍙�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '搴撳瓨鍗曚綅',
+      width: 100,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right',
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '搴撳瓨鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '瀛楁绱㈠紩',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/task-item/index.vue b/rsf-design/src/views/manager/task-item/index.vue
new file mode 100644
index 0000000..4d41189
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item/index.vue
@@ -0,0 +1,265 @@
+<template>
+  <div class="task-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useRoute } 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 {
+    fetchExportTaskItemReport,
+    fetchGetTaskItemDetail,
+    fetchGetTaskItemMany,
+    fetchTaskItemPage
+  } from '@/api/task-item'
+  import {
+    buildTaskItemPageQueryParams,
+    buildTaskItemPrintRows,
+    buildTaskItemSearchParams,
+    createTaskItemSearchState,
+    getTaskItemPaginationKey,
+    getTaskItemReportColumns,
+    normalizeTaskItemRow,
+    TASK_ITEM_REPORT_TITLE
+  } from './taskItemPage.helpers'
+  import { createTaskItemTableColumns } from './taskItemTable.columns'
+  import TaskItemDetailDrawer from './modules/task-item-detail-drawer.vue'
+
+  defineOptions({ name: 'TaskItem' })
+
+  const route = useRoute()
+  const userStore = useUserStore()
+  const initialTaskId = route.query.taskId ? Number(route.query.taskId) : ''
+  const searchForm = ref(createTaskItemSearchState({ taskId: Number.isFinite(initialTaskId) ? initialTaskId : '' }))
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = TASK_ITEM_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskItemSearchParams(searchForm.value))
+  const reportColumns = getTaskItemReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿/瀹㈡埛璁㈠崟鍙�/鐗╂枡缂栫爜'
+      }
+    },
+    {
+      label: '浠诲姟ID',
+      key: 'taskId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ换鍔D'
+      }
+    },
+    {
+      label: '涓诲崟ID',
+      key: 'orderId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ富鍗旾D'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '宸ュ崟鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿'
+      }
+    },
+    {
+      label: '瀹㈡埛璁㈠崟鍙�',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴疯鍗曞彿'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTaskItemPage,
+      apiParams: buildTaskItemPageQueryParams(searchForm.value),
+      paginationKey: getTaskItemPaginationKey(),
+      columnsFactory: () => createTaskItemTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeTaskItemRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskItemPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'task-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportTaskItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTaskItemPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetTaskItemDetail(id)
+    detailData.value = normalizeTaskItemRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    const resetSeed = route.query.taskId ? { taskId: Number(route.query.taskId) || '' } : {}
+    Object.assign(searchForm.value, createTaskItemSearchState(resetSeed))
+    resetSearchParams(buildTaskItemPageQueryParams(createTaskItemSearchState(resetSeed)))
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-item/modules/task-item-detail-drawer.vue b/rsf-design/src/views/manager/task-item/modules/task-item-detail-drawer.vue
new file mode 100644
index 0000000..1629b82
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item/modules/task-item-detail-drawer.vue
@@ -0,0 +1,56 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟妗f槑缁嗚鎯�"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="浠诲姟ID">{{ detail.taskId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓诲崟ID">{{ detail.orderId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.orderTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鏄庣粏ID">{{ detail.orderItemId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡ID">{{ detail.matnrId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸ュ崟鍙�">{{ detail.platWorkCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ detail.platOrderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="椤圭洰鍙�">{{ detail.projectCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹屾垚鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀛楁绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'TaskItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-item/taskItemPage.helpers.js b/rsf-design/src/views/manager/task-item/taskItemPage.helpers.js
new file mode 100644
index 0000000..941d1b4
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item/taskItemPage.helpers.js
@@ -0,0 +1,203 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'info', bool: false }
+}
+
+export const TASK_ITEM_REPORT_TITLE = '浠诲姟妗f槑缁嗘姤琛�'
+export const TASK_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+export function createTaskItemSearchState(seed = {}) {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    taskId: '',
+    orderId: '',
+    orderType: '',
+    orderItemId: '',
+    matnrId: '',
+    maktx: '',
+    matnrCode: '',
+    unit: '',
+    anfme: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    batch: '',
+    spec: '',
+    model: '',
+    memo: '',
+    status: '',
+    ...seed
+  }
+}
+
+export function getTaskItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildTaskItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'maktx',
+    'matnrCode',
+    'unit',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'batch',
+    'spec',
+    'model',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['taskId', 'orderId', 'orderType', 'orderItemId', 'matnrId', 'anfme', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildTaskItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskItemSearchParams(params)
+  }
+}
+
+function getTaskItemStatusMeta(value) {
+  return STATUS_META[Number(value)] || { text: '-', type: 'info', bool: false }
+}
+
+export function normalizeTaskItemRow(record = {}) {
+  const statusMeta = getTaskItemStatusMeta(record.status)
+  return {
+    ...record,
+    taskId: record.taskId ?? '--',
+    orderId: record.orderId ?? '--',
+    orderTypeText: record['orderType$'] || record.orderTypeText || record.orderType || '--',
+    wkTypeText: record['wkType$'] || record.wkTypeText || record.wkType || '--',
+    orderItemId: record.orderItemId ?? '--',
+    matnrId: record.matnrId ?? '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    maktx: normalizeText(record.maktx) || '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    platOrderCode: normalizeText(record.platOrderCode) || '--',
+    platWorkCode: normalizeText(record.platWorkCode) || '--',
+    projectCode: normalizeText(record.projectCode) || '--',
+    batch: normalizeText(record.batch) || '--',
+    unit: normalizeText(record.unit) || '--',
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    spec: normalizeText(record.spec) || '--',
+    model: normalizeText(record.model) || '--',
+    fieldsIndex: normalizeText(record.fieldsIndex) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getTaskItemReportColumns() {
+  return [
+    { prop: 'taskId', label: '浠诲姟ID' },
+    { prop: 'orderId', label: '涓诲崟ID' },
+    { prop: 'orderTypeText', label: '鍗曟嵁绫诲瀷' },
+    { prop: 'orderItemId', label: '鍗曟嵁鏄庣粏ID' },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { prop: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { prop: 'platWorkCode', label: '宸ュ崟鍙�' },
+    { prop: 'platOrderCode', label: '瀹㈡埛璁㈠崟鍙�' },
+    { prop: 'projectCode', label: '椤圭洰鍙�' },
+    { prop: 'batch', label: '搴撳瓨鎵规' },
+    { prop: 'unit', label: '搴撳瓨鍗曚綅' },
+    { prop: 'anfme', label: '鏁伴噺' },
+    { prop: 'workQty', label: '鎵ц鏁伴噺' },
+    { prop: 'qty', label: '瀹屾垚鏁伴噺' },
+    { prop: 'spec', label: '瑙勬牸' },
+    { prop: 'model', label: '鍨嬪彿' },
+    { prop: 'createByText', label: '鍒涘缓浜�' },
+    { prop: 'createTimeText', label: '鍒涘缓鏃堕棿' },
+    { prop: 'updateByText', label: '鏇存柊浜�' },
+    { prop: 'updateTimeText', label: '鏇存柊鏃堕棿' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildTaskItemPrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeTaskItemRow(record)
+    return {
+      taskId: row.taskId,
+      orderId: row.orderId,
+      orderTypeText: row.orderTypeText,
+      orderItemId: row.orderItemId,
+      matnrCode: row.matnrCode,
+      maktx: row.maktx,
+      platWorkCode: row.platWorkCode,
+      platOrderCode: row.platOrderCode,
+      projectCode: row.projectCode,
+      batch: row.batch,
+      unit: row.unit,
+      anfme: row.anfme,
+      workQty: row.workQty,
+      qty: row.qty,
+      spec: row.spec,
+      model: row.model,
+      createByText: row.createByText,
+      createTimeText: row.createTimeText,
+      updateByText: row.updateByText,
+      updateTimeText: row.updateTimeText,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/manager/task-item/taskItemTable.columns.js b/rsf-design/src/views/manager/task-item/taskItemTable.columns.js
new file mode 100644
index 0000000..b7f82b3
--- /dev/null
+++ b/rsf-design/src/views/manager/task-item/taskItemTable.columns.js
@@ -0,0 +1,109 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTaskItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'taskId',
+      label: '浠诲姟ID',
+      minWidth: 110,
+      align: 'right'
+    },
+    {
+      prop: 'orderId',
+      label: '涓诲崟ID',
+      minWidth: 110,
+      align: 'right'
+    },
+    {
+      prop: 'orderTypeText',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 110,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orderItemId',
+      label: '鍗曟嵁鏄庣粏ID',
+      minWidth: 120,
+      align: 'right'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platWorkCode',
+      label: '宸ュ崟鍙�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '搴撳瓨鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '搴撳瓨鍗曚綅',
+      width: 100,
+      align: 'center'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/task-log/index.vue b/rsf-design/src/views/manager/task-log/index.vue
new file mode 100644
index 0000000..eb0a2b2
--- /dev/null
+++ b/rsf-design/src/views/manager/task-log/index.vue
@@ -0,0 +1,250 @@
+<template>
+  <div class="task-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskLogDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportTaskLogReport,
+    fetchGetTaskLogDetail,
+    fetchGetTaskLogMany,
+    fetchTaskLogPage
+  } from '@/api/task-log'
+  import {
+    buildTaskLogPageQueryParams,
+    buildTaskLogPrintRows,
+    buildTaskLogSearchParams,
+    createTaskLogSearchState,
+    getTaskLogPaginationKey,
+    getTaskLogReportColumns,
+    normalizeTaskLogRow,
+    TASK_LOG_REPORT_TITLE
+  } from './taskLogPage.helpers'
+  import { createTaskLogTableColumns } from './taskLogTable.columns'
+  import TaskLogDetailDrawer from './modules/task-log-detail-drawer.vue'
+
+  defineOptions({ name: 'TaskLog' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createTaskLogSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = TASK_LOG_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskLogSearchParams(searchForm.value))
+  const reportColumns = getTaskLogReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿/鎵樼洏鐮�/鏈哄櫒浜虹紪鐮�'
+      }
+    },
+    {
+      label: '浠诲姟鍙�',
+      key: 'taskCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿'
+      }
+    },
+    {
+      label: '婧愬簱浣�',
+      key: 'orgLoc',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ簮搴撲綅'
+      }
+    },
+    {
+      label: '鐩爣搴撲綅',
+      key: 'targLoc',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ洰鏍囧簱浣�'
+      }
+    },
+    {
+      label: '鎵樼洏鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ墭鐩樼爜'
+      }
+    },
+    {
+      label: '鏈哄櫒浜虹紪鐮�',
+      key: 'robotCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ満鍣ㄤ汉缂栫爜'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTaskLogPage,
+      apiParams: buildTaskLogPageQueryParams(searchForm.value),
+      paginationKey: getTaskLogPaginationKey(),
+      columnsFactory: () => createTaskLogTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeTaskLogRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskLogMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskLogPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'task-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportTaskLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTaskLogPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetTaskLogDetail(id)
+    detailData.value = normalizeTaskLogRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskLogSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskLogSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-log/modules/task-log-detail-drawer.vue b/rsf-design/src/views/manager/task-log/modules/task-log-detail-drawer.vue
new file mode 100644
index 0000000..b2a5166
--- /dev/null
+++ b/rsf-design/src/views/manager/task-log/modules/task-log-detail-drawer.vue
@@ -0,0 +1,54 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟鍘嗗彶妗h鎯�"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="浠诲姟ID">{{ detail.taskId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鍙�">{{ detail.taskCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">{{ detail.taskStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">{{ detail.taskTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愬簱浣�">{{ detail.orgLoc || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愮珯鐐�">{{ detail.orgSite || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣搴撲綅">{{ detail.targLoc || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣绔欑偣">{{ detail.targSite || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏈哄櫒浜虹紪鐮�">{{ detail.robotCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浼樺厛绾�">{{ detail.sort ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮傚父鎻忚堪">{{ detail.expDesc || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮傚父缂栫爜">{{ detail.expCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'TaskLogDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task-log/taskLogPage.helpers.js b/rsf-design/src/views/manager/task-log/taskLogPage.helpers.js
new file mode 100644
index 0000000..41c7013
--- /dev/null
+++ b/rsf-design/src/views/manager/task-log/taskLogPage.helpers.js
@@ -0,0 +1,162 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const TASK_LOG_REPORT_TITLE = '浠诲姟鍘嗗彶妗f姤琛�'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+export function createTaskLogSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    taskId: '',
+    taskCode: '',
+    taskStatus: '',
+    taskType: '',
+    orgLoc: '',
+    orgSite: '',
+    targLoc: '',
+    targSite: '',
+    barcode: '',
+    robotCode: '',
+    exceStatus: '',
+    expDesc: '',
+    sort: '',
+    expCode: '',
+    startTime: '',
+    endTime: ''
+  }
+}
+
+export function getTaskLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildTaskLogSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'taskCode',
+    'orgLoc',
+    'orgSite',
+    'targLoc',
+    'targSite',
+    'barcode',
+    'robotCode',
+    'expDesc',
+    'expCode'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd', 'startTime', 'endTime'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['taskId', 'taskStatus', 'taskType', 'exceStatus', 'sort'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildTaskLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskLogSearchParams(params)
+  }
+}
+
+export function getTaskLogStatusMeta(value) {
+  return STATUS_META[Number(value)] || { text: '-', type: 'info' }
+}
+
+export function normalizeTaskLogRow(record = {}) {
+  const statusMeta = getTaskLogStatusMeta(record.status)
+  return {
+    ...record,
+    taskId: record.taskId ?? '--',
+    taskCode: record.taskCode || '--',
+    taskStatusText: record['taskStatus$'] || record.taskStatusText || (record.taskStatus ?? '--'),
+    taskTypeText: record['taskType$'] || record.taskTypeText || (record.taskType ?? '--'),
+    orgLoc: record.orgLoc || '--',
+    orgSite: record.orgSite || '--',
+    targLoc: record.targLoc || '--',
+    targSite: record.targSite || '--',
+    barcode: record.barcode || '--',
+    robotCode: record.robotCode || '--',
+    exceStatusText: record.exceStatusText || (record.exceStatus ?? '--'),
+    expDesc: record.expDesc || '--',
+    sort: record.sort ?? '--',
+    expCode: record.expCode || '--',
+    startTimeText: record['startTime$'] || record.startTime || '--',
+    endTimeText: record['endTime$'] || record.endTime || '--',
+    createByText: record['createBy$'] || record.createByText || '--',
+    createTimeText: record['createTime$'] || record.createTime || '--',
+    updateByText: record['updateBy$'] || record.updateByText || '--',
+    updateTimeText: record['updateTime$'] || record.updateTime || '--',
+    memo: record.memo || '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type
+  }
+}
+
+export function getTaskLogReportColumns() {
+  return [
+    { prop: 'taskCode', label: '浠诲姟鍙�' },
+    { prop: 'taskStatusText', label: '浠诲姟鐘舵��' },
+    { prop: 'taskTypeText', label: '浠诲姟绫诲瀷' },
+    { prop: 'orgLoc', label: '婧愬簱浣�' },
+    { prop: 'targLoc', label: '鐩爣搴撲綅' },
+    { prop: 'barcode', label: '鎵樼洏鐮�' },
+    { prop: 'robotCode', label: '鏈哄櫒浜虹紪鐮�' },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�' },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿' }
+  ]
+}
+
+export function buildTaskLogPrintRows(records = []) {
+  return records.map((record) => {
+    const row = normalizeTaskLogRow(record)
+    return {
+      taskCode: row.taskCode,
+      taskStatusText: row.taskStatusText,
+      taskTypeText: row.taskTypeText,
+      orgLoc: row.orgLoc,
+      targLoc: row.targLoc,
+      barcode: row.barcode,
+      robotCode: row.robotCode,
+      startTimeText: row.startTimeText,
+      endTimeText: row.endTimeText
+    }
+  })
+}
diff --git a/rsf-design/src/views/manager/task-log/taskLogTable.columns.js b/rsf-design/src/views/manager/task-log/taskLogTable.columns.js
new file mode 100644
index 0000000..2ece020
--- /dev/null
+++ b/rsf-design/src/views/manager/task-log/taskLogTable.columns.js
@@ -0,0 +1,109 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTaskLogTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'taskCode',
+      label: '浠诲姟鍙�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'taskStatusText',
+      label: '浠诲姟鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'taskTypeText',
+      label: '浠诲姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orgLoc',
+      label: '婧愬簱浣�',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orgSite',
+      label: '婧愮珯鐐�',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'targLoc',
+      label: '鐩爣搴撲綅',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'targSite',
+      label: '鐩爣绔欑偣',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'robotCode',
+      label: '鏈哄櫒浜虹紪鐮�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'startTimeText',
+      label: '寮�濮嬫椂闂�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'endTimeText',
+      label: '缁撴潫鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/task/index.vue b/rsf-design/src/views/manager/task/index.vue
new file mode 100644
index 0000000..138697d
--- /dev/null
+++ b/rsf-design/src/views/manager/task/index.vue
@@ -0,0 +1,352 @@
+<template>
+  <div class="task-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchCheckTask,
+    fetchCompleteTask,
+    fetchPickTask,
+    fetchRemoveTask,
+    fetchTaskDetail,
+    fetchTaskItemPage,
+    fetchTaskPage,
+    fetchTopTask
+  } from '@/api/task'
+  import TaskDetailDrawer from './modules/task-detail-drawer.vue'
+  import { createTaskTableColumns } from './taskTable.columns'
+  import {
+    buildTaskPageQueryParams,
+    confirmTaskAction,
+    createTaskSearchState,
+    normalizeTaskItemRow,
+    normalizeTaskRow
+  } from './taskPage.helpers'
+
+  defineOptions({ name: 'Task' })
+
+  const loading = ref(false)
+  const tableData = ref([])
+  const searchForm = ref(createTaskSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activeTaskRow = ref(null)
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ换鍔″彿/搴撲綅/鎵樼洏鐮�' }
+    },
+    {
+      label: '浠诲姟鍙�',
+      key: 'taskCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ换鍔″彿' }
+    },
+    {
+      label: '婧愬簱浣�',
+      key: 'orgLoc',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ簮搴撲綅' }
+    },
+    {
+      label: '鐩爣搴撲綅',
+      key: 'targLoc',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ洰鏍囧簱浣�' }
+    },
+    {
+      label: '鎵樼洏鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ユ墭鐩樼爜' }
+    }
+  ])
+
+  const detailColumns = computed(() => [
+    {
+      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 openDetailDrawer(row) {
+    activeTaskRow.value = row
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function handleActionClick(action, row) {
+    try {
+      if (action.key === 'view') {
+        await openDetailDrawer(row)
+        return
+      }
+
+      if (action.key === 'complete') {
+        await confirmTaskAction(`纭畾瀹屾垚浠诲姟 ${row.taskCode || ''} 鍚楋紵`)
+        await fetchCompleteTask(row.id)
+        ElMessage.success('浠诲姟瀹屾垚鎴愬姛')
+      } else if (action.key === 'remove') {
+        await confirmTaskAction(`纭畾鍙栨秷浠诲姟 ${row.taskCode || ''} 鍚楋紵`)
+        await fetchRemoveTask(row.id)
+        ElMessage.success('浠诲姟鍙栨秷鎴愬姛')
+      } else if (action.key === 'check') {
+        await confirmTaskAction(`纭畾鎵ц鐩樼偣鍑哄簱浠诲姟 ${row.taskCode || ''} 鍚楋紵`)
+        await fetchCheckTask(row.id)
+        ElMessage.success('鐩樼偣鍑哄簱鎴愬姛')
+      } else if (action.key === 'pick') {
+        await confirmTaskAction(`纭畾鎵ц鎷f枡鍑哄簱浠诲姟 ${row.taskCode || ''} 鍚楋紵`)
+        await fetchPickTask(row.id)
+        ElMessage.success('鎷f枡鍑哄簱鎴愬姛')
+      } else if (action.key === 'top') {
+        await fetchTopTask(row.id)
+        ElMessage.success('浠诲姟缃《鎴愬姛')
+      }
+
+      await loadPageData()
+      if (detailDrawerVisible.value && activeTaskRow.value?.id === row.id) {
+        await loadDetailResources()
+      }
+    } catch (error) {
+      if (error === 'cancel') {
+        return
+      }
+      ElMessage.error(error?.message || '浠诲姟鎿嶄綔澶辫触')
+    }
+  }
+
+  const { columns, columnChecks } = useTableColumns(() => createTaskTableColumns(handleActionClick))
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchTaskPage(
+          buildTaskPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '浠诲姟鍒楄〃鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeTaskRow(record))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadDetailResources() {
+    if (!activeTaskRow.value?.id) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const [detailResponse, taskItemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchTaskDetail(activeTaskRow.value.id), {}, {
+          timeoutMessage: '浠诲姟璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }),
+        guardRequestWithMessage(
+          fetchTaskItemPage({
+            taskId: activeTaskRow.value.id,
+            current: detailPagination.current,
+            pageSize: detailPagination.size
+          }),
+          {
+            records: [],
+            total: 0,
+            current: detailPagination.current,
+            size: detailPagination.size
+          },
+          { timeoutMessage: '浠诲姟鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+        )
+      ])
+
+      detailData.value = normalizeTaskRow(detailResponse)
+      detailTableData.value = Array.isArray(taskItemResponse?.records)
+        ? taskItemResponse.records.map((record) => normalizeTaskItemRow(record))
+        : []
+      updatePaginationState(detailPagination, taskItemResponse, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createTaskSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  onMounted(loadPageData)
+</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
new file mode 100644
index 0000000..7a65cb6
--- /dev/null
+++ b/rsf-design/src/views/manager/task/modules/task-detail-drawer.vue
@@ -0,0 +1,59 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟璇︽儏"
+    size="85%"
+    @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="flex items-center justify-between">
+        <div class="text-sm text-[var(--art-gray-600)]">浠诲姟鏄庣粏</div>
+        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+      </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>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/task/taskPage.helpers.js b/rsf-design/src/views/manager/task/taskPage.helpers.js
new file mode 100644
index 0000000..0fa5bda
--- /dev/null
+++ b/rsf-design/src/views/manager/task/taskPage.helpers.js
@@ -0,0 +1,146 @@
+import { ElMessageBox } from 'element-plus'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createTaskSearchState() {
+  return {
+    condition: '',
+    taskCode: '',
+    orgLoc: '',
+    targLoc: '',
+    barcode: ''
+  }
+}
+
+export function buildTaskPageQueryParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+
+  ;['condition', 'taskCode', 'orgLoc', 'targLoc', 'barcode'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function normalizeTaskRow(record = {}) {
+  return {
+    ...record,
+    taskCode: record.taskCode || '-',
+    taskStatusLabel: record['taskStatus$'] || '-',
+    taskTypeLabel: record['taskType$'] || '-',
+    warehTypeLabel: record['warehType$'] || '-',
+    orgLoc: record.orgLoc || '-',
+    orgSiteLabel: record['orgSite$'] || record.orgSite || '-',
+    targLoc: record.targLoc || '-',
+    targSiteLabel: record['targSite$'] || record.targSite || '-',
+    barcode: record.barcode || '-',
+    robotCode: record.robotCode || '-',
+    sort: normalizeNumber(record.sort),
+    statusText: record['status$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    canComplete: record.canComplete === true
+  }
+}
+
+export function normalizeTaskItemRow(record = {}) {
+  return {
+    ...record,
+    orderTypeLabel: record['orderType$'] || '-',
+    wkTypeLabel: record['wkType$'] || '-',
+    platWorkCode: record.platWorkCode || '-',
+    platItemId: record.platItemId || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    batch: record.batch || '-',
+    unit: record.unit || '-',
+    anfme: normalizeNumber(record.anfme),
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function canCheckTask(row = {}) {
+  return Number(row.taskStatus) === 199 && Number(row.taskType) === 107
+}
+
+export function canPickTask(row = {}) {
+  return Number(row.taskStatus) === 199 && Number(row.taskType) === 103
+}
+
+export function getTaskActionList(row = {}) {
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    },
+    ...(row.canComplete
+      ? [
+          {
+            key: 'complete',
+            label: '瀹屾垚浠诲姟',
+            icon: 'ri:checkbox-circle-line',
+            auth: 'update'
+          }
+        ]
+      : []),
+    ...(canCheckTask(row)
+      ? [
+          {
+            key: 'check',
+            label: '鐩樼偣鍑哄簱',
+            icon: 'ri:file-check-line',
+            auth: 'update'
+          }
+        ]
+      : []),
+    ...(canPickTask(row)
+      ? [
+          {
+            key: 'pick',
+            label: '鎷f枡鍑哄簱',
+            icon: 'ri:paint-line',
+            auth: 'update'
+          }
+        ]
+      : []),
+    {
+      key: 'top',
+      label: '浠诲姟缃《',
+      icon: 'ri:pushpin-line',
+      auth: 'update'
+    },
+    {
+      key: 'remove',
+      label: '鍙栨秷浠诲姟',
+      icon: 'ri:close-circle-line',
+      color: '#f56c6c',
+      auth: 'delete'
+    }
+  ]
+}
+
+export async function confirmTaskAction(message) {
+  await ElMessageBox.confirm(message, '鎻愮ず', {
+    type: 'warning',
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷'
+  })
+}
diff --git a/rsf-design/src/views/manager/task/taskTable.columns.js b/rsf-design/src/views/manager/task/taskTable.columns.js
new file mode 100644
index 0000000..63b5da5
--- /dev/null
+++ b/rsf-design/src/views/manager/task/taskTable.columns.js
@@ -0,0 +1,93 @@
+import { h } from 'vue'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getTaskActionList } from './taskPage.helpers'
+
+export function createTaskTableColumns(handleActionClick) {
+  return [
+    {
+      prop: 'taskCode',
+      label: '浠诲姟鍙�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'taskStatusLabel',
+      label: '浠诲姟鐘舵��',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'taskTypeLabel',
+      label: '浠诲姟绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'warehTypeLabel',
+      label: '璁惧绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orgLoc',
+      label: '婧愬簱浣�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orgSiteLabel',
+      label: '婧愮珯鐐�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'targLoc',
+      label: '鐩爣搴撲綅',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'targSiteLabel',
+      label: '鐩爣绔欑偣',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'robotCode',
+      label: '鏈哄櫒浜虹紪鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'sort',
+      label: '浼樺厛绾�',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 120,
+      fixed: 'right',
+      formatter: (row) =>
+        h('div', [
+          h(ArtButtonMore, {
+            list: getTaskActionList(row),
+            onClick: (item) => handleActionClick(item, row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/manager/wave-rule/index.vue b/rsf-design/src/views/manager/wave-rule/index.vue
new file mode 100644
index 0000000..41970b2
--- /dev/null
+++ b/rsf-design/src/views/manager/wave-rule/index.vue
@@ -0,0 +1,248 @@
+<template>
+  <div class="wave-rule-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板娉㈡绛栫暐</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <WaveRuleDialog
+        v-model:visible="dialogVisible"
+        :wave-rule-data="currentWaveRuleData"
+        :type-options="typeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <WaveRuleDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteWaveRule,
+    fetchDictDataPage,
+    fetchGetWaveRuleDetail,
+    fetchSaveWaveRule,
+    fetchUpdateWaveRule,
+    fetchWaveRulePage
+  } from '@/api/system-manage'
+  import WaveRuleDialog from './modules/wave-rule-dialog.vue'
+  import WaveRuleDetailDrawer from './modules/wave-rule-detail-drawer.vue'
+  import { createWaveRuleTableColumns } from './waveRuleTable.columns'
+  import {
+    buildWaveRuleDialogModel,
+    buildWaveRulePageQueryParams,
+    buildWaveRuleSavePayload,
+    buildWaveRuleSearchParams,
+    buildWaveRuleTypeOptions,
+    createWaveRuleSearchState,
+    getWaveRuleDictTypeCode,
+    getWaveRulePaginationKey,
+    normalizeWaveRuleListRow
+  } from './waveRulePage.helpers'
+
+  defineOptions({ name: 'WaveRule' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createWaveRuleSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const typeOptions = ref([])
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙锋垨鍚嶇О'
+      }
+    },
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙�'
+      }
+    },
+    {
+      label: '绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: typeOptions.value
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ瓥鐣ュ悕绉�'
+      }
+    }
+  ])
+
+  async function loadTypeOptions() {
+    const records = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: getWaveRuleDictTypeCode(),
+        status: 1
+      }),
+      [],
+      {
+        timeoutMessage: '娉㈡绛栫暐绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    typeOptions.value = buildWaveRuleTypeOptions(Array.isArray(records?.records) ? records.records : records?.list || records || [])
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeWaveRuleListRow(await fetchGetWaveRuleDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇娉㈡绛栫暐璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentWaveRuleData.value = buildWaveRuleDialogModel(await fetchGetWaveRuleDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇娉㈡绛栫暐璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchWaveRulePage,
+      apiParams: buildWaveRulePageQueryParams(searchForm.value),
+      paginationKey: getWaveRulePaginationKey(),
+      columnsFactory: () =>
+        createWaveRuleTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeWaveRuleListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentWaveRuleData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildWaveRuleDialogModel(),
+    buildEditModel: (record) => buildWaveRuleDialogModel(record),
+    buildSavePayload: (formData) => buildWaveRuleSavePayload(formData),
+    saveRequest: fetchSaveWaveRule,
+    updateRequest: fetchUpdateWaveRule,
+    deleteRequest: fetchDeleteWaveRule,
+    entityName: '娉㈡绛栫暐',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  onMounted(() => {
+    loadTypeOptions()
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildWaveRuleSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWaveRuleSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/manager/wave-rule/modules/wave-rule-detail-drawer.vue b/rsf-design/src/views/manager/wave-rule/modules/wave-rule-detail-drawer.vue
new file mode 100644
index 0000000..1e4247e
--- /dev/null
+++ b/rsf-design/src/views/manager/wave-rule/modules/wave-rule-detail-drawer.vue
@@ -0,0 +1,41 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娉㈡绛栫暐璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="缂栧彿">{{ displayData.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="绫诲瀷">{{ displayData.typeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeWaveRuleListRow } from '../waveRulePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeWaveRuleListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/manager/wave-rule/modules/wave-rule-dialog.vue b/rsf-design/src/views/manager/wave-rule/modules/wave-rule-dialog.vue
new file mode 100644
index 0000000..5de7261
--- /dev/null
+++ b/rsf-design/src/views/manager/wave-rule/modules/wave-rule-dialog.vue
@@ -0,0 +1,153 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildWaveRuleDialogModel, createWaveRuleFormState } from '../waveRulePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    waveRuleData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createWaveRuleFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫娉㈡绛栫暐' : '鏂板娉㈡绛栫暐'))
+
+  const rules = computed(() => ({
+    type: [{ required: true, message: '璇烽�夋嫨绛栫暐绫诲瀷', trigger: 'change' }],
+    name: [{ required: true, message: '璇疯緭鍏ョ瓥鐣ュ悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        disabled: true,
+        placeholder: '鏂板鍚庤嚜鍔ㄧ敓鎴�'
+      }
+    },
+    {
+      label: '绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        options: props.typeOptions,
+        placeholder: '璇烽�夋嫨绛栫暐绫诲瀷'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ瓥鐣ュ悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createWaveRuleFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildWaveRuleDialogModel(props.waveRuleData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.waveRuleData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/manager/wave-rule/waveRulePage.helpers.js b/rsf-design/src/views/manager/wave-rule/waveRulePage.helpers.js
new file mode 100644
index 0000000..d96c6ac
--- /dev/null
+++ b/rsf-design/src/views/manager/wave-rule/waveRulePage.helpers.js
@@ -0,0 +1,114 @@
+const WAVE_RULE_DICT_TYPE_CODE = 'sys_wave_rule_code'
+
+export function getWaveRuleDictTypeCode() {
+  return WAVE_RULE_DICT_TYPE_CODE
+}
+
+export function createWaveRuleSearchState() {
+  return {
+    condition: '',
+    code: '',
+    type: '',
+    name: '',
+    status: ''
+  }
+}
+
+export function createWaveRuleFormState() {
+  return {
+    id: null,
+    code: '',
+    type: '',
+    name: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getWaveRulePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWaveRuleStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function buildWaveRuleSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    code: String(params.code || '').trim(),
+    name: String(params.name || '').trim(),
+    ...(params.type !== '' && params.type !== null && params.type !== undefined
+      ? { type: Number(params.type) }
+      : {}),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildWaveRulePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaveRuleSearchParams(params)
+  }
+}
+
+export function buildWaveRuleDialogModel(record = {}) {
+  return {
+    ...createWaveRuleFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    code: record.code || '',
+    type: record.type !== undefined && record.type !== null && record.type !== ''
+      ? Number(record.type)
+      : '',
+    name: record.name || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildWaveRuleSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    ...(formData.code ? { code: String(formData.code).trim() } : {}),
+    type: Number(formData.type),
+    name: String(formData.name || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function buildWaveRuleTypeOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((item) => ({
+    label: item.label || item.name || String(item.value ?? ''),
+    value: Number(item.value)
+  }))
+}
+
+export function normalizeWaveRuleListRow(record = {}) {
+  const statusMeta = getWaveRuleStatusMeta(record.status)
+  return {
+    ...record,
+    code: record.code || '',
+    name: record.name || '',
+    memo: record.memo || '',
+    typeText: record['type$'] || record.typeText || '-',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || record.updateByLabel || '',
+    createByLabel: record['createBy$'] || record.createByLabel || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/manager/wave-rule/waveRuleTable.columns.js b/rsf-design/src/views/manager/wave-rule/waveRuleTable.columns.js
new file mode 100644
index 0000000..f2bf1fd
--- /dev/null
+++ b/rsf-design/src/views/manager/wave-rule/waveRuleTable.columns.js
@@ -0,0 +1,73 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createWaveRuleTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'code',
+      label: '缂栧彿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeText',
+      label: '绫诲瀷',
+      minWidth: 140,
+      formatter: (row) => row.typeText || '-'
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateByLabel',
+      label: '鏇存柊浜�',
+      width: 120,
+      formatter: (row) => row.updateByLabel || '-'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/asn-order-item-log/asnOrderItemLogPage.helpers.js b/rsf-design/src/views/orders/asn-order-item-log/asnOrderItemLogPage.helpers.js
new file mode 100644
index 0000000..01469b7
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item-log/asnOrderItemLogPage.helpers.js
@@ -0,0 +1,169 @@
+import { normalizeAsnOrderItemLogRow } from '../asn-order-log/asnOrderLogPage.helpers.js'
+
+export const ASN_ORDER_ITEM_LOG_REPORT_TITLE = '鏀惰揣鍘嗗彶鏄庣粏鎶ヨ〃'
+export const ASN_ORDER_ITEM_LOG_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+export function createAsnOrderItemLogSearchState(seed = {}) {
+  return {
+    condition: '',
+    logId: '',
+    orderId: '',
+    orderCode: '',
+    platItemId: '',
+    poDetlId: '',
+    poCode: '',
+    fieldsIndex: '',
+    matnrId: '',
+    matnrCode: '',
+    maktx: '',
+    anfme: '',
+    stockUnit: '',
+    purQty: '',
+    purUnit: '',
+    qty: '',
+    splrCode: '',
+    splrBatch: '',
+    splrName: '',
+    qrcode: '',
+    trackCode: '',
+    barcode: '',
+    packName: '',
+    ntyStatus: '',
+    memo: '',
+    status: '',
+    ...seed
+  }
+}
+
+export function getAsnOrderItemLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+function pushText(result, key, value) {
+  if (value === null || value === undefined) {
+    return
+  }
+  const text = String(value).trim()
+  if (text) {
+    result[key] = text
+  }
+}
+
+function pushNumber(result, key, value) {
+  if (value === '' || value === null || value === undefined) {
+    return
+  }
+  const numericValue = Number(value)
+  if (!Number.isNaN(numericValue)) {
+    result[key] = numericValue
+  }
+}
+
+export function buildAsnOrderItemLogSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'orderCode',
+    'platItemId',
+    'poCode',
+    'fieldsIndex',
+    'matnrCode',
+    'maktx',
+    'stockUnit',
+    'purUnit',
+    'splrCode',
+    'splrBatch',
+    'splrName',
+    'qrcode',
+    'trackCode',
+    'barcode',
+    'packName',
+    'memo'
+  ].forEach((key) => pushText(result, key, params[key]))
+
+  ;[
+    'logId',
+    'orderId',
+    'poDetlId',
+    'matnrId',
+    'anfme',
+    'purQty',
+    'qty',
+    'ntyStatus',
+    'status'
+  ].forEach((key) => pushNumber(result, key, params[key]))
+
+  return result
+}
+
+export function buildAsnOrderItemLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildAsnOrderItemLogSearchParams(params)
+  }
+}
+
+export function getAsnOrderItemLogReportColumns() {
+  return [
+    { key: 'asnCode', label: 'ASN鍗曞彿' },
+    { key: 'platItemId', label: '骞冲彴琛屽彿' },
+    { key: 'poDetlId', label: 'PO鍗曟槑缁咺D' },
+    { key: 'poCode', label: 'PO鍗曞彿' },
+    { key: 'fieldsIndex', label: '鍔ㄦ�佸瓧娈电储寮�' },
+    { key: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { key: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { key: 'anfme', label: '閫佽揣鏁伴噺' },
+    { key: 'stockUnit', label: '搴撳瓨鍗曚綅' },
+    { key: 'purQty', label: '閲囪喘鏁伴噺' },
+    { key: 'purUnit', label: '閲囪喘鍗曚綅' },
+    { key: 'qty', label: '宸叉敹鏁伴噺' },
+    { key: 'splrCode', label: '渚涘簲鍟嗙紪鐮�' },
+    { key: 'splrBatch', label: '渚涘簲鍟嗘壒娆�' },
+    { key: 'splrName', label: '渚涘簲鍟嗗悕绉�' },
+    { key: 'qrcode', label: '浜岀淮鐮�' },
+    { key: 'trackCode', label: '璺熻釜鐮�' },
+    { key: 'barcode', label: '鏉″舰鐮�' },
+    { key: 'packName', label: '鍖呰鍚嶇О' },
+    { key: 'ntyStatusText', label: '涓婃姤鐘舵��' },
+    { key: 'statusText', label: '鐘舵��' },
+    { key: 'updateByText', label: '鏇存柊浜�' },
+    { key: 'updateTimeText', label: '鏇存柊鏃堕棿' },
+    { key: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildAsnOrderItemLogPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeAsnOrderItemLogRow(record))
+}
+
+export function buildAsnOrderItemLogReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = ASN_ORDER_ITEM_LOG_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: ASN_ORDER_ITEM_LOG_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...ASN_ORDER_ITEM_LOG_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/asn-order-item-log/index.vue b/rsf-design/src/views/orders/asn-order-item-log/index.vue
new file mode 100644
index 0000000..68187eb
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item-log/index.vue
@@ -0,0 +1,256 @@
+<template>
+  <div class="asn-order-item-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="resolvedPreviewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useRoute } from 'vue-router'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchAsnOrderItemLogPage,
+    fetchExportAsnOrderItemLogReport,
+    fetchGetAsnOrderItemLogMany
+  } from '@/api/asn-order-log'
+  import { createAsnOrderItemLogColumns } from '../asn-order-log/asnOrderLogTable.columns.js'
+  import { normalizeAsnOrderItemLogRow } from '../asn-order-log/asnOrderLogPage.helpers.js'
+  import {
+    ASN_ORDER_ITEM_LOG_REPORT_STYLE,
+    ASN_ORDER_ITEM_LOG_REPORT_TITLE,
+    buildAsnOrderItemLogPageQueryParams,
+    buildAsnOrderItemLogPrintRows,
+    buildAsnOrderItemLogReportMeta,
+    buildAsnOrderItemLogSearchParams,
+    createAsnOrderItemLogSearchState,
+    getAsnOrderItemLogPaginationKey,
+    getAsnOrderItemLogReportColumns
+  } from './asnOrderItemLogPage.helpers.js'
+
+  defineOptions({ name: 'AsnOrderItemLog' })
+
+  const route = useRoute()
+  const userStore = useUserStore()
+  const initialLogId = route.query.logId || route.query.id
+  const searchForm = ref(
+    createAsnOrderItemLogSearchState({
+      logId: initialLogId !== undefined ? Number(initialLogId) || '' : ''
+    })
+  )
+  const selectedRows = ref([])
+  const reportTitle = ASN_ORDER_ITEM_LOG_REPORT_TITLE
+  const reportColumns = getAsnOrderItemLogReportColumns()
+  const reportQueryParams = computed(() => buildAsnOrderItemLogSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿/PO鍗曞彿/鐗╂枡缂栫爜'
+      }
+    },
+    {
+      label: '鏃ュ織ID',
+      key: 'logId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ棩蹇桰D'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'orderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '涓婃姤鐘舵��',
+      key: 'ntyStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鏈笂鎶�', value: 0 },
+          { label: '宸蹭笂鎶�', value: 1 },
+          { label: '閮ㄥ垎涓婃姤', value: 2 }
+        ]
+      }
+    }
+  ])
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchAsnOrderItemLogPage,
+      apiParams: buildAsnOrderItemLogPageQueryParams(searchForm.value),
+      paginationKey: getAsnOrderItemLogPaginationKey(),
+      columnsFactory: () => createAsnOrderItemLogColumns()
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeAsnOrderItemLogRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildAsnOrderItemLogPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    const resetSeed = initialLogId !== undefined ? { logId: Number(initialLogId) || '' } : {}
+    Object.assign(searchForm.value, createAsnOrderItemLogSearchState(resetSeed))
+    resetSearchParams(buildAsnOrderItemLogPageQueryParams(createAsnOrderItemLogSearchState(resetSeed)))
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetAsnOrderItemLogMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchAsnOrderItemLogPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'asn-order-item-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportAsnOrderItemLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildAsnOrderItemLogPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: {
+        ...ASN_ORDER_ITEM_LOG_REPORT_STYLE
+      }
+    })
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildAsnOrderItemLogReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || ASN_ORDER_ITEM_LOG_REPORT_STYLE.orientation
+    })
+  )
+</script>
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
new file mode 100644
index 0000000..d5c43d2
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item/asnOrderItemPage.helpers.js
@@ -0,0 +1,245 @@
+export const ASN_ORDER_ITEM_REPORT_TITLE = '鏀惰揣鏄庣粏鎶ヨ〃'
+export const ASN_ORDER_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const STATUS_MAP = {
+  1: { label: '姝e父', tagType: 'success' },
+  0: { label: '鍐荤粨', tagType: 'info' }
+}
+
+const NTY_STATUS_MAP = {
+  0: { label: '鏈笂鎶�', tagType: 'info' },
+  1: { label: '宸蹭笂鎶�', tagType: 'success' }
+}
+
+function normalizeText(value, fallback = '-') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  return String(value)
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function pushText(result, key, value) {
+  const text = normalizeText(value, '')
+  if (text) {
+    result[key] = text
+  }
+}
+
+function pushNumber(result, key, value) {
+  if (value === '' || value === null || value === undefined) {
+    return
+  }
+  const numericValue = Number(value)
+  if (Number.isFinite(numericValue)) {
+    result[key] = numericValue
+  }
+}
+
+function pushRange(result, key, value) {
+  if (!Array.isArray(value) || value.length === 0) {
+    return
+  }
+  const normalizedRange = value.filter((item) => item !== null && item !== undefined && item !== '')
+  if (normalizedRange.length === 2) {
+    result[key] = normalizedRange
+  }
+}
+
+function getOrderTypeLabel(type) {
+  if (type === 'in') return '鍏ュ簱'
+  if (type === 'out') return '鍑哄簱'
+  return normalizeText(type, '-')
+}
+
+function getStatusConfig(status) {
+  const numericStatus = Number(status)
+  return STATUS_MAP[numericStatus] || { label: normalizeText(status, '-'), tagType: 'info' }
+}
+
+function getNtyStatusConfig(status) {
+  const numericStatus = Number(status)
+  return NTY_STATUS_MAP[numericStatus] || { label: normalizeText(status, '-'), tagType: 'info' }
+}
+
+export function createAsnOrderItemSearchState() {
+  return {
+    condition: '',
+    orderCode: '',
+    poCode: '',
+    platWorkCode: '',
+    platItemId: '',
+    matnrCode: '',
+    maktx: '',
+    spec: '',
+    model: '',
+    batch: '',
+    stockUnit: '',
+    purUnit: '',
+    splrCode: '',
+    splrName: '',
+    splrBatch: '',
+    qrcode: '',
+    trackCode: '',
+    packName: '',
+    projectCode: '',
+    targetWarehouseId: '',
+    status: '',
+    ntyStatus: '',
+    createTimeRange: [],
+    updateTimeRange: []
+  }
+}
+
+export function buildAsnOrderItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'orderCode',
+    'poCode',
+    'platWorkCode',
+    'platItemId',
+    'matnrCode',
+    'maktx',
+    'spec',
+    'model',
+    'batch',
+    'stockUnit',
+    'purUnit',
+    'splrCode',
+    'splrName',
+    'splrBatch',
+    'qrcode',
+    'trackCode',
+    'packName',
+    'projectCode',
+    'targetWarehouseId'
+  ].forEach((key) => pushText(result, key, params[key]))
+
+  ;['status', 'ntyStatus'].forEach((key) => pushNumber(result, key, params[key]))
+  ;['createTimeRange', 'updateTimeRange'].forEach((key) => pushRange(result, key, params[key]))
+
+  return result
+}
+
+export function buildAsnOrderItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderBy ? { orderBy: normalizeText(params.orderBy) } : {}),
+    ...buildAsnOrderItemSearchParams(params)
+  }
+}
+
+export function normalizeAsnOrderItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    poCode: normalizeText(record.poCode, '-'),
+    typeLabel: getOrderTypeLabel(record.type),
+    wkTypeLabel: normalizeText(record['wkType$'] || record.wkType, '-'),
+    purchaseOrgName: normalizeText(record.purchaseOrgName, '-'),
+    purchaseUserName: normalizeText(record.purchaseUserName, '-'),
+    supplierId: normalizeText(record.supplierId, '-'),
+    supplierName: normalizeText(record.supplierName, '-'),
+    platWorkCode: normalizeText(record.platWorkCode, '-'),
+    platItemId: normalizeText(record.platItemId, '-'),
+    matnrCode: normalizeText(record.matnrCode, '-'),
+    maktx: normalizeText(record.maktx, '-'),
+    batch: normalizeText(record.batch, '-'),
+    stockUnit: normalizeText(record.stockUnit, '-'),
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    targetWarehouseId: normalizeText(record.targetWarehouseId, '-'),
+    businessTimeText: normalizeText(record.businessTime, '-'),
+    updateTimeText: normalizeText(record.updateTime, '-'),
+    memo: normalizeText(record.memo, '-')
+  }
+}
+
+export function normalizeAsnOrderItemDetail(record = {}) {
+  const statusConfig = getStatusConfig(record.status)
+  const ntyStatusConfig = getNtyStatusConfig(record.ntyStatus)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    orderCode: normalizeText(record.orderCode, '-'),
+    poCode: normalizeText(record.poCode, '-'),
+    typeLabel: getOrderTypeLabel(record.type),
+    poDetlId: record.poDetlId ?? '-',
+    platWorkCode: normalizeText(record.platWorkCode, '-'),
+    platItemId: normalizeText(record.platItemId, '-'),
+    projectCode: normalizeText(record.projectCode, '-'),
+    matnrCode: normalizeText(record.matnrCode, '-'),
+    maktx: normalizeText(record.maktx, '-'),
+    spec: normalizeText(record.spec, '-'),
+    model: normalizeText(record.model, '-'),
+    batch: normalizeText(record.batch, '-'),
+    stockUnit: normalizeText(record.stockUnit, '-'),
+    purQty: normalizeNumber(record.purQty),
+    purUnit: normalizeText(record.purUnit, '-'),
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    workQty: normalizeNumber(record.workQty),
+    splrCode: normalizeText(record.splrCode, '-'),
+    splrName: normalizeText(record.splrName, '-'),
+    splrBatch: normalizeText(record.splrBatch, '-'),
+    qrcode: normalizeText(record.qrcode, '-'),
+    barcode: normalizeText(record.barcode, '-'),
+    packName: normalizeText(record.packName, '-'),
+    prodTimeText: normalizeText(record.prodTime, '-'),
+    targetWarehouseId: normalizeText(record.targetWarehouseId, '-'),
+    sourceWarehouseId: normalizeText(record.sourceWarehouseId, '-'),
+    ntyStatusText: ntyStatusConfig.label,
+    ntyStatusTagType: ntyStatusConfig.tagType,
+    statusText: statusConfig.label,
+    statusTagType: statusConfig.tagType,
+    isptResultText: normalizeText(record['isptResult$'] || record.isptResult, '-'),
+    updateByText: normalizeText(record['updateBy$'] || record.updateBy, '-'),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTime, '-'),
+    createByText: normalizeText(record['createBy$'] || record.createBy, '-'),
+    createTimeText: normalizeText(record['createTime$'] || record.createTime, '-'),
+    memo: normalizeText(record.memo, '-'),
+    extendFields: record.extendFields || {}
+  }
+}
+
+export function buildAsnOrderItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeAsnOrderItemRow(record))
+}
+
+export function buildAsnOrderItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = ASN_ORDER_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: ASN_ORDER_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...ASN_ORDER_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/asn-order-item/asnOrderItemTable.columns.js b/rsf-design/src/views/orders/asn-order-item/asnOrderItemTable.columns.js
new file mode 100644
index 0000000..3306ced
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item/asnOrderItemTable.columns.js
@@ -0,0 +1,66 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+function createTextColumn(prop, label, minWidth, { align, formatter } = {}) {
+  return {
+    prop,
+    label,
+    minWidth,
+    align,
+    showOverflowTooltip: true,
+    formatter: formatter || ((row) => row?.[prop] || '-')
+  }
+}
+
+export function createAsnOrderItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    createTextColumn('poCode', 'PO鍗曞彿', 160),
+    createTextColumn('wkTypeLabel', '涓氬姟绫诲瀷', 120),
+    createTextColumn('typeLabel', '鍗曟嵁绫诲瀷', 110),
+    createTextColumn('purchaseOrgName', '閲囪喘缁勭粐', 150),
+    createTextColumn('purchaseUserName', '閲囪喘鍛�', 120),
+    createTextColumn('supplierName', '渚涘簲鍟�', 160),
+    createTextColumn('platWorkCode', '璁″垝璺熻釜鍙�', 150),
+    createTextColumn('platItemId', '琛屽彿', 110),
+    createTextColumn('matnrCode', '鐗╂枡缂栫爜', 160),
+    createTextColumn('maktx', '鐗╂枡鍚嶇О', 220),
+    createTextColumn('batch', '渚涘簲鍟嗘壒娆�', 140),
+    createTextColumn('stockUnit', '搴撳瓨鍗曚綅', 110),
+    {
+      prop: 'anfme',
+      label: '閫佽揣鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? 0
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? 0
+    },
+    createTextColumn('targetWarehouseId', '寤鸿鐩爣浠�', 140),
+    createTextColumn('businessTimeText', '涓氬姟鏃堕棿', 180),
+    createTextColumn('updateTimeText', '鏇存柊鏃堕棿', 180),
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      align: 'right',
+      formatter: (row) =>
+        h(
+          'div',
+          { class: 'flex justify-end' },
+          [
+            h(ArtButtonTable, {
+              type: 'view',
+              onClick: () => handleView?.(row)
+            })
+          ]
+        )
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/asn-order-item/index.vue b/rsf-design/src/views/orders/asn-order-item/index.vue
new file mode 100644
index 0000000..f1f4cce
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item/index.vue
@@ -0,0 +1,386 @@
+<template>
+  <div class="asn-order-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ListExportPrint
+              :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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <AsnOrderItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  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 {
+    fetchAsnOrderItemFullPage,
+    fetchExportAsnOrderItemReport,
+    fetchGetAsnOrderItemDetail
+  } from '@/api/asn-order-item'
+  import AsnOrderItemDetailDrawer from './modules/asn-order-item-detail-drawer.vue'
+  import { createAsnOrderItemTableColumns } from './asnOrderItemTable.columns'
+  import {
+    ASN_ORDER_ITEM_REPORT_STYLE,
+    ASN_ORDER_ITEM_REPORT_TITLE,
+    buildAsnOrderItemPageQueryParams,
+    buildAsnOrderItemPrintRows,
+    buildAsnOrderItemReportMeta,
+    buildAsnOrderItemSearchParams,
+    createAsnOrderItemSearchState,
+    normalizeAsnOrderItemDetail,
+    normalizeAsnOrderItemRow
+  } from './asnOrderItemPage.helpers'
+
+  defineOptions({ name: 'AsnOrderItem' })
+
+  const DEFAULT_PAGE_SIZE = 20
+
+  const userStore = useUserStore()
+  const reportTitle = ASN_ORDER_ITEM_REPORT_TITLE
+  const searchForm = ref(createAsnOrderItemSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const activeItemId = ref(null)
+
+  const reportQueryParams = computed(() => ({
+    ...buildAsnOrderItemSearchParams(searchForm.value),
+    orderBy: 'id desc'
+  }))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� PO 鍗曞彿/鐗╂枡缂栫爜/鐗╂枡鍚嶇О/渚涘簲鍟�'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� PO 鍗曞彿'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'orderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� ASN 鍗曞彿'
+      }
+    },
+    {
+      label: '璁″垝璺熻釜鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍒掕窡韪彿'
+      }
+    },
+    {
+      label: '琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍙�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '搴撳瓨鍗曚綅',
+      key: 'stockUnit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樺崟浣�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '涓婃姤鐘舵��',
+      key: 'ntyStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鏈笂鎶�', value: 0 },
+          { label: '宸蹭笂鎶�', value: 1 }
+        ]
+      }
+    },
+    {
+      label: '鍒涘缓鏃堕棿',
+      key: 'createTimeRange',
+      type: 'datetimerange',
+      props: {
+        clearable: true,
+        startPlaceholder: '寮�濮嬫椂闂�',
+        endPlaceholder: '缁撴潫鏃堕棿',
+        rangeSeparator: '鑷�'
+      }
+    },
+    {
+      label: '鏇存柊鏃堕棿',
+      key: 'updateTimeRange',
+      type: 'datetimerange',
+      props: {
+        clearable: true,
+        startPlaceholder: '寮�濮嬫椂闂�',
+        endPlaceholder: '缁撴潫鏃堕棿',
+        rangeSeparator: '鑷�'
+      }
+    }
+  ])
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchAsnOrderItemFullPage,
+      apiParams: buildAsnOrderItemPageQueryParams({
+        ...searchForm.value,
+        pageSize: DEFAULT_PAGE_SIZE,
+        orderBy: 'id desc'
+      }),
+      columnsFactory: () =>
+        createAsnOrderItemTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeAsnOrderItemRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(
+      buildAsnOrderItemPageQueryParams({
+        ...searchForm.value,
+        orderBy: 'id desc'
+      })
+    )
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createAsnOrderItemSearchState()
+    resetSearchParams()
+  }
+
+  async function openDetail(row) {
+    activeItemId.value = row.id
+    detailData.value = normalizeAsnOrderItemDetail(row)
+    detailDrawerVisible.value = true
+    await loadDetailResource()
+  }
+
+  async function loadDetailResource() {
+    if (!activeItemId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const detailResponse = await guardRequestWithMessage(
+        fetchGetAsnOrderItemDetail(activeItemId.value),
+        {},
+        {
+          timeoutMessage: '鏀惰揣鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+
+      detailData.value = normalizeAsnOrderItemDetail({
+        ...detailData.value,
+        ...detailResponse
+      })
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鏀惰揣鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function loadAllReportRows() {
+    const reportPageSize = Number(pagination.total) > 0 ? Number(pagination.total) : DEFAULT_PAGE_SIZE
+    const response = await guardRequestWithMessage(
+      fetchAsnOrderItemFullPage(
+        buildAsnOrderItemPageQueryParams({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: reportPageSize,
+          orderBy: 'id desc'
+        })
+      ),
+      { records: [], total: 0, current: 1, size: reportPageSize },
+      {
+        timeoutMessage: '鏀惰揣鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'asn-order-item.xlsx',
+    requestExport: async () => {
+      const records = await loadAllReportRows()
+      const ids = records
+        .map((item) => item?.id)
+        .filter((id) => id !== undefined && id !== null)
+
+      if (ids.length === 0) {
+        throw new Error('鏆傛棤鍙鍑虹殑鏁版嵁')
+      }
+
+      return fetchExportAsnOrderItemReport(
+        { ids },
+        {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }
+      )
+    },
+    resolvePrintRecords: async () => loadAllReportRows(),
+    buildPreviewRows: (records) => buildAsnOrderItemPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...ASN_ORDER_ITEM_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildAsnOrderItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation ||
+        ASN_ORDER_ITEM_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/asn-order-item/modules/asn-order-item-detail-drawer.vue b/rsf-design/src/views/orders/asn-order-item/modules/asn-order-item-detail-drawer.vue
new file mode 100644
index 0000000..edd2178
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-item/modules/asn-order-item-detail-drawer.vue
@@ -0,0 +1,107 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鏀惰揣鏄庣粏璇︽儏"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘缁勭粐">{{ detail.purchaseOrgName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘鍛�">{{ detail.purchaseUserName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟咺D">{{ detail.supplierId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ detail.supplierName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟鏃堕棿">{{ detail.businessTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寤鸿鐩爣浠�">{{ detail.targetWarehouseId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusTagType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="涓婃姤鐘舵��">
+            <ElTag :type="detail.ntyStatusTagType || 'info'" effect="light">
+              {{ detail.ntyStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="鏄庣粏淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="璁″垝琛屽彿">{{ detail.platItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁″垝璺熻釜鍙�">{{ detail.platWorkCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉″舰鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浜岀淮鐮�">{{ detail.qrcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍖呰鍚嶇О">{{ detail.packName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鍗曚綅">{{ detail.stockUnit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘鍗曚綅">{{ detail.purUnit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閫佽揣鏁伴噺">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲囪喘鏁伴噺">{{ detail.purQty ?? 0 }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐢熶骇鏃ユ湡">{{ detail.prodTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐ㄦ缁撴灉">{{ detail.isptResultText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉ユ簮浠撳簱">{{ detail.sourceWarehouseId || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+
+        <ElDescriptions
+          v-if="Object.keys(detail.extendFields || {}).length > 0"
+          title="鎵╁睍瀛楁"
+          :column="2"
+          border
+        >
+          <ElDescriptionsItem
+            v-for="[key, value] in Object.entries(detail.extendFields || {})"
+            :key="key"
+            :label="key"
+          >
+            {{ value || '--' }}
+          </ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'AsnOrderItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js b/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js
new file mode 100644
index 0000000..49a1d8c
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-log/asnOrderLogPage.helpers.js
@@ -0,0 +1,306 @@
+export const ASN_ORDER_LOG_REPORT_TITLE = '鍘嗗彶閫氱煡鍗曟姤琛�'
+export const ASN_ORDER_LOG_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const RLE_STATUS_META = {
+  0: { text: '姝e父', type: 'info' },
+  1: { text: '宸查噴鏀�', type: 'success' }
+}
+
+const NTY_STATUS_META = {
+  0: { text: '鏈笂鎶�', type: 'info' },
+  1: { text: '宸蹭笂鎶�', type: 'success' },
+  2: { text: '閮ㄥ垎涓婃姤', type: 'warning' }
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'danger' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeDateText(value) {
+  return normalizeText(value)
+}
+
+function getStatusMeta(value, map, fallbackText = '-') {
+  const numericValue = Number(value)
+  const meta = map[numericValue]
+  if (meta) {
+    return meta
+  }
+  const text = normalizeText(value)
+  if (!text) {
+    return { text: fallbackText, type: 'info' }
+  }
+  return { text, type: 'info' }
+}
+
+function normalizeTagText(value, map) {
+  const text = normalizeText(value)
+  if (text) {
+    return text
+  }
+  const numericValue = Number(value)
+  const meta = map[numericValue]
+  return meta?.text || '-'
+}
+
+export function createAsnOrderLogSearchState() {
+  return {
+    condition: '',
+    code: '',
+    poCode: '',
+    poId: '',
+    type: '',
+    wkType: '',
+    logisNo: '',
+    arrTime: '',
+    rleStatus: '',
+    ntyStatus: '',
+    exceStatus: '',
+    status: ''
+  }
+}
+
+export function buildAsnOrderLogSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    poCode: normalizeText(params.poCode),
+    poId:
+      params.poId !== undefined && params.poId !== null && params.poId !== ''
+        ? normalizeNumber(params.poId)
+        : void 0,
+    type: normalizeText(params.type),
+    wkType: normalizeText(params.wkType),
+    logisNo: normalizeText(params.logisNo),
+    arrTime: normalizeText(params.arrTime),
+    rleStatus:
+      params.rleStatus !== undefined && params.rleStatus !== null && params.rleStatus !== ''
+        ? normalizeNumber(params.rleStatus)
+        : void 0,
+    ntyStatus:
+      params.ntyStatus !== undefined && params.ntyStatus !== null && params.ntyStatus !== ''
+        ? normalizeNumber(params.ntyStatus)
+        : void 0,
+    exceStatus:
+      params.exceStatus !== undefined && params.exceStatus !== null && params.exceStatus !== ''
+        ? normalizeNumber(params.exceStatus)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
+  )
+}
+
+export function buildAsnOrderLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildAsnOrderLogSearchParams(params)
+  }
+}
+
+export function buildAsnOrderLogDetailQueryParams(params = {}) {
+  return {
+    logId: params.logId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeAsnOrderLogRow(record = {}) {
+  const rleStatusMeta = getStatusMeta(record.rleStatus, RLE_STATUS_META)
+  const ntyStatusMeta = getStatusMeta(record.ntyStatus, NTY_STATUS_META)
+  const statusMeta = getStatusMeta(record.status, STATUS_META)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    asnId: record.asnId ?? '--',
+    code: normalizeText(record.code) || '--',
+    poCode: normalizeText(record.poCode) || '--',
+    poId: record.poId ?? '--',
+    type: normalizeText(record.type) || '',
+    typeText: normalizeTagText(record['type$'] || record.typeText || record.type, {}),
+    wkType: normalizeText(record.wkType) || '',
+    wkTypeText: normalizeTagText(record['wkType$'] || record.wkTypeText || record.wkType, {}),
+    anfme: record.anfme ?? '--',
+    qty: record.qty ?? '--',
+    logisNo: normalizeText(record.logisNo) || '--',
+    arrTime: record.arrTime ?? null,
+    arrTimeText: normalizeDateText(record['arrTime$'] || record.arrTime) || '--',
+    rleStatus: record.rleStatus ?? '--',
+    rleStatusText: normalizeTagText(record['rleStatus$'] || rleStatusMeta.text, RLE_STATUS_META),
+    rleStatusTagType: rleStatusMeta.type,
+    ntyStatus: record.ntyStatus ?? '--',
+    ntyStatusText: normalizeTagText(record['ntyStatus$'] || ntyStatusMeta.text, NTY_STATUS_META),
+    ntyStatusTagType: ntyStatusMeta.type,
+    exceStatus: record.exceStatus ?? '--',
+    exceStatusText: normalizeTagText(record['exceStatus$'] || record.exceStatusText || record.exceStatus, {}),
+    status: record.status ?? '--',
+    statusText: normalizeTagText(record['status$'] || statusMeta.text, STATUS_META),
+    statusType: statusMeta.type,
+    createByText: normalizeText(record['createBy$'] || record.createByText || '') || '--',
+    createTimeText: normalizeDateText(record['createTime$'] || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText || '') || '--',
+    updateTimeText: normalizeDateText(record['updateTime$'] || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function normalizeAsnOrderItemLogRow(record = {}) {
+  const ntyStatusMeta = getStatusMeta(record.ntyStatus, NTY_STATUS_META)
+  const statusMeta = getStatusMeta(record.status, STATUS_META)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    logId: record.logId ?? '--',
+    asnItemId: record.asnItemId ?? '--',
+    asnId: record.asnId ?? '--',
+    asnCode: normalizeText(record.asnCode) || '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    poDetlId: record.poDetlId ?? '--',
+    poCode: normalizeText(record.poCode) || '--',
+    fieldsIndex: normalizeText(record.fieldsIndex) || '--',
+    matnrId: record.matnrId ?? '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    maktx: normalizeText(record.maktx) || '--',
+    anfme: record.anfme ?? '--',
+    stockUnit: normalizeText(record.stockUnit) || '--',
+    purQty: record.purQty ?? '--',
+    purUnit: normalizeText(record.purUnit) || '--',
+    qty: record.qty ?? '--',
+    splrCode: normalizeText(record.splrCode) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    splrName: normalizeText(record.splrName) || '--',
+    qrcode: normalizeText(record.qrcode) || '--',
+    trackCode: normalizeText(record.trackCode) || '--',
+    barcode: normalizeText(record.barcode) || '--',
+    packName: normalizeText(record.packName) || '--',
+    ntyStatus: record.ntyStatus ?? '--',
+    ntyStatusText: normalizeTagText(record['ntyStatus$'] || ntyStatusMeta.text, NTY_STATUS_META),
+    ntyStatusTagType: ntyStatusMeta.type,
+    status: record.status ?? '--',
+    statusText: normalizeTagText(record['status$'] || statusMeta.text, STATUS_META),
+    statusType: statusMeta.type,
+    createByText: normalizeText(record['createBy$'] || record.createByText || '') || '--',
+    createTimeText: normalizeDateText(record['createTime$'] || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText || '') || '--',
+    updateTimeText: normalizeDateText(record['updateTime$'] || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildAsnOrderLogPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeAsnOrderLogRow(record))
+}
+
+export function buildAsnOrderLogReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = ASN_ORDER_LOG_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: ASN_ORDER_LOG_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...ASN_ORDER_LOG_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getAsnOrderLogRleStatusOptions() {
+  return [
+    { label: '姝e父', value: 0 },
+    { label: '宸查噴鏀�', value: 1 }
+  ]
+}
+
+export function getAsnOrderLogNtyStatusOptions() {
+  return [
+    { label: '鏈笂鎶�', value: 0 },
+    { label: '宸蹭笂鎶�', value: 1 },
+    { label: '閮ㄥ垎涓婃姤', value: 2 }
+  ]
+}
+
+export function getAsnOrderLogStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getAsnOrderLogTypeOptions() {
+  return []
+}
+
+export function getAsnOrderLogWkTypeOptions() {
+  return []
+}
+
+export function getAsnOrderLogExceStatusOptions() {
+  return []
+}
+
+export function resolveDictOptions(records = [], options = {}) {
+  const { group } = options
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .filter((item) => {
+      if (!item || typeof item !== 'object') {
+        return false
+      }
+      if (group === undefined) {
+        return true
+      }
+      return normalizeText(item.group) === normalizeText(group)
+    })
+    .map((item) => {
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === undefined || value === null || value === '') {
+        return null
+      }
+      return {
+        value: normalizeText(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || value)
+      }
+    })
+    .filter(Boolean)
+}
diff --git a/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js b/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js
new file mode 100644
index 0000000..417d2ae
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-log/asnOrderLogTable.columns.js
@@ -0,0 +1,313 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+function buildTagRenderer(textKey, typeKey) {
+  return (row) =>
+    h(
+      ElTag,
+      {
+        type: row?.[typeKey] || 'info',
+        effect: 'light'
+      },
+      () => row?.[textKey] || '--'
+    )
+}
+
+export function createAsnOrderLogTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: 'ASN鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'poCode',
+      label: 'PO鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.poCode || '--'
+    },
+    {
+      prop: 'poId',
+      label: 'PO鍗旾D',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.poId ?? '--'
+    },
+    {
+      prop: 'typeText',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeText || '--'
+    },
+    {
+      prop: 'wkTypeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeText || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '閫佽揣鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'logisNo',
+      label: '鐗╂祦鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.logisNo || '--'
+    },
+    {
+      prop: 'arrTimeText',
+      label: '棰勮鍒拌揪鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.arrTimeText || '--'
+    },
+    {
+      prop: 'rleStatusText',
+      label: '閲婃斁鐘舵��',
+      width: 110,
+      align: 'center',
+      formatter: buildTagRenderer('rleStatusText', 'rleStatusTagType')
+    },
+    {
+      prop: 'ntyStatusText',
+      label: '涓婃姤鐘舵��',
+      width: 110,
+      align: 'center',
+      formatter: buildTagRenderer('ntyStatusText', 'ntyStatusTagType')
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.exceStatusText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: buildTagRenderer('statusText', 'statusType')
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
+
+export function createAsnOrderItemLogColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'asnCode',
+      label: 'ASN鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.asnCode || '--'
+    },
+    {
+      prop: 'platItemId',
+      label: '骞冲彴琛屽彿',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platItemId || '--'
+    },
+    {
+      prop: 'poDetlId',
+      label: 'PO鍗曟槑缁咺D',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.poDetlId ?? '--'
+    },
+    {
+      prop: 'poCode',
+      label: 'PO鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.poCode || '--'
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鍔ㄦ�佸瓧娈电储寮�',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.fieldsIndex || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '閫佽揣鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'stockUnit',
+      label: '搴撳瓨鍗曚綅',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.stockUnit || '--'
+    },
+    {
+      prop: 'purQty',
+      label: '閲囪喘鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.purQty ?? '--'
+    },
+    {
+      prop: 'purUnit',
+      label: '閲囪喘鍗曚綅',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.purUnit || '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrCode || '--'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrBatch || '--'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrName || '--'
+    },
+    {
+      prop: 'qrcode',
+      label: '浜岀淮鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.qrcode || '--'
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.trackCode || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鏉″舰鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'packName',
+      label: '鍖呰鍚嶇О',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.packName || '--'
+    },
+    {
+      prop: 'ntyStatusText',
+      label: '涓婃姤鐘舵��',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.ntyStatusText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => row.statusText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/asn-order-log/index.vue b/rsf-design/src/views/orders/asn-order-log/index.vue
new file mode 100644
index 0000000..968c48a
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-log/index.vue
@@ -0,0 +1,468 @@
+<template>
+  <div class="asn-order-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <AsnOrderLogDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :items-loading="detailItemsLoading"
+      :detail="detailData"
+      :item-rows="detailItemRows"
+      :item-columns="detailItemColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  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 { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    buildAsnOrderLogDetailQueryParams,
+    buildAsnOrderLogPageQueryParams,
+    buildAsnOrderLogPrintRows,
+    buildAsnOrderLogReportMeta,
+    buildAsnOrderLogSearchParams,
+    createAsnOrderLogSearchState,
+    getAsnOrderLogExceStatusOptions,
+    getAsnOrderLogNtyStatusOptions,
+    getAsnOrderLogRleStatusOptions,
+    getAsnOrderLogStatusOptions,
+    normalizeAsnOrderItemLogRow,
+    normalizeAsnOrderLogRow,
+    resolveDictOptions,
+    ASN_ORDER_LOG_REPORT_STYLE,
+    ASN_ORDER_LOG_REPORT_TITLE
+  } from './asnOrderLogPage.helpers'
+  import {
+    fetchAsnOrderItemLogPage,
+    fetchAsnOrderLogPage,
+    fetchExportAsnOrderLogReport,
+    fetchGetAsnOrderLogDetail,
+    fetchGetAsnOrderLogMany
+  } from '@/api/asn-order-log'
+  import AsnOrderLogDetailDrawer from './modules/asn-order-log-detail-drawer.vue'
+  import {
+    createAsnOrderItemLogColumns,
+    createAsnOrderLogTableColumns
+  } from './asnOrderLogTable.columns'
+
+  defineOptions({ name: 'AsnOrderLog' })
+
+  const userStore = useUserStore()
+  const reportTitle = ASN_ORDER_LOG_REPORT_TITLE
+  const searchForm = ref(createAsnOrderLogSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailItemsLoading = ref(false)
+  const detailData = ref({})
+  const detailItemRows = ref([])
+  const activeLogId = ref(null)
+  const typeOptions = ref([])
+  const wkTypeOptions = ref([])
+  const exceStatusOptions = ref([])
+  const detailItemColumns = createAsnOrderItemLogColumns()
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildAsnOrderLogSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿/PO鍗曞彿/鐗╂祦鍗曞彿'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿'
+      }
+    },
+    {
+      label: 'PO鍗旾D',
+      key: 'poId',
+      type: 'number',
+      props: {
+        min: 0,
+        controls: false,
+        placeholder: '璇疯緭鍏O鍗旾D'
+      }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: typeOptions.value
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: wkTypeOptions.value
+      }
+    },
+    {
+      label: '鐗╂祦鍗曞彿',
+      key: 'logisNo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿娴佸崟鍙�'
+      }
+    },
+    {
+      label: '棰勮鍒拌揪鏃堕棿',
+      key: 'arrTime',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鏃ユ湡'
+      }
+    },
+    {
+      label: '閲婃斁鐘舵��',
+      key: 'rleStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getAsnOrderLogRleStatusOptions()
+      }
+    },
+    {
+      label: '涓婃姤鐘舵��',
+      key: 'ntyStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getAsnOrderLogNtyStatusOptions()
+      }
+    },
+    {
+      label: '鎵ц鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: exceStatusOptions.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getAsnOrderLogStatusOptions()
+      }
+    }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  function openDetail(row) {
+    activeLogId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    loadDetailResources()
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchAsnOrderLogPage,
+      apiParams: buildAsnOrderLogPageQueryParams(searchForm.value),
+      columnsFactory: () => createAsnOrderLogTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeAsnOrderLogRow(item)) : []
+    }
+  })
+
+  async function loadDetailResources() {
+    if (!activeLogId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    detailItemsLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(
+          fetchGetAsnOrderLogDetail(activeLogId.value),
+          {},
+          {
+            timeoutMessage: '鍘嗗彶閫氱煡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+          }
+        ),
+        guardRequestWithMessage(
+          fetchAsnOrderItemLogPage(
+            buildAsnOrderLogDetailQueryParams({
+              logId: activeLogId.value,
+              current: detailPagination.current,
+              pageSize: detailPagination.size
+            })
+          ),
+          { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
+          {
+            timeoutMessage: '鍘嗗彶閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+          }
+        )
+      ])
+
+      detailData.value = normalizeAsnOrderLogRow(detailResponse || {})
+      const itemResult = defaultResponseAdapter(itemResponse)
+      detailItemRows.value = Array.isArray(itemResult.records)
+        ? itemResult.records.map((item) => normalizeAsnOrderItemLogRow(item))
+        : []
+      updatePaginationState(
+        detailPagination,
+        itemResult,
+        detailPagination.current,
+        detailPagination.size
+      )
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailItemRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇鍘嗗彶閫氱煡鍗曡鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+      detailItemsLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildAsnOrderLogSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createAsnOrderLogSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  function buildPreviewDialogMeta(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: {
+        ...ASN_ORDER_LOG_REPORT_STYLE
+      }
+    }
+  }
+
+  async function resolvePrintRecords(payload) {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetAsnOrderLogMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchAsnOrderLogPage({
+        ...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: 'asn-order-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportAsnOrderLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildAsnOrderLogPrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildAsnOrderLogReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || ASN_ORDER_LOG_REPORT_STYLE.orientation
+    })
+  )
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_order_type',
+        status: 1
+      }),
+      { records: [] },
+      {
+        timeoutMessage: '鍗曟嵁绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    typeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadWkTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_business_type',
+        group: 1,
+        status: 1
+      }),
+      { records: [] },
+      {
+        timeoutMessage: '涓氬姟绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    wkTypeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records, {
+      group: 1
+    })
+  }
+
+  async function loadExceStatusOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_asn_exce_status',
+        status: 1
+      }),
+      { records: [] },
+      {
+        timeoutMessage: '鎵ц鐘舵�侀�夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      }
+    )
+    exceStatusOptions.value = resolveDictOptions(defaultResponseAdapter(response).records)
+  }
+
+  onMounted(async () => {
+    await Promise.allSettled([loadTypeOptions(), loadWkTypeOptions(), loadExceStatusOptions()])
+  })
+</script>
diff --git a/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue
new file mode 100644
index 0000000..529bd8b
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order-log/modules/asn-order-log-detail-drawer.vue
@@ -0,0 +1,78 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍘嗗彶閫氱煡鍗曡鎯�"
+    size="88%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="asn-order-log-detail-scroll">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN涓诲崟ID">{{ detail.asnId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="PO鍗旾D">{{ detail.poId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閫佽揣鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂祦鍗曞彿">{{ detail.logisNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="棰勮鍒拌揪鏃堕棿">{{ detail.arrTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閲婃斁鐘舵��">{{ detail.rleStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓婃姤鐘舵��">{{ detail.ntyStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</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 || itemsLoading" @click="$emit('refresh')">鍒锋柊</ElButton>
+        </div>
+
+        <ElCard shadow="never" class="border border-[var(--art-border-color)]">
+          <ArtTable
+            :loading="itemsLoading"
+            :data="itemRows"
+            :columns="itemColumns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'AsnOrderLogDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { 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', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
+
+<style scoped>
+  .asn-order-log-detail-scroll {
+    height: calc(100vh - 120px);
+  }
+</style>
diff --git a/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
new file mode 100644
index 0000000..0edd1f6
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
@@ -0,0 +1,260 @@
+export const ASN_ORDER_REPORT_TITLE = '鍏ュ簱閫氱煡鍗曟姤琛�'
+export const ASN_ORDER_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const ASN_ORDER_STATUS_MAP = {
+  0: { label: '鏈墽琛�', tagType: 'info' },
+  1: { label: '鎵ц涓�', tagType: 'warning' },
+  2: { label: '鏀惰揣瀹屾垚', tagType: 'success' },
+  3: { label: '浠诲姟鎵ц涓�', tagType: 'warning' },
+  4: { label: '宸插畬鎴�', tagType: 'success' },
+  8: { label: '鍙栨秷', tagType: 'danger' },
+  9: { label: '宸插叧闂�', tagType: 'info' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function normalizeNullableNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return '-'
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : '-'
+}
+
+function getStatusConfig(status, statusText) {
+  const numericStatus = Number(status)
+  const fallback = ASN_ORDER_STATUS_MAP[numericStatus] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+export function createAsnOrderSearchState() {
+  return {
+    condition: '',
+    code: '',
+    poCode: '',
+    wkType: '',
+    exceStatus: '',
+    supplierName: '',
+    purchaseUserName: ''
+  }
+}
+
+export function createPurchaseFilterSearchState() {
+  return {
+    condition: '',
+    code: '',
+    source: '',
+    supplierName: ''
+  }
+}
+
+export function buildAsnOrderSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'poCode', 'wkType', 'supplierName', 'purchaseUserName'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+
+  return result
+}
+
+export function buildAsnOrderPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildAsnOrderSearchParams(params)
+  }
+}
+
+export function buildAsnOrderDetailQueryParams(params = {}) {
+  return {
+    orderId: params.orderId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function buildPurchaseFilterSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'source', 'supplierName'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+  return result
+}
+
+export function buildPurchaseFilterPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildPurchaseFilterSearchParams(params)
+  }
+}
+
+export function normalizeAsnOrderRow(record = {}) {
+  const statusConfig = getStatusConfig(record.exceStatus, record['exceStatus$'])
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: record.code || '-',
+    poCode: record.poCode || '-',
+    wkTypeLabel: record['wkType$'] || record.wkType || '-',
+    orderTypeLabel: record['type$'] || record.type || '-',
+    exceStatusText: statusConfig.label,
+    exceStatusTagType: statusConfig.tagType,
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    purchaseOrgName: record.purchaseOrgName || '-',
+    purchaseUserName: record.purchaseUserName || '-',
+    supplierId: record.supplierId || '-',
+    supplierName: record.supplierName || '-',
+    businessTimeText: record['businessTime$'] || record.businessTime || '-',
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    memo: record.memo || '-',
+    canComplete: Number(record.exceStatus) === 1
+  }
+}
+
+export function normalizeAsnOrderItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    orderCode: record.orderCode || '-',
+    poCode: record.poCode || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    platItemId: record.platItemId || '-',
+    splrBatch: record.splrBatch || '-',
+    splrCode: record.splrCode || '-',
+    splrName: record.splrName || record.supplierName || '-',
+    stockUnit: record.stockUnit || record.unit || '-',
+    purUnit: record.purUnit || '-',
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    memo: record.memo || '-',
+    prodTimeText: record['prodTime$'] || record.prodTime || '-'
+  }
+}
+
+export function normalizePurchaseRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: record.code || '-',
+    source: record.source || '-',
+    wkTypeLabel: record['wkType$'] || record.wkType || '-',
+    typeLabel: record['type$'] || record.type || '-',
+    supplierName: record.supplierName || '-',
+    purchaseUserName: record.purchaseUserName || '-',
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    remainingQty: Math.max(normalizeNumber(record.anfme) - normalizeNumber(record.qty), 0),
+    exceStatusText: record['exceStatus$'] || '-',
+    businessTimeText: record['businessTime$'] || record.businessTime || '-'
+  }
+}
+
+export function normalizePurchaseItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    platItemId: record.platItemId || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.matnrName || record.maktx || '-',
+    splrName: record.splrName || '-',
+    splrCode: record.splrCode || '-',
+    splrBatch: record.splrBatch || '-',
+    unit: record.unit || record.stockUnit || '-',
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    nromQty: normalizeNullableNumber(record.nromQty)
+  }
+}
+
+export function buildAsnOrderPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeAsnOrderRow(record))
+}
+
+export function buildAsnOrderReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = ASN_ORDER_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: ASN_ORDER_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...ASN_ORDER_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getAsnOrderStatusOptions() {
+  return Object.entries(ASN_ORDER_STATUS_MAP).map(([value, item]) => ({
+    label: item.label,
+    value: Number(value)
+  }))
+}
+
+export function getAsnOrderActionList(row = {}) {
+  const normalizedRow = normalizeAsnOrderRow(row)
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    },
+    {
+      key: 'print',
+      label: '鎵撳嵃',
+      icon: 'ri:printer-line'
+    },
+    {
+      key: 'complete',
+      label: '瀹屾垚',
+      icon: 'ri:check-line',
+      color: 'var(--el-color-success)',
+      disabled: !normalizedRow.canComplete
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js b/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
new file mode 100644
index 0000000..569f7e9
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/asnOrderTable.columns.js
@@ -0,0 +1,296 @@
+import { h } from 'vue'
+import { ElButton, ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import { getAsnOrderActionList } from './asnOrderPage.helpers'
+
+export function createAsnOrderTableColumns({ handleActionClick }) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: 'ASN鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'poCode',
+      label: 'PO鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '搴旀敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'supplierName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'purchaseUserName',
+      label: '閲囪喘鍛�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鍗曟嵁鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.exceStatusTagType || 'info', effect: 'light' },
+          () => row.exceStatusText
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getAsnOrderActionList(row),
+          onClick: (item) => handleActionClick(item, row)
+        })
+    }
+  ]
+}
+
+export function createAsnOrderDetailItemColumns() {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platItemId',
+      label: 'PO琛屽彿',
+      width: 110
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockUnit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '搴旀敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    }
+  ]
+}
+
+export function createAsnOrderPurchaseColumns({ handleChoosePurchase }) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'code',
+      label: 'PO鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'source',
+      label: '鏉ユ簮',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'supplierName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'purchaseUserName',
+      label: '閲囪喘鍛�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'remainingQty',
+      label: '鍙缓鍗曟暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'exceStatusText',
+      label: 'PO鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleChoosePurchase(row)
+        })
+    }
+  ]
+}
+
+export function createAsnOrderPurchaseItemColumns() {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'platItemId',
+      label: 'PO琛屽彿',
+      width: 110
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'anfme',
+      label: '閲囪喘鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸茬敓鎴怉SN鏁伴噺',
+      width: 130,
+      align: 'right'
+    },
+    {
+      prop: 'nromQty',
+      label: '鏀惰揣鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'selectAction',
+      label: '鎿嶄綔鎻愮ず',
+      minWidth: 120,
+      formatter: () =>
+        h(
+          ElButton,
+          {
+            link: true,
+            type: 'primary',
+            class: '!px-0'
+          },
+          () => '闅� PO 鍏ㄩ噺寤哄崟'
+        )
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/asn-order/index.vue b/rsf-design/src/views/orders/asn-order/index.vue
new file mode 100644
index 0000000..1ef8bd6
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/index.vue
@@ -0,0 +1,399 @@
+<template>
+  <div class="asn-order-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton type="primary" @click="poDialogVisible = true">鎸塒O寤哄崟</ElButton>
+            <ListExportPrint
+              :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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <AsnOrderDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+
+    <AsnOrderCreateByPoDialog v-model:visible="poDialogVisible" @success="handlePoCreateSuccess" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchAsnOrderItemPage,
+    fetchAsnOrderPage,
+    fetchCompleteAsnOrder,
+    fetchExportAsnOrderReport,
+    fetchGetAsnOrderDetail,
+    fetchGetAsnOrderMany
+  } from '@/api/asn-order'
+  import AsnOrderDetailDrawer from './modules/asn-order-detail-drawer.vue'
+  import AsnOrderCreateByPoDialog from './modules/asn-order-create-by-po-dialog.vue'
+  import {
+    createAsnOrderDetailItemColumns,
+    createAsnOrderTableColumns
+  } from './asnOrderTable.columns'
+  import {
+    ASN_ORDER_REPORT_STYLE,
+    ASN_ORDER_REPORT_TITLE,
+    buildAsnOrderDetailQueryParams,
+    buildAsnOrderPageQueryParams,
+    buildAsnOrderPrintRows,
+    buildAsnOrderReportMeta,
+    buildAsnOrderSearchParams,
+    createAsnOrderSearchState,
+    getAsnOrderStatusOptions,
+    normalizeAsnOrderItemRow,
+    normalizeAsnOrderRow
+  } from './asnOrderPage.helpers'
+
+  defineOptions({ name: 'AsnOrder' })
+
+  const userStore = useUserStore()
+  const reportTitle = ASN_ORDER_REPORT_TITLE
+  const searchForm = ref(createAsnOrderSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activeOrderId = ref(null)
+  const poDialogVisible = ref(false)
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildAsnOrderSearchParams(searchForm.value))
+  const detailColumns = computed(() => createAsnOrderDetailItemColumns())
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� ASN 鍗曞彿/PO 鍗曞彿/渚涘簲鍟�'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� ASN 鍗曞彿'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� PO 鍗曞彿'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�'
+      }
+    },
+    {
+      label: '鍗曟嵁鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getAsnOrderStatusOptions()
+      }
+    },
+    {
+      label: '渚涘簲鍟�',
+      key: 'supplierName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢'
+      }
+    },
+    {
+      label: '閲囪喘鍛�',
+      key: 'purchaseUserName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ噰璐憳'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    activeOrderId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) {
+      return
+    }
+
+    try {
+      if (action.key === 'view') {
+        await openDetail(row)
+        return
+      }
+
+      if (action.key === 'print') {
+        await handlePrint({ ids: [row.id], pageSize: 1 })
+        return
+      }
+
+      if (action.key === 'complete') {
+        await ElMessageBox.confirm(`纭畾瀹屾垚鍏ュ簱閫氱煡鍗� ${row.code || ''} 鍚楋紵`, '瀹屾垚纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchCompleteAsnOrder(row.id)
+        ElMessage.success('鍏ュ簱閫氱煡鍗曞凡瀹屾垚')
+        await refreshData()
+        if (detailDrawerVisible.value && activeOrderId.value === row.id) {
+          await loadDetailResources()
+        }
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') {
+        return
+      }
+      ElMessage.error(error?.message || '鍏ュ簱閫氱煡鍗曟搷浣滃け璐�')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshSoft,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchAsnOrderPage,
+      apiParams: buildAsnOrderPageQueryParams(searchForm.value),
+      columnsFactory: () =>
+        createAsnOrderTableColumns({
+          handleActionClick
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeAsnOrderRow(item)) : []
+    }
+  })
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadDetailResources() {
+    if (!activeOrderId.value) {
+      return
+    }
+
+    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,
+            current: detailPagination.current,
+            size: detailPagination.size
+          },
+          {
+            timeoutMessage: '鍏ュ簱閫氱煡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+          }
+        )
+      ])
+
+      detailData.value = normalizeAsnOrderRow(detailResponse)
+      detailTableData.value = Array.isArray(itemResponse?.records)
+        ? itemResponse.records.map((item) => normalizeAsnOrderItemRow(item))
+        : []
+      updatePaginationState(
+        detailPagination,
+        itemResponse,
+        detailPagination.current,
+        detailPagination.size
+      )
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildAsnOrderSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createAsnOrderSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  async function handlePoCreateSuccess() {
+    await refreshSoft()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'asn-order.xlsx',
+    requestExport: (payload) =>
+      fetchExportAsnOrderReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetAsnOrderMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchAsnOrderPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize:
+            Number(pagination.total) > 0
+              ? Number(pagination.total)
+              : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildAsnOrderPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...ASN_ORDER_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildAsnOrderReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || ASN_ORDER_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue b/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
new file mode 100644
index 0000000..f5339e0
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-create-by-po-dialog.vue
@@ -0,0 +1,343 @@
+<template>
+  <ElDialog
+    :model-value="visible"
+    title="鎸塒O寤哄崟"
+    width="90%"
+    top="4vh"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex flex-col gap-4">
+      <ArtSearchBar
+        v-model="searchForm"
+        :items="searchItems"
+        :showExpand="true"
+        @search="handleSearch"
+        @reset="handleReset"
+      />
+
+      <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
+        <ElCard class="art-table-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between gap-3">
+              <div class="text-sm font-medium">鍙缓鍗� PO 鍒楄〃</div>
+              <ElButton :loading="purchaseLoading" @click="refreshPurchaseData">鍒锋柊</ElButton>
+            </div>
+          </template>
+
+          <ArtTable
+            :loading="purchaseLoading"
+            :data="purchaseData"
+            :columns="purchaseColumns"
+            :pagination="purchasePagination"
+            @pagination:size-change="handlePurchaseSizeChange"
+            @pagination:current-change="handlePurchaseCurrentChange"
+          />
+        </ElCard>
+
+        <ElCard class="art-table-card" shadow="never">
+          <template #header>
+            <div class="flex items-center justify-between gap-3">
+              <div class="flex flex-col">
+                <span class="text-sm font-medium">PO 鏄庣粏棰勮</span>
+                <span class="text-xs text-[var(--art-gray-600)]">
+                  {{
+                    selectedPurchase.code
+                      ? `褰撳墠閫夋嫨锛�${selectedPurchase.code}`
+                      : '璇峰厛鍦ㄥ乏渚ч�夋嫨涓�涓� PO 鍗�'
+                  }}
+                </span>
+              </div>
+              <ElButton
+                :disabled="!selectedPurchase.id"
+                :loading="itemLoading"
+                @click="reloadSelectedPurchaseItems"
+              >
+                鍒锋柊鏄庣粏
+              </ElButton>
+            </div>
+          </template>
+
+          <ArtTable :loading="itemLoading" :data="purchaseItems" :columns="purchaseItemColumns" />
+        </ElCard>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="flex items-center justify-between gap-3">
+        <div class="text-xs text-[var(--art-gray-600)]">
+          {{
+            selectedPurchase.id
+              ? `灏嗘寜 PO ${selectedPurchase.code} 鐨� ${purchaseItems.length} 鏉℃槑缁嗙敓鎴愬叆搴撻�氱煡鍗昤
+              : '璇烽�夋嫨涓�涓彲寤哄崟鐨� PO 鍗�'
+          }}
+        </div>
+        <ElSpace>
+          <ElButton @click="handleVisibleChange(false)">鍙栨秷</ElButton>
+          <ElButton
+            type="primary"
+            :loading="submitLoading"
+            :disabled="!selectedPurchase.id"
+            @click="handleConfirm"
+          >
+            鐢熸垚鍏ュ簱閫氱煡鍗�
+          </ElButton>
+        </ElSpace>
+      </div>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, ref, watch } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchCreateAsnOrderByPurchase,
+    fetchPurchaseFilterPage,
+    fetchPurchaseItemPage
+  } from '@/api/asn-order'
+  import {
+    buildPurchaseFilterPageQueryParams,
+    buildPurchaseFilterSearchParams,
+    createPurchaseFilterSearchState,
+    normalizePurchaseItemRow,
+    normalizePurchaseRow
+  } from '../asnOrderPage.helpers'
+  import {
+    createAsnOrderPurchaseColumns,
+    createAsnOrderPurchaseItemColumns
+  } from '../asnOrderTable.columns'
+
+  defineOptions({ name: 'AsnOrderCreateByPoDialog' })
+
+  // 鐪熷疄鍚庣娴佺▼:
+  // 1. /purchase/filters/page 鏌ヨ鍙缓鍗� PO
+  // 2. /purchaseItem/page 鍔犺浇 PO 鏄庣粏
+  // 3. /asnOrder/purchases/save 鐢熸垚 ASN
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false }
+  })
+
+  const emit = defineEmits(['update:visible', 'success'])
+
+  const searchForm = ref(createPurchaseFilterSearchState())
+  const selectedPurchase = ref({})
+  const purchaseItems = ref([])
+  const itemLoading = ref(false)
+  const submitLoading = ref(false)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� PO 鍗曞彿/鏉ユ簮/渚涘簲鍟�'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� PO 鍗曞彿'
+      }
+    },
+    {
+      label: '鏉ユ簮',
+      key: 'source',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潵婧�'
+      }
+    },
+    {
+      label: '渚涘簲鍟�',
+      key: 'supplierName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢'
+      }
+    }
+  ])
+
+  async function handleChoosePurchase(row) {
+    selectedPurchase.value = row
+    await loadPurchaseItems(row.id)
+  }
+
+  const purchaseItemColumns = computed(() => createAsnOrderPurchaseItemColumns())
+
+  const {
+    data: purchaseData,
+    loading: purchaseLoading,
+    pagination: purchasePagination,
+    columns: purchaseColumns,
+    replaceSearchParams,
+    handleSizeChange: handlePurchaseSizeChange,
+    handleCurrentChange: handlePurchaseCurrentChange,
+    refreshData: refreshPurchaseData,
+    getData: getPurchaseData
+  } = useTable({
+    core: {
+      apiFn: fetchPurchaseFilterPage,
+      apiParams: buildPurchaseFilterPageQueryParams(searchForm.value),
+      immediate: false,
+      columnsFactory: () =>
+        createAsnOrderPurchaseColumns({
+          handleChoosePurchase
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizePurchaseRow(item)) : []
+    }
+  })
+
+  watch(
+    () => props.visible,
+    async (visible) => {
+      if (!visible) {
+        selectedPurchase.value = {}
+        purchaseItems.value = []
+        submitLoading.value = false
+        return
+      }
+
+      searchForm.value = createPurchaseFilterSearchState()
+      replaceSearchParams(buildPurchaseFilterSearchParams(searchForm.value))
+      await getPurchaseData()
+    }
+  )
+
+  watch(
+    () => purchaseData.value,
+    (rows) => {
+      if (!selectedPurchase.value?.id || !Array.isArray(rows)) {
+        return
+      }
+      const matched = rows.find((item) => item.id === selectedPurchase.value.id)
+      if (matched) {
+        selectedPurchase.value = matched
+      }
+    }
+  )
+
+  async function loadPurchaseItems(purchaseId) {
+    if (!purchaseId) {
+      purchaseItems.value = []
+      return
+    }
+
+    itemLoading.value = true
+    try {
+      const firstPage = await guardRequestWithMessage(
+        fetchPurchaseItemPage({
+          purchaseId,
+          current: 1,
+          pageSize: 200
+        }),
+        {
+          records: [],
+          total: 0,
+          current: 1,
+          size: 200
+        },
+        {
+          timeoutMessage: 'PO 鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+
+      const firstRecords = Array.isArray(firstPage?.records) ? firstPage.records : []
+      const total = Number(firstPage?.total || firstRecords.length)
+
+      if (total > firstRecords.length) {
+        const fullPage = await guardRequestWithMessage(
+          fetchPurchaseItemPage({
+            purchaseId,
+            current: 1,
+            pageSize: total
+          }),
+          {
+            records: firstRecords,
+            total,
+            current: 1,
+            size: total
+          },
+          {
+            timeoutMessage: 'PO 鍏ㄩ噺鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+          }
+        )
+        purchaseItems.value = Array.isArray(fullPage?.records)
+          ? fullPage.records.map((item) => normalizePurchaseItemRow(item))
+          : firstRecords.map((item) => normalizePurchaseItemRow(item))
+        return
+      }
+
+      purchaseItems.value = firstRecords.map((item) => normalizePurchaseItemRow(item))
+    } finally {
+      itemLoading.value = false
+    }
+  }
+
+  async function reloadSelectedPurchaseItems() {
+    if (!selectedPurchase.value?.id) {
+      return
+    }
+    await loadPurchaseItems(selectedPurchase.value.id)
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildPurchaseFilterSearchParams(searchForm.value))
+    getPurchaseData()
+  }
+
+  async function handleReset() {
+    searchForm.value = createPurchaseFilterSearchState()
+    selectedPurchase.value = {}
+    purchaseItems.value = []
+    replaceSearchParams(buildPurchaseFilterSearchParams(searchForm.value))
+    await getPurchaseData()
+  }
+
+  async function handleConfirm() {
+    if (!selectedPurchase.value?.id) {
+      ElMessage.warning('璇峰厛閫夋嫨涓�涓� PO 鍗�')
+      return
+    }
+    if (!purchaseItems.value.length) {
+      ElMessage.warning('褰撳墠 PO 鍗曟病鏈夊彲寤哄崟鏄庣粏')
+      return
+    }
+
+    submitLoading.value = true
+    try {
+      await fetchCreateAsnOrderByPurchase({
+        purchaseId: selectedPurchase.value.id,
+        items: purchaseItems.value.map((item) => ({ ...item }))
+      })
+      ElMessage.success('宸叉牴鎹� PO 鍗曠敓鎴愬叆搴撻�氱煡鍗�')
+      emit('success')
+      handleVisibleChange(false)
+    } catch (error) {
+      ElMessage.error(error?.message || '鎸� PO 寤哄崟澶辫触')
+    } finally {
+      submitLoading.value = false
+    }
+  }
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
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
new file mode 100644
index 0000000..f9c0ebe
--- /dev/null
+++ b/rsf-design/src/views/orders/asn-order/modules/asn-order-detail-drawer.vue
@@ -0,0 +1,73 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍏ュ簱閫氱煡鍗曡鎯�"
+    size="88%"
+    @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>
+
+      <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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="$emit('size-change', $event)"
+        @pagination:current-change="$emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'AsnOrderDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check-diff-item/checkDiffItemPage.helpers.js b/rsf-design/src/views/orders/check-diff-item/checkDiffItemPage.helpers.js
new file mode 100644
index 0000000..f3a4e57
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff-item/checkDiffItemPage.helpers.js
@@ -0,0 +1,91 @@
+const CHECK_DIFF_ITEM_REPORT_TITLE = '鐩樼偣宸紓鏄庣粏鎶ヨ〃'
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
+  )
+}
+
+export function createCheckDiffItemSearchState() {
+  return {
+    condition: '',
+    checkId: '',
+    orderCode: '',
+    matnrCode: '',
+    barcode: '',
+    reason: '',
+    exceStatus: ''
+  }
+}
+
+export function buildCheckDiffItemSearchParams(params = {}) {
+  return filterParams(params, ['current', 'pageSize', 'size'])
+}
+
+export function buildCheckDiffItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+function getCheckDiffItemStatusMeta(status) {
+  const normalized = Number(status)
+  if (normalized === 2) {
+    return { text: '宸插畬鎴�', type: 'success' }
+  }
+  if (normalized === 1) {
+    return { text: '澶勭悊涓�', type: 'warning' }
+  }
+  return { text: '寰呭鐞�', type: 'info' }
+}
+
+export function normalizeCheckDiffItemRow(row = {}) {
+  const statusMeta = getCheckDiffItemStatusMeta(row.exceStatus ?? row.exceStatus$)
+  const checkQty = Number(row.checkQty ?? 0)
+  const anfme = Number(row.anfme ?? 0)
+  return {
+    ...row,
+    exceStatusLabel: row.exceStatusLabel || row.exceStatus$ || statusMeta.text,
+    exceStatusTagType: row.exceStatusTagType || statusMeta.type,
+    diffQty: row.diffQty ?? Number((checkQty - anfme).toFixed(3)),
+    updateTimeText: row.updateTimeText || row.updateTime$ || row.updateTime || '-',
+    createTimeText: row.createTimeText || row.createTime$ || row.createTime || '-'
+  }
+}
+
+export function getCheckDiffItemActionList(record = {}) {
+  return [
+    { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:file-list-3-line' },
+    {
+      key: 'approve',
+      label: '瀹℃壒閫氳繃',
+      icon: 'ri:checkbox-circle-line',
+      auth: 'edit',
+      disabled: Number(record.exceStatus) === 2
+    }
+  ]
+}
+
+export function buildCheckDiffItemPrintRows(records = []) {
+  return Array.isArray(records) ? records.map((item) => normalizeCheckDiffItemRow(item)) : []
+}
+
+export function buildCheckDiffItemReportMeta(rows = []) {
+  return {
+    reportTitle: CHECK_DIFF_ITEM_REPORT_TITLE,
+    reportDate: new Date().toLocaleDateString('zh-CN'),
+    printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+    count: rows.length
+  }
+}
+
+export { CHECK_DIFF_ITEM_REPORT_TITLE, getCheckDiffItemStatusMeta }
diff --git a/rsf-design/src/views/orders/check-diff-item/checkDiffItemTable.columns.js b/rsf-design/src/views/orders/check-diff-item/checkDiffItemTable.columns.js
new file mode 100644
index 0000000..089d6b9
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff-item/checkDiffItemTable.columns.js
@@ -0,0 +1,108 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getCheckDiffItemActionList, getCheckDiffItemStatusMeta } from './checkDiffItemPage.helpers'
+
+export function createCheckDiffItemTableColumns({ handleView, handleApprove } = {}) {
+  return [
+    { type: 'selection', width: 52, fixed: 'left' },
+    {
+      prop: 'orderCode',
+      label: '鐩樼偣鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '甯愰潰搴撳瓨',
+      width: 110,
+      formatter: (row) => row.anfme ?? '-'
+    },
+    {
+      prop: 'checkQty',
+      label: '鐩樼偣搴撳瓨',
+      width: 110,
+      formatter: (row) => row.checkQty ?? '-'
+    },
+    {
+      prop: 'diffQty',
+      label: '宸紓鏁伴噺',
+      width: 110,
+      formatter: (row) => row.diffQty ?? '-'
+    },
+    {
+      prop: 'reason',
+      label: '宸紓鍘熷洜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'exceStatus',
+      label: '鐩樼偣鐘舵��',
+      width: 120,
+      formatter: (row) => {
+        const statusMeta = getCheckDiffItemStatusMeta(row.exceStatus ?? row.exceStatus$)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      sortable: true,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 140,
+      fixed: 'right',
+      formatter: (row) =>
+        h('div', [
+          h(ArtButtonMore, {
+            list: getCheckDiffItemActionList(row),
+            onClick: (item) => {
+              if (item.key === 'view') handleView?.(row)
+              if (item.key === 'approve') handleApprove?.(row)
+            }
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/check-diff-item/index.vue b/rsf-design/src/views/orders/check-diff-item/index.vue
new file mode 100644
index 0000000..7364784
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff-item/index.vue
@@ -0,0 +1,224 @@
+<template>
+  <div class="check-diff-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="columns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <CheckDiffItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchCheckDiffItemPage,
+    fetchDeleteCheckDiffItem,
+    fetchExportCheckDiffItemReport,
+    fetchGetCheckDiffItemDetail,
+    fetchGetCheckDiffItemMany,
+    fetchUpdateCheckDiffItem
+  } from '@/api/check-diff'
+  import CheckDiffItemDetailDrawer from './modules/check-diff-item-detail-drawer.vue'
+  import {
+    CHECK_DIFF_ITEM_REPORT_TITLE,
+    buildCheckDiffItemPageQueryParams,
+    buildCheckDiffItemPrintRows,
+    buildCheckDiffItemReportMeta,
+    buildCheckDiffItemSearchParams,
+    createCheckDiffItemSearchState,
+    normalizeCheckDiffItemRow
+  } from './checkDiffItemPage.helpers'
+  import { createCheckDiffItemTableColumns } from './checkDiffItemTable.columns'
+
+  defineOptions({ name: 'CheckDiffItem' })
+
+  const userStore = useUserStore()
+  const reportTitle = CHECK_DIFF_ITEM_REPORT_TITLE
+  const searchForm = ref(createCheckDiffItemSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const reportQueryParams = computed(() => buildCheckDiffItemSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�/鐗╂枡缂栫爜/鎵樼洏鐮�' } },
+    { label: '鐩樼偣鍗旾D', key: 'checkId', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟ID' } },
+    { label: '鐩樼偣鍗曞彿', key: 'orderCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�' } },
+    { label: '鐗╂枡缂栫爜', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' } },
+    { label: '鎵樼洏鐮�', key: 'barcode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ墭鐩樼爜' } },
+    { label: '宸紓鍘熷洜', key: 'reason', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ樊寮傚師鍥�' } },
+    {
+      label: '鐩樼偣鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '寰呭鐞�', value: 0 },
+          { label: '澶勭悊涓�', value: 1 },
+          { label: '宸插畬鎴�', value: 2 }
+        ]
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailData.value = normalizeCheckDiffItemRow(row)
+  }
+
+  async function handleApprove(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瀹℃壒閫氳繃 ${row.orderCode || ''} / ${row.matnrCode || ''} 鍚楋紵`, '瀹℃壒纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchUpdateCheckDiffItem({ ...row, exceStatus: 2 })
+      ElMessage.success('瀹℃壒鎴愬姛')
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '瀹℃壒澶辫触')
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾鍒犻櫎宸紓鏄庣粏 ${row.orderCode || ''} / ${row.matnrCode || ''} 鍚楋紵`, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteCheckDiffItem(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    if (action.key === 'view') {
+      openDetail(row)
+      return
+    }
+    if (action.key === 'approve') {
+      await handleApprove(row)
+      return
+    }
+    if (action.key === 'delete') {
+      await handleDelete(row)
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchCheckDiffItemPage,
+      apiParams: buildCheckDiffItemPageQueryParams(searchForm.value),
+      columnsFactory: () => createCheckDiffItemTableColumns({ handleView: openDetail, handleApprove })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeCheckDiffItemRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetCheckDiffItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchCheckDiffItemPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
+    usePrintExportPage({
+      downloadFileName: 'check-diff-item.xlsx',
+      requestExport: (payload) =>
+        fetchExportCheckDiffItemReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildCheckDiffItemPrintRows(records),
+      buildPreviewMeta: (rows) => buildCheckDiffItemReportMeta(rows)
+    })
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildCheckDiffItemSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createCheckDiffItemSearchState()
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check-diff-item/modules/check-diff-item-detail-drawer.vue b/rsf-design/src/views/orders/check-diff-item/modules/check-diff-item-detail-drawer.vue
new file mode 100644
index 0000000..f1b3e31
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff-item/modules/check-diff-item-detail-drawer.vue
@@ -0,0 +1,42 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鐩樼偣宸紓鏄庣粏璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElDescriptions :column="2" border>
+      <ElDescriptionsItem label="鐩樼偣鍗曞彿">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐩樼偣鐘舵��">
+        <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+          {{ detail.exceStatusLabel || '--' }}
+        </ElTag>
+      </ElDescriptionsItem>
+      <ElDescriptionsItem label="甯愰潰搴撳瓨">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐩樼偣搴撳瓨">{{ detail.checkQty ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="宸紓鏁伴噺">{{ detail.diffQty ?? '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="宸紓鍘熷洜">{{ detail.reason || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="澶囨敞">{{ detail.memo || '--' }}</ElDescriptionsItem>
+    </ElDescriptions>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'CheckDiffItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check-diff/checkDiffPage.helpers.js b/rsf-design/src/views/orders/check-diff/checkDiffPage.helpers.js
new file mode 100644
index 0000000..949e94a
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff/checkDiffPage.helpers.js
@@ -0,0 +1,124 @@
+const CHECK_DIFF_REPORT_TITLE = '鐩樼偣宸紓鍗曟姤琛�'
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
+  )
+}
+
+export function buildCheckDiffPageRequestParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildCheckDiffItemPageRequestParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function createCheckDiffSearchState() {
+  return {
+    condition: '',
+    orderCode: '',
+    checkType: '',
+    exceStatus: '',
+    areaId: '',
+    areaName: '',
+    memo: ''
+  }
+}
+
+export function buildCheckDiffSearchParams(params = {}) {
+  return filterParams(params, ['current', 'pageSize', 'size'])
+}
+
+export function buildCheckDiffPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+export function buildCheckDiffDetailQueryParams(params = {}) {
+  return {
+    checkId: params.checkId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+function getCheckTypeLabel(checkType) {
+  if (checkType === 0 || checkType === '0') return '鏄庣洏'
+  if (checkType === 1 || checkType === '1') return '鏆楃洏'
+  if (checkType === null || checkType === undefined || checkType === '') return '-'
+  return String(checkType)
+}
+
+function getCheckDiffStatusMeta(status) {
+  const normalized = Number(status)
+  if (normalized === 1) {
+    return { text: '鎵ц涓�', type: 'warning' }
+  }
+  if (normalized === 2) {
+    return { text: '宸插畬鎴�', type: 'success' }
+  }
+  if (normalized === 8) {
+    return { text: '宸插彇娑�', type: 'danger' }
+  }
+  return { text: '鏈墽琛�', type: 'info' }
+}
+
+export function normalizeCheckDiffRow(row = {}) {
+  const statusMeta = getCheckDiffStatusMeta(row.exceStatus ?? row.exceStatus$)
+  return {
+    ...row,
+    checkTypeLabel: row.checkTypeLabel || row.checkType$ || getCheckTypeLabel(row.checkType),
+    exceStatusLabel: row.exceStatusLabel || row.exceStatus$ || statusMeta.text,
+    exceStatusTagType: row.exceStatusTagType || statusMeta.type,
+    createTimeText: row.createTimeText || row.createTime$ || row.createTime || '-',
+    updateTimeText: row.updateTimeText || row.updateTime$ || row.updateTime || '-'
+  }
+}
+
+export function getCheckDiffActionList(record = {}) {
+  return [
+    { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:file-list-3-line' },
+    { key: 'print', label: '鎵撳嵃', icon: 'ri:printer-line' },
+    {
+      key: 'delete',
+      label: '鍒犻櫎',
+      icon: 'ri:delete-bin-4-line',
+      color: '#f56c6c',
+      auth: 'delete'
+    }
+  ].filter((item) => item.key !== 'delete' || Number(record.exceStatus) === 0 || Number(record.exceStatus) === 1)
+}
+
+export function buildCheckDiffPrintRows(records = []) {
+  return Array.isArray(records) ? records.map((item) => normalizeCheckDiffRow(item)) : []
+}
+
+export function buildCheckDiffReportMeta(rows = []) {
+  return {
+    reportTitle: CHECK_DIFF_REPORT_TITLE,
+    reportDate: new Date().toLocaleDateString('zh-CN'),
+    printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+    count: rows.length
+  }
+}
+
+export { CHECK_DIFF_REPORT_TITLE, getCheckDiffStatusMeta }
diff --git a/rsf-design/src/views/orders/check-diff/checkDiffTable.columns.js b/rsf-design/src/views/orders/check-diff/checkDiffTable.columns.js
new file mode 100644
index 0000000..d42acec
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff/checkDiffTable.columns.js
@@ -0,0 +1,69 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getCheckDiffActionList, getCheckDiffStatusMeta } from './checkDiffPage.helpers'
+
+export function createCheckDiffTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 52, fixed: 'left' },
+    {
+      prop: 'orderCode',
+      label: '鐩樼偣鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'checkTypeLabel',
+      label: '鐩樼偣绫诲瀷',
+      width: 110,
+      formatter: (row) => row.checkTypeLabel || '-'
+    },
+    {
+      prop: 'areaName',
+      label: '搴撳尯鍚嶇О',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '搴旂洏鏁伴噺',
+      width: 110,
+      formatter: (row) => row.anfme ?? '-'
+    },
+    {
+      prop: 'checkQty',
+      label: '宸茬洏鏁伴噺',
+      width: 110,
+      formatter: (row) => row.checkQty ?? '-'
+    },
+    {
+      prop: 'exceStatus',
+      label: '鍗曟嵁鐘舵��',
+      width: 120,
+      formatter: (row) => {
+        const statusMeta = getCheckDiffStatusMeta(row.exceStatus ?? row.exceStatus$)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      sortable: true,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 140,
+      fixed: 'right',
+      formatter: (row) =>
+        h('div', [
+          h(ArtButtonMore, {
+            list: getCheckDiffActionList(row),
+            onClick: (item) => handleActionClick?.(item, row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/check-diff/index.vue b/rsf-design/src/views/orders/check-diff/index.vue
new file mode 100644
index 0000000..9cec74e
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff/index.vue
@@ -0,0 +1,281 @@
+<template>
+  <div class="check-diff-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="columns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <CheckDiffDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :items="detailItemRows"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchCheckDiffItemPage,
+    fetchCheckDiffPage,
+    fetchDeleteCheckDiff,
+    fetchExportCheckDiffReport,
+    fetchGetCheckDiffDetail,
+    fetchGetCheckDiffMany
+  } from '@/api/check-diff'
+  import CheckDiffDetailDrawer from './modules/check-diff-detail-drawer.vue'
+  import {
+    CHECK_DIFF_REPORT_TITLE,
+    buildCheckDiffDetailQueryParams,
+    buildCheckDiffPageQueryParams,
+    buildCheckDiffPrintRows,
+    buildCheckDiffReportMeta,
+    buildCheckDiffSearchParams,
+    createCheckDiffSearchState,
+    normalizeCheckDiffRow
+  } from './checkDiffPage.helpers'
+  import { createCheckDiffTableColumns } from './checkDiffTable.columns'
+  import { createCheckDiffItemTableColumns } from '../check-diff-item/checkDiffItemTable.columns'
+  import { normalizeCheckDiffItemRow } from '../check-diff-item/checkDiffItemPage.helpers'
+
+  defineOptions({ name: 'CheckDiff' })
+
+  const userStore = useUserStore()
+  const reportTitle = CHECK_DIFF_REPORT_TITLE
+  const searchForm = ref(createCheckDiffSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailItemRows = ref([])
+  const activeCheckDiffId = ref(null)
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildCheckDiffSearchParams(searchForm.value))
+  const detailColumns = computed(() => createCheckDiffItemTableColumns({ handleView: () => {} }))
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�/搴撳尯鍚嶇О/澶囨敞' } },
+    { label: '鐩樼偣鍗曞彿', key: 'orderCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�' } },
+    {
+      label: '鐩樼偣绫诲瀷',
+      key: 'checkType',
+      type: 'select',
+      props: { clearable: true, options: [{ label: '鏄庣洏', value: 0 }, { label: '鏆楃洏', value: 1 }] }
+    },
+    {
+      label: '鍗曟嵁鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鏈墽琛�', value: 0 },
+          { label: '鎵ц涓�', value: 1 },
+          { label: '宸插畬鎴�', value: 2 },
+          { label: '宸插彇娑�', value: 8 }
+        ]
+      }
+    },
+    { label: '搴撳尯ID', key: 'areaId', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ簱鍖篒D' } },
+    { label: '搴撳尯鍚嶇О', key: 'areaName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�' } },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function openDetail(row) {
+    activeCheckDiffId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾鍒犻櫎鐩樼偣宸紓鍗� ${row.orderCode || ''} 鍚楋紵`, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteCheckDiff(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    if (action.key === 'view') {
+      await openDetail(row)
+      return
+    }
+    if (action.key === 'print') {
+      await handlePrint({ ids: [row.id], pageSize: 1 })
+      return
+    }
+    if (action.key === 'delete') {
+      await handleDelete(row)
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchCheckDiffPage,
+      apiParams: buildCheckDiffPageQueryParams(searchForm.value),
+      columnsFactory: () => createCheckDiffTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeCheckDiffRow(item)) : [])
+    }
+  })
+
+  async function loadDetailResources() {
+    if (!activeCheckDiffId.value) return
+    detailLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchGetCheckDiffDetail(activeCheckDiffId.value), {}, { timeoutMessage: '鐩樼偣宸紓鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }),
+        guardRequestWithMessage(
+          fetchCheckDiffItemPage(
+            buildCheckDiffDetailQueryParams({
+              checkId: activeCheckDiffId.value,
+              current: detailPagination.current,
+              pageSize: detailPagination.size
+            })
+          ),
+          { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
+          { timeoutMessage: '鐩樼偣宸紓鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+        )
+      ])
+      detailData.value = normalizeCheckDiffRow(detailResponse)
+      detailItemRows.value = Array.isArray(itemResponse?.records)
+        ? itemResponse.records.map((item) => normalizeCheckDiffItemRow(item))
+        : []
+      updatePaginationState(detailPagination, itemResponse, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildCheckDiffSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createCheckDiffSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetCheckDiffMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchCheckDiffPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
+    usePrintExportPage({
+      downloadFileName: 'check-diff.xlsx',
+      requestExport: (payload) =>
+        fetchExportCheckDiffReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildCheckDiffPrintRows(records),
+      buildPreviewMeta: (rows) => buildCheckDiffReportMeta(rows)
+    })
+</script>
diff --git a/rsf-design/src/views/orders/check-diff/modules/check-diff-detail-drawer.vue b/rsf-design/src/views/orders/check-diff/modules/check-diff-detail-drawer.vue
new file mode 100644
index 0000000..9df8fce
--- /dev/null
+++ b/rsf-design/src/views/orders/check-diff/modules/check-diff-detail-drawer.vue
@@ -0,0 +1,61 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鐩樼偣宸紓鍗曡鎯�"
+    size="88%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="鐩樼偣鍗曞彿">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩樼偣绫诲瀷">{{ detail.checkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳尯鍚嶇О">{{ detail.areaName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusLabel || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="甯愰潰搴撳瓨">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩樼偣搴撳瓨">{{ detail.checkQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElCard shadow="never" class="border border-[var(--art-border-color)]">
+          <template #header>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">鐩樼偣鏄庣粏</div>
+          </template>
+          <ArtTable
+            :loading="loading"
+            :data="items"
+            :columns="columns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'CheckDiffDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    items: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check-item/checkOrderItemPage.helpers.js b/rsf-design/src/views/orders/check-item/checkOrderItemPage.helpers.js
new file mode 100644
index 0000000..8c36052
--- /dev/null
+++ b/rsf-design/src/views/orders/check-item/checkOrderItemPage.helpers.js
@@ -0,0 +1,55 @@
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+export function createCheckOrderItemSearchState() {
+  return {
+    condition: '',
+    orderCode: '',
+    matnrCode: '',
+    maktx: '',
+    barcode: ''
+  }
+}
+
+export function buildCheckOrderItemSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'orderCode', 'matnrCode', 'maktx', 'barcode'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+  return result
+}
+
+export function buildCheckOrderItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.orderId ? { orderId: params.orderId } : {}),
+    ...buildCheckOrderItemSearchParams(params)
+  }
+}
+
+export function normalizeCheckOrderItemRow(record = {}) {
+  return {
+    ...record,
+    orderId: record.orderId ?? '-',
+    orderCode: record.orderCode || '-',
+    platOrderCode: record.platOrderCode || '-',
+    matnrId: record.matnrId ?? '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    stockUnit: record.stockUnit || '-',
+    splrBatch: record.splrBatch || '-',
+    splrCode: record.splrCode || '-',
+    splrName: record.splrName || '-',
+    barcode: record.barcode || record.trackCode || '-',
+    anfme: record.anfme ?? 0,
+    workQty: record.workQty ?? 0,
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    memo: record.memo || '-'
+  }
+}
diff --git a/rsf-design/src/views/orders/check-item/checkOrderItemTable.columns.js b/rsf-design/src/views/orders/check-item/checkOrderItemTable.columns.js
new file mode 100644
index 0000000..6f6577b
--- /dev/null
+++ b/rsf-design/src/views/orders/check-item/checkOrderItemTable.columns.js
@@ -0,0 +1,80 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createCheckOrderItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'orderCode',
+      label: '鐩樼偣鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockUnit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旂洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸茬洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/check-item/index.vue b/rsf-design/src/views/orders/check-item/index.vue
new file mode 100644
index 0000000..90a366d
--- /dev/null
+++ b/rsf-design/src/views/orders/check-item/index.vue
@@ -0,0 +1,92 @@
+<template>
+  <div class="check-order-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <CheckOrderItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchCheckOrderItemPage, fetchGetCheckOrderItemDetail } from '@/api/check-order'
+  import { buildCheckOrderItemPageQueryParams, buildCheckOrderItemSearchParams, createCheckOrderItemSearchState, normalizeCheckOrderItemRow } from './checkOrderItemPage.helpers'
+  import { createCheckOrderItemTableColumns } from './checkOrderItemTable.columns'
+  import CheckOrderItemDetailDrawer from './modules/check-order-item-detail-drawer.vue'
+
+  defineOptions({ name: 'CheckOrderItem' })
+
+  const searchForm = ref(createCheckOrderItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�/鐗╂枡缂栫爜' } },
+    { label: '鐩樼偣鍗曞彿', key: 'orderCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�' } },
+    { label: '鐗╂枡缂栫爜', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' } },
+    { label: '鐗╂枡鍚嶇О', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' } },
+    { label: '鎵樼洏鐮�', key: 'barcode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ墭鐩樼爜' } }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    const detail = await fetchGetCheckOrderItemDetail(row.id)
+    detailData.value = normalizeCheckOrderItemRow({ ...row, ...detail })
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchCheckOrderItemPage,
+      apiParams: buildCheckOrderItemPageQueryParams(searchForm.value),
+      columnsFactory: () => createCheckOrderItemTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeCheckOrderItemRow(item)) : [])
+    }
+  })
+
+  function handleSelectionChange() {}
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildCheckOrderItemSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createCheckOrderItemSearchState()
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check-item/modules/check-order-item-detail-drawer.vue b/rsf-design/src/views/orders/check-item/modules/check-order-item-detail-drawer.vue
new file mode 100644
index 0000000..2c38e83
--- /dev/null
+++ b/rsf-design/src/views/orders/check-item/modules/check-order-item-detail-drawer.vue
@@ -0,0 +1,43 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鐩樼偣鍗曟槑缁嗚鎯�"
+    size="68%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="鐩樼偣鍗旾D">{{ detail.orderId ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐩樼偣鍗曞彿">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="骞冲彴鍗曞彿">{{ detail.platOrderCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曚綅">{{ detail.stockUnit || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴旂洏鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="宸茬洏鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ detail.splrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟�">{{ detail.splrName || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'CheckOrderItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/check/checkOrderPage.helpers.js b/rsf-design/src/views/orders/check/checkOrderPage.helpers.js
new file mode 100644
index 0000000..1eecf03
--- /dev/null
+++ b/rsf-design/src/views/orders/check/checkOrderPage.helpers.js
@@ -0,0 +1,135 @@
+export const CHECK_ORDER_REPORT_TITLE = '鐩樼偣鍗曟姤琛�'
+
+const CHECK_EXCE_STATUS_MAP = {
+  0: { label: '鏈墽琛�', tagType: 'info' },
+  1: { label: '鎵ц涓�', tagType: 'warning' },
+  2: { label: '宸插畬鎴�', tagType: 'success' },
+  8: { label: '宸插彇娑�', tagType: 'danger' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function getExceStatusConfig(status, statusText) {
+  const fallback = CHECK_EXCE_STATUS_MAP[Number(status)] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+export function createCheckOrderSearchState() {
+  return {
+    condition: '',
+    code: '',
+    wkType: '',
+    exceStatus: '',
+    arrTime: '',
+    memo: ''
+  }
+}
+
+export function buildCheckOrderSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'wkType', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.arrTime) {
+    result.arrTime = params.arrTime
+  }
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+
+  return result
+}
+
+export function buildCheckOrderPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildCheckOrderSearchParams(params)
+  }
+}
+
+export function buildCheckOrderDetailQueryParams(params = {}) {
+  return {
+    orderId: params.orderId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeCheckOrderRow(record = {}) {
+  const exceStatusConfig = getExceStatusConfig(record.exceStatus, record['exceStatus$'])
+  return {
+    ...record,
+    code: record.code || '-',
+    wkTypeLabel: record['wkType$'] || record.wkType || '-',
+    checkTypeLabel: record.checkType$ || record.checkType || '-',
+    anfme: record.anfme ?? 0,
+    workQty: record.workQty ?? 0,
+    qty: record.qty ?? 0,
+    arrTimeText: record['arrTime$'] || record.arrTime || '-',
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    memo: record.memo || '-',
+    exceStatusText: exceStatusConfig.label,
+    exceStatusTagType: exceStatusConfig.tagType,
+    canCancel: Number(record.exceStatus) === 0
+  }
+}
+
+export function normalizeCheckOrderItemRow(record = {}) {
+  return {
+    ...record,
+    orderCode: record.orderCode || '-',
+    platOrderCode: record.platOrderCode || '-',
+    matnrId: record.matnrId ?? '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    stockUnit: record.stockUnit || '-',
+    splrBatch: record.splrBatch || '-',
+    splrCode: record.splrCode || '-',
+    splrName: record.splrName || '-',
+    barcode: record.barcode || record.trackCode || '-',
+    anfme: record.anfme ?? 0,
+    workQty: record.workQty ?? 0,
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function getCheckOrderActionList(row = {}) {
+  const normalizedRow = normalizeCheckOrderRow(row)
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    },
+    {
+      key: 'print',
+      label: '鎵撳嵃',
+      icon: 'ri:printer-line'
+    },
+    {
+      key: 'cancel',
+      label: '鍙栨秷鍗曟嵁',
+      icon: 'ri:close-circle-line',
+      color: 'var(--el-color-danger)',
+      disabled: !normalizedRow.canCancel
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/check/checkOrderTable.columns.js b/rsf-design/src/views/orders/check/checkOrderTable.columns.js
new file mode 100644
index 0000000..32138de
--- /dev/null
+++ b/rsf-design/src/views/orders/check/checkOrderTable.columns.js
@@ -0,0 +1,134 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getCheckOrderActionList } from './checkOrderPage.helpers'
+
+export function createCheckOrderTableColumns({ handleActionClick }) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '鐩樼偣鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '鐩樼偣绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'checkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '搴旂洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸茬洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '纭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'arrTimeText',
+      label: '鐩樼偣鏃堕棿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鍗曟嵁鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.exceStatusTagType || 'info', effect: 'light' },
+          () => row.exceStatusText
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getCheckOrderActionList(row),
+          onClick: (item) => handleActionClick(item, row)
+        })
+    }
+  ]
+}
+
+export function createCheckOrderDetailItemColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'orderCode',
+      label: '鐩樼偣鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockUnit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旂洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸茬洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/check/index.vue b/rsf-design/src/views/orders/check/index.vue
new file mode 100644
index 0000000..e16fe5b
--- /dev/null
+++ b/rsf-design/src/views/orders/check/index.vue
@@ -0,0 +1,249 @@
+<template>
+  <div class="check-order-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <CheckOrderDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchCancelCheckOrder,
+    fetchCheckOrderItemPage,
+    fetchCheckOrderPage,
+    fetchExportCheckOrderReport,
+    fetchGetCheckOrderDetail,
+    fetchGetCheckOrderMany
+  } from '@/api/check-order'
+  import CheckOrderDetailDrawer from './modules/check-order-detail-drawer.vue'
+  import {
+    CHECK_ORDER_REPORT_TITLE,
+    buildCheckOrderDetailQueryParams,
+    buildCheckOrderPageQueryParams,
+    buildCheckOrderSearchParams,
+    createCheckOrderSearchState,
+    normalizeCheckOrderItemRow,
+    normalizeCheckOrderRow
+  } from './checkOrderPage.helpers'
+  import {
+    createCheckOrderDetailItemColumns,
+    createCheckOrderTableColumns
+  } from './checkOrderTable.columns'
+
+  defineOptions({ name: 'CheckOrder' })
+
+  const userStore = useUserStore()
+  const reportTitle = CHECK_ORDER_REPORT_TITLE
+  const searchForm = ref(createCheckOrderSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activeOrderId = ref(null)
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildCheckOrderSearchParams(searchForm.value))
+  const detailColumns = computed(() => createCheckOrderDetailItemColumns())
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�/澶囨敞' } },
+    { label: '鐩樼偣鍗曞彿', key: 'code', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐瑰崟鍙�' } },
+    { label: '鐩樼偣绫诲瀷', key: 'wkType', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洏鐐圭被鍨�' } },
+    {
+      label: '鍗曟嵁鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: { clearable: true, options: [{ label: '鏈墽琛�', value: 0 }, { label: '鎵ц涓�', value: 1 }, { label: '宸插畬鎴�', value: 2 }, { label: '宸插彇娑�', value: 8 }] }
+    },
+    { label: '鐩樼偣鏃ユ湡', key: 'arrTime', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function openDetail(row) {
+    activeOrderId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    try {
+      if (action.key === 'view') {
+        await openDetail(row)
+        return
+      }
+      if (action.key === 'print') {
+        await handlePrint({ ids: [row.id], pageSize: 1 })
+        return
+      }
+      if (action.key === 'cancel') {
+        await ElMessageBox.confirm(`纭畾鍙栨秷鐩樼偣鍗� ${row.code || ''} 鍚楋紵`, '鍙栨秷纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchCancelCheckOrder(row.id)
+        ElMessage.success('鐩樼偣鍗曞凡鍙栨秷')
+        await refreshData()
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '鐩樼偣鍗曟搷浣滃け璐�')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchCheckOrderPage,
+      apiParams: buildCheckOrderPageQueryParams(searchForm.value),
+      columnsFactory: () => createCheckOrderTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeCheckOrderRow(item)) : [])
+    }
+  })
+
+  async function loadDetailResources() {
+    if (!activeOrderId.value) return
+    detailLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchGetCheckOrderDetail(activeOrderId.value), {}, { timeoutMessage: '鐩樼偣鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }),
+        guardRequestWithMessage(
+          fetchCheckOrderItemPage(buildCheckOrderDetailQueryParams({ orderId: activeOrderId.value, current: detailPagination.current, pageSize: detailPagination.size })),
+          { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
+          { timeoutMessage: '鐩樼偣鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+        )
+      ])
+      detailData.value = normalizeCheckOrderRow(detailResponse)
+      detailTableData.value = Array.isArray(itemResponse?.records) ? itemResponse.records.map((item) => normalizeCheckOrderItemRow(item)) : []
+      updatePaginationState(detailPagination, itemResponse, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildCheckOrderSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createCheckOrderSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetCheckOrderMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(await fetchCheckOrderPage({ ...reportQueryParams.value, current: 1, pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 })).records
+  }
+
+  const { previewVisible, previewRows, previewMeta: resolvedPreviewMeta, handlePreviewVisibleChange, handleExport, handlePrint } = usePrintExportPage({
+    downloadFileName: 'check-order.xlsx',
+    requestExport: (payload) => fetchExportCheckOrderReport(payload, { headers: { Authorization: userStore.accessToken || '' } }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => records.map((item) => normalizeCheckOrderRow(item)),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+</script>
diff --git a/rsf-design/src/views/orders/check/modules/check-order-detail-drawer.vue b/rsf-design/src/views/orders/check/modules/check-order-detail-drawer.vue
new file mode 100644
index 0000000..f862671
--- /dev/null
+++ b/rsf-design/src/views/orders/check/modules/check-order-detail-drawer.vue
@@ -0,0 +1,71 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鐩樼偣鍗曡鎯�"
+    size="88%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="check-order-detail-scroll">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="鐩樼偣鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩樼偣绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.checkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴旂洏鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸茬洏鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="纭鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩樼偣鏃堕棿">{{ detail.arrTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElCard shadow="never" class="border border-[var(--art-border-color)]">
+          <template #header>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">鐗╂枡缂栫爜鏄庣粏</div>
+          </template>
+          <ArtTable
+            :loading="loading"
+            :data="data"
+            :columns="columns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'CheckOrderDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
+
+<style scoped>
+  .check-order-detail-scroll {
+    height: calc(100vh - 120px);
+  }
+</style>
diff --git a/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
new file mode 100644
index 0000000..c38a44f
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/deliveryItemPage.helpers.js
@@ -0,0 +1,146 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const DELIVERY_ITEM_REPORT_TITLE = 'DO鍗曟槑缁嗘姤琛�'
+export const DELIVERY_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function createDeliveryItemSearchState() {
+  return {
+    condition: '',
+    deliveryCode: '',
+    platItemId: '',
+    matnrCode: '',
+    maktx: '',
+    splrName: '',
+    splrBatch: ''
+  }
+}
+
+export function buildDeliveryItemSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'deliveryCode', 'platItemId', 'matnrCode', 'maktx', 'splrName', 'splrCode', 'splrBatch', 'memo'].forEach(
+    (key) => {
+      const value = normalizeText(params[key])
+      if (value) {
+        result[key] = value
+      }
+    }
+  )
+
+  if (params.deliveryId !== '' && params.deliveryId !== undefined && params.deliveryId !== null) {
+    result.deliveryId = normalizeNumber(params.deliveryId)
+  }
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = normalizeNumber(params.status)
+  }
+
+  return result
+}
+
+export function buildDeliveryItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeliveryItemSearchParams(params)
+  }
+}
+
+export function buildDeliveryItemDetailQueryParams(params = {}) {
+  return {
+    deliveryItemId: params.deliveryItemId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeDeliveryItemRow(record = {}) {
+  const statusMeta = normalizeStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    id: record.id ?? null,
+    deliveryId: record.deliveryId ?? '--',
+    deliveryCode: normalizeText(record.deliveryCode) || '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    maktx: normalizeText(record.maktx || record.matnrName) || '--',
+    matnrName: normalizeText(record.maktx || record.matnrName) || '--',
+    fieldsIndex: normalizeText(record.fieldsIndex) || '--',
+    unit: normalizeText(record.unit) || '--',
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    nromQty: record.nromQty ?? '--',
+    printQty: record.printQty ?? '--',
+    splrName: normalizeText(record.splrName) || '--',
+    splrCode: normalizeText(record.splrCode) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    batch: normalizeText(record.batch) || '--',
+    trackCode: normalizeText(record.trackCode) || '--',
+    packName: normalizeText(record.packName) || '--',
+    prodTimeText: normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildDeliveryItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeDeliveryItemRow(record))
+}
+
+export function buildDeliveryItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = DELIVERY_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: DELIVERY_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...DELIVERY_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js b/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js
new file mode 100644
index 0000000..47eea48
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/deliveryItemTable.columns.js
@@ -0,0 +1,141 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createDeliveryItemTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'deliveryCode',
+      label: '浜ゆ帴鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.deliveryCode || '--'
+    },
+    {
+      prop: 'deliveryId',
+      label: '涓诲崟ID',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.deliveryId ?? '--'
+    },
+    {
+      prop: 'platItemId',
+      label: '骞冲彴琛屽彿',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platItemId || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鍔ㄦ�佸瓧娈电储寮�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.fieldsIndex || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插嚭鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'nromQty',
+      label: '鏍囧噯鍖呰',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.nromQty ?? '--'
+    },
+    {
+      prop: 'printQty',
+      label: '鎵撳嵃鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.printQty ?? '--'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrName || '--'
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrCode || '--'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrBatch || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleActionClick?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/delivery-item/index.vue b/rsf-design/src/views/orders/delivery-item/index.vue
new file mode 100644
index 0000000..eea4e50
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/index.vue
@@ -0,0 +1,178 @@
+<template>
+  <div class="delivery-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <DeliveryItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDeliveryItemPage, fetchGetDeliveryItemDetail } from '@/api/delivery'
+  import DeliveryItemDetailDrawer from './modules/delivery-item-detail-drawer.vue'
+  import { createDeliveryItemTableColumns } from './deliveryItemTable.columns.js'
+  import {
+    buildDeliveryItemPageQueryParams,
+    createDeliveryItemSearchState,
+    normalizeDeliveryItemRow
+  } from './deliveryItemPage.helpers.js'
+
+  defineOptions({ name: 'DeliveryItem' })
+
+  const searchForm = ref(createDeliveryItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ氦鎺ュ崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О'
+      }
+    },
+    {
+      label: '浜ゆ帴鍗曞彿',
+      key: 'deliveryCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ氦鎺ュ崟鍙�'
+      }
+    },
+    {
+      label: '骞冲彴琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙拌鍙�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗗悕绉�',
+      key: 'splrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    guardRequestWithMessage(fetchGetDeliveryItemDetail(row.id), {}, {
+      timeoutMessage: '浜ゆ帴鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+    })
+      .then((detail) => {
+        detailData.value = normalizeDeliveryItemRow(detail)
+      })
+      .catch((error) => {
+        detailDrawerVisible.value = false
+        detailData.value = {}
+        ElMessage.error(error?.message || '鑾峰彇浜ゆ帴鍗曟槑缁嗚鎯呭け璐�')
+      })
+      .finally(() => {
+        detailLoading.value = false
+      })
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchDeliveryItemPage,
+      apiParams: buildDeliveryItemPageQueryParams({
+        ...searchForm.value,
+        pageSize: 20
+      }),
+      columnsFactory: () => createDeliveryItemTableColumns({ handleActionClick: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeDeliveryItemRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildDeliveryItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createDeliveryItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/orders/delivery-item/modules/delivery-item-detail-drawer.vue b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-detail-drawer.vue
new file mode 100644
index 0000000..6688dd8
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery-item/modules/delivery-item-detail-drawer.vue
@@ -0,0 +1,74 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浜ゆ帴鍗曟槑缁嗚鎯�"
+    size="960px"
+    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">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="浜ゆ帴鍗曞彿">{{ detail.deliveryCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓诲崟ID">{{ detail.deliveryId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="骞冲彴琛屽彿">{{ detail.platItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍔ㄦ�佸瓧娈电储寮�">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸插嚭鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏍囧噯鍖呰">{{ detail.nromQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵撳嵃鏁伴噺">{{ detail.printQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ detail.splrName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ detail.splrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璺熻釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍖呰">{{ detail.packName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐢熶骇鏃ユ湡">{{ detail.prodTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'DeliveryItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
new file mode 100644
index 0000000..c770e3c
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery/deliveryPage.helpers.js
@@ -0,0 +1,264 @@
+const DELIVERY_STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '绂佺敤', type: 'danger', bool: false }
+}
+
+const DELIVERY_EXCE_STATUS_META = {
+  0: { text: '鏈墽琛�', type: 'info' },
+  1: { text: '鎵ц涓�', type: 'warning' },
+  2: { text: '閮ㄥ垎瀹屾垚', type: 'primary' },
+  3: { text: '宸插畬鎴�', type: 'success' }
+}
+
+export const DELIVERY_REPORT_TITLE = 'DO鍗曟姤琛�'
+export const DELIVERY_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return DELIVERY_STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return DELIVERY_STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+function normalizeExceStatusMeta(exceStatus, exceStatusText) {
+  if (exceStatusText) {
+    const numericValue = Number(exceStatus)
+    const fallback = DELIVERY_EXCE_STATUS_META[numericValue] || {
+      text: exceStatusText,
+      type: 'info'
+    }
+    return fallback
+  }
+  return DELIVERY_EXCE_STATUS_META[Number(exceStatus)] || {
+    text: normalizeText(exceStatus) || '--',
+    type: 'info'
+  }
+}
+
+export function createDeliverySearchState() {
+  return {
+    condition: '',
+    code: '',
+    platId: '',
+    type: '',
+    wkType: '',
+    source: '',
+    exceStatus: '',
+    memo: ''
+  }
+}
+
+export function getDeliveryPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildDeliverySearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'platId', 'type', 'wkType', 'source', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = normalizeNumber(params.exceStatus)
+  }
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = normalizeNumber(params.status)
+  }
+
+  if (params.timeStart !== '' && params.timeStart !== undefined && params.timeStart !== null) {
+    result.timeStart = normalizeText(params.timeStart)
+  }
+
+  if (params.timeEnd !== '' && params.timeEnd !== undefined && params.timeEnd !== null) {
+    result.timeEnd = normalizeText(params.timeEnd)
+  }
+
+  return result
+}
+
+export function buildDeliveryPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDeliverySearchParams(params)
+  }
+}
+
+export function buildDeliveryDetailQueryParams(params = {}) {
+  return {
+    deliveryId: params.deliveryId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeDeliveryRow(record = {}) {
+  const statusMeta = normalizeStatusMeta(record.statusBool ?? record.status)
+  const exceStatusMeta = normalizeExceStatusMeta(record.exceStatus, record['exceStatus$'] || record.exceStatusText)
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: normalizeText(record.code) || '--',
+    platId: normalizeText(record.platId) || '--',
+    platCode: normalizeText(record.platCode) || '--',
+    source: normalizeText(record.source) || '--',
+    typeLabel: normalizeText(record['type$'] || record.type) || '--',
+    wkTypeLabel: normalizeText(record['wkType$'] || record.wkType) || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    exceStatusText: normalizeText(record['exceStatus$'] || record.exceStatusText) || exceStatusMeta.text,
+    exceStatusTagType: exceStatusMeta.type,
+    anfme: record.anfme ?? '--',
+    qty: record.qty ?? '--',
+    workQty: record.workQty ?? '--',
+    startTimeText: normalizeText(record['startTime$'] || record.startTimeText || record.startTime) || '--',
+    endTimeText: normalizeText(record['endTime$'] || record.endTimeText || record.endTime) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function normalizeDeliveryItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    deliveryId: record.deliveryId ?? '--',
+    deliveryCode: normalizeText(record.deliveryCode) || '--',
+    platOrderCode: normalizeText(record.platOrderCode) || '--',
+    platWorkCode: normalizeText(record.platWorkCode) || '--',
+    projectCode: normalizeText(record.projectCode) || '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    matnrName: normalizeText(record.maktx || record.matnrName) || '--',
+    maktx: normalizeText(record.maktx || record.matnrName) || '--',
+    fieldsIndex: normalizeText(record.fieldsIndex) || '--',
+    unit: normalizeText(record.unit) || '--',
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    nromQty: record.nromQty ?? '--',
+    printQty: record.printQty ?? '--',
+    splrName: normalizeText(record.splrName) || '--',
+    splrCode: normalizeText(record.splrCode) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    batch: normalizeText(record.batch) || '--',
+    trackCode: normalizeText(record.trackCode) || '--',
+    packName: normalizeText(record.packName) || '--',
+    prodTimeText: normalizeText(record['prodTime$'] || record.prodTimeText || record.prodTime) || '--',
+    statusText: normalizeStatusMeta(record.statusBool ?? record.status).text,
+    statusType: normalizeStatusMeta(record.statusBool ?? record.status).type,
+    statusBool:
+      record.statusBool !== void 0
+        ? Boolean(record.statusBool)
+        : normalizeStatusMeta(record.statusBool ?? record.status).bool,
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildDeliveryPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeDeliveryRow(record))
+}
+
+export function buildDeliveryItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeDeliveryItemRow(record))
+}
+
+export function buildDeliveryReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = DELIVERY_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: DELIVERY_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...DELIVERY_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function buildDeliveryItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = DELIVERY_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: 'DO鍗曟槑缁嗘姤琛�',
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...DELIVERY_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getDeliveryActionList(row = {}) {
+  const normalizedRow = normalizeDeliveryRow(row)
+  const actions = [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    }
+  ]
+
+  if (Number(normalizedRow.exceStatus) === 0) {
+    actions.push({
+      key: 'delete',
+      label: '鍒犻櫎',
+      icon: 'ri:delete-bin-5-line',
+      color: 'var(--art-error)'
+    })
+  }
+
+  return actions
+}
diff --git a/rsf-design/src/views/orders/delivery/deliveryTable.columns.js b/rsf-design/src/views/orders/delivery/deliveryTable.columns.js
new file mode 100644
index 0000000..ae3f7e8
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery/deliveryTable.columns.js
@@ -0,0 +1,122 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getDeliveryActionList } from './deliveryPage.helpers.js'
+
+export function createDeliveryTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'platId',
+      label: 'ERP涓诲崟鏍囪瘑',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platId || '--'
+    },
+    {
+      prop: 'typeLabel',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeLabel || '--'
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeLabel || '--'
+    },
+    {
+      prop: 'source',
+      label: '鍗曟嵁鏉ユ簮',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.source || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '搴旀敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '瀹炴敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'platCode',
+      label: '骞冲彴鍗曞彿',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platCode || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
+    },
+    {
+      prop: 'startTimeText',
+      label: '璁″垝鍑哄簱鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.startTimeText || '--'
+    },
+    {
+      prop: 'endTimeText',
+      label: '璁″垝鍑哄簱缁撴潫鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.endTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getDeliveryActionList(row),
+          onClick: (item) => handleActionClick?.(item, row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/delivery/index.vue b/rsf-design/src/views/orders/delivery/index.vue
new file mode 100644
index 0000000..851a0d5
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery/index.vue
@@ -0,0 +1,387 @@
+<template>
+  <div class="delivery-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <DeliveryDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :items-loading="detailItemsLoading"
+      :detail="detailData"
+      :item-rows="detailItemRows"
+      :pagination="detailItemPagination"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    buildDeliveryDetailQueryParams,
+    buildDeliveryPageQueryParams,
+    buildDeliveryPrintRows,
+    buildDeliveryReportMeta,
+    buildDeliverySearchParams,
+    createDeliverySearchState,
+    getDeliveryPaginationKey,
+    normalizeDeliveryItemRow,
+    normalizeDeliveryRow
+  } from './deliveryPage.helpers.js'
+  import {
+    fetchDeleteDelivery,
+    fetchDeliveryItemPage,
+    fetchDeliveryPage,
+    fetchExportDeliveryReport,
+    fetchGetDeliveryDetail,
+    fetchGetDeliveryMany
+  } from '@/api/delivery'
+  import DeliveryDetailDrawer from './modules/delivery-detail-drawer.vue'
+  import { createDeliveryTableColumns } from './deliveryTable.columns.js'
+
+  defineOptions({ name: 'Delivery' })
+
+  const userStore = useUserStore()
+  const reportTitle = 'DO鍗曟姤琛�'
+  const searchForm = ref(createDeliverySearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailItemsLoading = ref(false)
+  const detailData = ref({})
+  const detailItemRows = ref([])
+  const activeDeliveryId = ref(null)
+  const detailItemPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildDeliverySearchParams(searchForm.value))
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鍙�/ERP涓诲崟鏍囪瘑/骞冲彴鍗曞彿'
+      }
+    },
+    {
+      label: '鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鍙�'
+      }
+    },
+    {
+      label: 'ERP涓诲崟鏍囪瘑',
+      key: 'platId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏RP涓诲崟鏍囪瘑'
+      }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'type',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鎹被鍨�'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�'
+      }
+    },
+    {
+      label: '鍗曟嵁鏉ユ簮',
+      key: 'source',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟鎹潵婧�'
+      }
+    },
+    {
+      label: '鎵ц鐘舵��',
+      key: 'exceStatus',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ墽琛岀姸鎬�'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function loadDetailItems(deliveryId) {
+    if (!deliveryId) {
+      detailItemRows.value = []
+      detailItemPagination.total = 0
+      return
+    }
+
+    detailItemsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchDeliveryItemPage(
+          buildDeliveryDetailQueryParams({
+            deliveryId,
+            current: detailItemPagination.current,
+            pageSize: detailItemPagination.size
+          })
+        ),
+        { records: [] },
+        { timeoutMessage: '浜ゆ帴鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      const normalizedResponse = defaultResponseAdapter(response)
+      detailItemRows.value = normalizedResponse.records.map((item) => normalizeDeliveryItemRow(item))
+      detailItemPagination.total = Number(normalizedResponse.total || 0)
+      detailItemPagination.current = Number(normalizedResponse.current || detailItemPagination.current || 1)
+      detailItemPagination.size = Number(normalizedResponse.size || detailItemPagination.size || 20)
+    } finally {
+      detailItemsLoading.value = false
+    }
+  }
+
+  async function loadDeliveryDetail(row) {
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchGetDeliveryDetail(row.id),
+        {},
+        { timeoutMessage: '浜ゆ帴鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      detailData.value = normalizeDeliveryRow(detail)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openDetail(row) {
+    activeDeliveryId.value = row.id
+    detailDrawerVisible.value = true
+    detailItemPagination.current = 1
+    detailItemRows.value = []
+    detailData.value = {}
+
+    try {
+      await loadDeliveryDetail(row)
+      await loadDetailItems(row.id)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      ElMessage.error(error?.message || '鑾峰彇浜ゆ帴鍗曡鎯呭け璐�')
+    }
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄や氦鎺ュ崟銆�${row.code || row.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteDelivery(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  function handleTableActionClick(action, row) {
+    if (!action) {
+      return
+    }
+    if (action.key === 'view') {
+      openDetail(row)
+      return
+    }
+    if (action.key === 'delete') {
+      handleDelete(row)
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshRemove,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchDeliveryPage,
+      apiParams: buildDeliveryPageQueryParams({
+        ...searchForm.value,
+        pageSize: 20
+      }),
+      paginationKey: getDeliveryPaginationKey(),
+      columnsFactory: () => createDeliveryTableColumns({ handleActionClick: handleTableActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeDeliveryRow(item)) : [])
+    }
+  })
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildDeliveryPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createDeliverySearchState())
+    resetSearchParams()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetDeliveryMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchDeliveryPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta: rawPreviewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'delivery.xlsx',
+    requestExport: (payload) =>
+      fetchExportDeliveryReport(
+        Array.isArray(payload?.ids) && payload.ids.length > 0 ? reportQueryParams.value : payload,
+        {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }
+      ),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildDeliveryPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          titleAlign: 'center',
+          titleLevel: 'strong',
+          orientation: 'landscape',
+          density: 'compact',
+          showSequence: true
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildDeliveryReportMeta({
+      previewMeta: rawPreviewMeta.value,
+      count: previewRows.value.length,
+      orientation: rawPreviewMeta.value?.reportStyle?.orientation || 'landscape'
+    })
+  )
+
+  async function handleDetailCurrentChange(current) {
+    detailItemPagination.current = current
+    await loadDetailItems(activeDeliveryId.value)
+  }
+
+  async function handleDetailSizeChange(size) {
+    detailItemPagination.size = size
+    detailItemPagination.current = 1
+    await loadDetailItems(activeDeliveryId.value)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue b/rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue
new file mode 100644
index 0000000..920a6ee
--- /dev/null
+++ b/rsf-design/src/views/orders/delivery/modules/delivery-detail-drawer.vue
@@ -0,0 +1,178 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浜ゆ帴鍗曡鎯�"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="浜ゆ帴鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ERP涓诲崟鏍囪瘑">{{ detail.platId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="骞冲彴鍗曞彿">{{ detail.platCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鏉ユ簮">{{ detail.source || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴旀敹鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹炴敹鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁″垝鍑哄簱鏃堕棿">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁″垝鍑哄簱缁撴潫鏃堕棿">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+            <ElTag effect="plain">鍏� {{ itemRows.length }} 鏉�</ElTag>
+          </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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import ArtTable from '@/components/core/tables/art-table/index.vue'
+
+  defineOptions({ name: 'DeliveryDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    itemsLoading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    itemRows: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+
+  const itemColumns = [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'deliveryCode',
+      label: '浜ゆ帴鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platItemId',
+      label: '骞冲彴琛屽彿',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鍔ㄦ�佸瓧娈电储寮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸插嚭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'nromQty',
+      label: '鏍囧噯鍖呰',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'printQty',
+      label: '鎵撳嵃鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    }
+  ]
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/out-stock-item/index.vue b/rsf-design/src/views/orders/out-stock-item/index.vue
new file mode 100644
index 0000000..58ecafa
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock-item/index.vue
@@ -0,0 +1,217 @@
+<template>
+  <div class="out-stock-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <OutStockItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchGetOutStockItemDetail, fetchOutStockItemPage } from '@/api/out-stock-item'
+  import OutStockItemDetailDrawer from './modules/out-stock-item-detail-drawer.vue'
+  import { createOutStockItemTableColumns } from './outStockItemTable.columns.js'
+  import {
+    buildOutStockItemPageQueryParams,
+    createOutStockItemSearchState,
+    normalizeOutStockItemRow
+  } from './outStockItemPage.helpers.js'
+
+  defineOptions({ name: 'OutStockItem' })
+
+  const searchForm = ref(createOutStockItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О'
+      }
+    },
+    {
+      label: '鍑哄簱鍗曞彿',
+      key: 'orderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍙�'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿'
+      }
+    },
+    {
+      label: '骞冲彴琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙拌鍙�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '鏉″舰鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯褰㈢爜'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    guardRequestWithMessage(fetchGetOutStockItemDetail(row.id), {}, {
+      timeoutMessage: '鍑哄簱鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+    })
+      .then((detail) => {
+        detailData.value = normalizeOutStockItemRow(detail)
+      })
+      .catch((error) => {
+        detailDrawerVisible.value = false
+        detailData.value = {}
+        ElMessage.error(error?.message || '鑾峰彇鍑哄簱鍗曟槑缁嗚鎯呭け璐�')
+      })
+      .finally(() => {
+        detailLoading.value = false
+      })
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchOutStockItemPage,
+      apiParams: buildOutStockItemPageQueryParams({
+        ...searchForm.value,
+        pageSize: 20
+      }),
+      columnsFactory: () => createOutStockItemTableColumns({ handleActionClick: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeOutStockItemRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildOutStockItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createOutStockItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/orders/out-stock-item/modules/out-stock-item-detail-drawer.vue b/rsf-design/src/views/orders/out-stock-item/modules/out-stock-item-detail-drawer.vue
new file mode 100644
index 0000000..4829125
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock-item/modules/out-stock-item-detail-drawer.vue
@@ -0,0 +1,87 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鍑哄簱鍗曟槑缁嗚鎯�"
+    size="80%"
+    destroy-on-close
+    append-to-body
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElSkeleton :loading="loading" animated :rows="10">
+        <div class="flex min-h-full flex-col gap-4 pr-2">
+          <ElDescriptions :column="3" border>
+            <ElDescriptionsItem label="鍑哄簱鍗曞彿">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="骞冲彴琛屽彿">{{ detail.platItemId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="骞冲彴璁㈠崟鍙�">{{ detail.platOrderCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="骞冲彴宸ュ崟鍙�">{{ detail.platWorkCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="椤圭洰鍙�">{{ detail.projectCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="搴撳瓨鍗曚綅">{{ detail.stockUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閲囪喘鍗曚綅">{{ detail.purUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍩烘湰鍗曚綅">{{ detail.baseUnit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瀛楁绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉″舰鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="浜岀淮鐮�">{{ detail.qrcode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍖呰鍚嶇О">{{ detail.packName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">
+              <ElTag :type="detail.statusTagType || 'info'" effect="light">
+                {{ detail.statusText || '--' }}
+              </ElTag>
+            </ElDescriptionsItem>
+            <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸插嚭鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="閲囪喘鏁伴噺">{{ detail.purQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="闇�姹傛暟閲�">{{ detail.demandQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ detail.splrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ detail.splrName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉ユ簮浠撳簱">{{ detail.sourceWarehouseId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐩爣浠撳簱">{{ detail.targetWarehouseId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璐т富">{{ detail.ownerName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="淇濈鑰�">{{ detail.keeperName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="3">{{ detail.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions :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>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'OutStockItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</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
new file mode 100644
index 0000000..27cffa4
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock-item/outStockItemPage.helpers.js
@@ -0,0 +1,136 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'danger' }
+}
+
+function normalizeText(value, fallback = '--') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  return String(value).trim() || fallback
+}
+
+function normalizeNumber(value, fallback = undefined) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isNaN(numericValue) ? fallback : numericValue
+}
+
+function pushText(result, key, value) {
+  const text = normalizeText(value, '')
+  if (text) {
+    result[key] = text
+  }
+}
+
+function pushNumber(result, key, value) {
+  const numericValue = normalizeNumber(value)
+  if (numericValue !== undefined) {
+    result[key] = numericValue
+  }
+}
+
+function getStatusMeta(status, statusText) {
+  const numericStatus = Number(status)
+  return STATUS_META[numericStatus] || {
+    text: normalizeText(statusText || status, '--'),
+    type: 'info'
+  }
+}
+
+export function createOutStockItemSearchState() {
+  return {
+    condition: '',
+    orderCode: '',
+    poCode: '',
+    platItemId: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    splrBatch: '',
+    trackCode: '',
+    barcode: '',
+    fieldsIndex: '',
+    status: ''
+  }
+}
+
+export function buildOutStockItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'orderCode',
+    'poCode',
+    'platItemId',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'splrBatch',
+    'trackCode',
+    'barcode',
+    'fieldsIndex'
+  ].forEach((key) => pushText(result, key, params[key]))
+
+  pushNumber(result, 'status', params.status)
+
+  return result
+}
+
+export function buildOutStockItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutStockItemSearchParams(params)
+  }
+}
+
+export function normalizeOutStockItemRow(record = {}) {
+  const statusMeta = getStatusMeta(record.status, record['status$'])
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    orderCode: normalizeText(record.orderCode),
+    poCode: normalizeText(record.poCode),
+    platItemId: normalizeText(record.platItemId),
+    platOrderCode: normalizeText(record.platOrderCode),
+    platWorkCode: normalizeText(record.platWorkCode),
+    projectCode: normalizeText(record.projectCode),
+    matnrCode: normalizeText(record.matnrCode),
+    maktx: normalizeText(record.maktx),
+    spec: normalizeText(record.spec),
+    model: normalizeText(record.model),
+    batch: normalizeText(record.batch),
+    splrCode: normalizeText(record.splrCode),
+    splrName: normalizeText(record.splrName),
+    splrBatch: normalizeText(record.splrBatch),
+    stockUnit: normalizeText(record.stockUnit),
+    purUnit: normalizeText(record.purUnit),
+    baseUnit: normalizeText(record.baseUnit),
+    trackCode: normalizeText(record.trackCode),
+    barcode: normalizeText(record.barcode),
+    qrcode: normalizeText(record.qrcode),
+    packName: normalizeText(record.packName),
+    fieldsIndex: normalizeText(record.fieldsIndex),
+    sourceWarehouseId: normalizeText(record.sourceWarehouseId),
+    targetWarehouseId: normalizeText(record.targetWarehouseId),
+    ownerName: normalizeText(record.ownerName),
+    keeperName: normalizeText(record.keeperName),
+    inStockType: normalizeText(record.inStockType),
+    statusText: statusMeta.text,
+    statusTagType: statusMeta.type,
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    purQty: record.purQty ?? '--',
+    demandQty: record.demandQty ?? '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText),
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime),
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime),
+    memo: normalizeText(record.memo)
+  }
+}
diff --git a/rsf-design/src/views/orders/out-stock-item/outStockItemTable.columns.js b/rsf-design/src/views/orders/out-stock-item/outStockItemTable.columns.js
new file mode 100644
index 0000000..d12747b
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock-item/outStockItemTable.columns.js
@@ -0,0 +1,119 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createOutStockItemTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'orderCode',
+      label: '鍑哄簱鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.orderCode || '--'
+    },
+    {
+      prop: 'poCode',
+      label: 'PO鍗曞彿',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.poCode || '--'
+    },
+    {
+      prop: 'platItemId',
+      label: '骞冲彴琛屽彿',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platItemId || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrBatch || '--'
+    },
+    {
+      prop: 'stockUnit',
+      label: '搴撳瓨鍗曚綅',
+      width: 100,
+      align: 'center',
+      formatter: (row) => row.stockUnit || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插嚭鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '瀛楁绱㈠紩',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.fieldsIndex || '--'
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusTagType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleActionClick?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/out-stock/index.vue b/rsf-design/src/views/orders/out-stock/index.vue
new file mode 100644
index 0000000..5f73851
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock/index.vue
@@ -0,0 +1,318 @@
+<template>
+  <div class="out-stock-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <OutStockDetailDrawer v-model:visible="detailDrawerVisible" :loading="detailLoading" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchCancelOutStock,
+    fetchCompleteOutStock,
+    fetchDeleteOutStock,
+    fetchExportOutStockReport,
+    fetchGetOutStockDetail,
+    fetchGetOutStockMany,
+    fetchOutStockPage
+  } from '@/api/out-stock'
+  import OutStockDetailDrawer from './modules/out-stock-detail-drawer.vue'
+  import {
+    OUT_STOCK_REPORT_TITLE,
+    OUT_STOCK_REPORT_STYLE,
+    buildOutStockPageQueryParams,
+    buildOutStockPrintRows,
+    buildOutStockReportMeta,
+    buildOutStockSearchParams,
+    createOutStockSearchState,
+    normalizeOutStockRow
+  } from './outStockPage.helpers'
+  import { createOutStockTableColumns } from './outStockTable.columns'
+
+  defineOptions({ name: 'OutStock' })
+
+  const userStore = useUserStore()
+  const reportTitle = OUT_STOCK_REPORT_TITLE
+  const searchForm = ref(createOutStockSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const activeOutStockId = ref(null)
+
+  const reportQueryParams = computed(() => buildOutStockSearchParams(searchForm.value))
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ嚭搴撳崟鍙�/PO鍗曞彿/瀹㈡埛' } },
+    { label: '鍑哄簱鍗曞彿', key: 'code', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ嚭搴撳崟鍙�' } },
+    { label: 'PO鍗曞彿', key: 'poCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏O鍗曞彿' } },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '閿�鍞嚭搴撳崟', value: 'sales_out' },
+          { label: '璋冩嫧鍑哄簱鍗�', value: 'transfer_out' },
+          { label: '搴撳瓨鍑哄簱鍗�', value: 'stock_out' },
+          { label: '澶囪揣鍑哄簱鍗�', value: 'pre_out' }
+        ]
+      }
+    },
+    {
+      label: '鍗曟嵁鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鍒濆鍖�', value: 10 },
+          { label: '寰呭鐞�', value: 11 },
+          { label: '鐢熸垚宸ヤ綔妗�', value: 13 },
+          { label: '浣滀笟涓�', value: 14 },
+          { label: '宸插畬鎴�', value: 15 },
+          { label: '鍙栨秷', value: 8 }
+        ]
+      }
+    },
+    {
+      label: '閲婃斁鐘舵��',
+      key: 'rleStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 0 },
+          { label: '宸查噴鏀�', value: 1 }
+        ]
+      }
+    },
+    { label: '鐗╂祦鍗曞彿', key: 'logisNo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿娴佸崟鍙�' } },
+    { label: '瀹㈡埛鍚嶇О', key: 'customerName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ鎴峰悕绉�' } },
+    { label: '閿�鍞粍缁�', key: 'saleOrgName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ラ攢鍞粍缁�' } },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  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
+      })
+  }
+
+  async function handleComplete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瀹屾垚鍑哄簱鍗� ${row.code || ''} 鍚楋紵`, '瀹屾垚纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchCompleteOutStock(row.id)
+      ElMessage.success('瀹屾垚鎴愬姛')
+      await refreshData()
+      if (detailDrawerVisible.value && activeOutStockId.value === row.id) {
+        openDetail(row)
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '瀹屾垚澶辫触')
+    }
+  }
+
+  async function handleCancel(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾鍙栨秷鍑哄簱鍗� ${row.code || ''} 鍚楋紵`, '鍙栨秷纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchCancelOutStock(row.id)
+      ElMessage.success('鍙栨秷鎴愬姛')
+      await refreshData()
+      if (detailDrawerVisible.value && activeOutStockId.value === row.id) {
+        openDetail(row)
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '鍙栨秷澶辫触')
+    }
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾鍒犻櫎鍑哄簱鍗� ${row.code || ''} 鍚楋紵`, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteOutStock(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    if (action.key === 'view') {
+      openDetail(row)
+      return
+    }
+    if (action.key === 'print') {
+      await handlePrint({ ids: [row.id], pageSize: 1 })
+      return
+    }
+    if (action.key === 'complete') {
+      await handleComplete(row)
+      return
+    }
+    if (action.key === 'cancel') {
+      await handleCancel(row)
+      return
+    }
+    if (action.key === 'delete') {
+      await handleDelete(row)
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchOutStockPage,
+      apiParams: buildOutStockPageQueryParams(searchForm.value),
+      columnsFactory: () => createOutStockTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeOutStockRow(item)) : [])
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetOutStockMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchOutStockPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const { previewVisible, previewRows, previewMeta: rawPreviewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
+    usePrintExportPage({
+      downloadFileName: 'out-stock.xlsx',
+      requestExport: (payload) =>
+        fetchExportOutStockReport(payload, {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+      resolvePrintRecords,
+      buildPreviewRows: (records) => buildOutStockPrintRows(records),
+      buildPreviewMeta: (rows) => {
+        const now = new Date()
+        return {
+          reportTitle,
+          reportDate: now.toLocaleDateString('zh-CN'),
+          printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+          operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+          count: rows.length,
+          reportStyle: { ...OUT_STOCK_REPORT_STYLE }
+        }
+      }
+    })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildOutStockReportMeta({
+      previewMeta: rawPreviewMeta.value,
+      count: previewRows.value.length,
+      orientation: rawPreviewMeta.value?.reportStyle?.orientation || OUT_STOCK_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildOutStockPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createOutStockSearchState()
+    resetSearchParams()
+  }
+</script>
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
new file mode 100644
index 0000000..feebb70
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue
@@ -0,0 +1,74 @@
+<template>
+  <ElDrawer
+    v-model="visibleProxy"
+    :title="'鍑哄簱鍗曡鎯�'"
+    size="720px"
+    destroy-on-close
+    append-to-body
+  >
+    <ElScrollbar class="h-full">
+      <ElSkeleton :loading="loading" animated :rows="8">
+        <div class="space-y-4">
+          <ElDescriptions :column="2" border>
+            <ElDescriptionsItem label="鍑哄簱鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="PO鍗曞彿">{{ detail.poCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeLabel || '--' }}</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="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions :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>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'OutStockDetailDrawer' })
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visibleProxy = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+</script>
diff --git a/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
new file mode 100644
index 0000000..d31e6ed
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
@@ -0,0 +1,211 @@
+const OUT_STOCK_STATUS_META = {
+  8: { text: '鍙栨秷', type: 'danger' },
+  10: { text: '鍒濆鍖�', type: 'info' },
+  11: { text: '寰呭鐞�', type: 'warning' },
+  13: { text: '鐢熸垚宸ヤ綔妗�', type: 'primary' },
+  14: { text: '浣滀笟涓�', type: 'warning' },
+  15: { text: '宸插畬鎴�', type: 'success' }
+}
+
+const OUT_STOCK_RLE_STATUS_META = {
+  0: { text: '姝e父', type: 'success' },
+  1: { text: '宸查噴鏀�', type: 'warning' }
+}
+
+const OUT_STOCK_TYPE_META = {
+  out: '鍑哄簱鍗�'
+}
+
+export const OUT_STOCK_REPORT_TITLE = '鍑哄簱鍗曟姤琛�'
+export const OUT_STOCK_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeStatusMeta(exceStatus, exceStatusText) {
+  if (exceStatusText) {
+    const fallback = OUT_STOCK_STATUS_META[Number(exceStatus)] || {
+      text: exceStatusText,
+      type: 'info'
+    }
+    return fallback
+  }
+  return OUT_STOCK_STATUS_META[Number(exceStatus)] || {
+    text: normalizeText(exceStatus) || '--',
+    type: 'info'
+  }
+}
+
+function normalizeRleStatusMeta(rleStatus, rleStatusText) {
+  if (rleStatusText) {
+    const fallback = OUT_STOCK_RLE_STATUS_META[Number(rleStatus)] || {
+      text: rleStatusText,
+      type: 'info'
+    }
+    return fallback
+  }
+  return OUT_STOCK_RLE_STATUS_META[Number(rleStatus)] || {
+    text: normalizeText(rleStatus) || '--',
+    type: 'info'
+  }
+}
+
+export function createOutStockSearchState() {
+  return {
+    condition: '',
+    code: '',
+    poCode: '',
+    wkType: '',
+    exceStatus: '',
+    rleStatus: '',
+    logisNo: '',
+    customerName: '',
+    saleOrgName: '',
+    memo: ''
+  }
+}
+
+export function buildOutStockSearchParams(params = {}) {
+  const result = {
+    type: 'out'
+  }
+
+  ;['condition', 'code', 'poCode', 'wkType', 'logisNo', 'customerName', 'saleOrgName', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = normalizeNumber(params.exceStatus)
+  }
+
+  if (params.rleStatus !== '' && params.rleStatus !== undefined && params.rleStatus !== null) {
+    result.rleStatus = normalizeNumber(params.rleStatus)
+  }
+
+  return result
+}
+
+export function buildOutStockPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutStockSearchParams(params)
+  }
+}
+
+export function normalizeOutStockRow(record = {}) {
+  const statusMeta = normalizeStatusMeta(record.exceStatus, record['exceStatus$'] || record.exceStatusText)
+  const rleStatusMeta = normalizeRleStatusMeta(record.rleStatus, record['rleStatus$'] || record.rleStatusText)
+  const typeValue = normalizeText(record['type$'] || record.type)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: normalizeText(record.code) || '--',
+    poCode: normalizeText(record.poCode) || '--',
+    typeLabel: OUT_STOCK_TYPE_META[typeValue] || typeValue || '--',
+    wkTypeLabel: normalizeText(record['wkType$'] || record.wkType) || '--',
+    exceStatusText: statusMeta.text,
+    exceStatusTagType: statusMeta.type,
+    rleStatusText: rleStatusMeta.text,
+    rleStatusTagType: rleStatusMeta.type,
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    logisNo: normalizeText(record.logisNo) || '--',
+    saleOrgName: normalizeText(record.saleOrgName) || '--',
+    saleUserName: normalizeText(record.saleUserName) || '--',
+    businessTimeText: normalizeText(record['businessTime$'] || record.businessTimeText || record.businessTime) || '--',
+    customerId: normalizeText(record.customerId) || '--',
+    customerName: normalizeText(record.customerName) || '--',
+    stockOrgName: normalizeText(record.stockOrgName) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--',
+    canComplete: Number(record.exceStatus) !== 15,
+    canCancel: Number(record.exceStatus) === 10,
+    canDelete: Number(record.exceStatus) !== 15
+  }
+}
+
+export function getOutStockActionList(row = {}) {
+  const normalizedRow = normalizeOutStockRow(row)
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    },
+    {
+      key: 'print',
+      label: '鎵撳嵃',
+      icon: 'ri:printer-line'
+    },
+    {
+      key: 'complete',
+      label: '瀹屾垚',
+      icon: 'ri:check-line',
+      color: 'var(--el-color-success)',
+      disabled: !normalizedRow.canComplete
+    },
+    {
+      key: 'cancel',
+      label: '鍙栨秷',
+      icon: 'ri:close-circle-line',
+      color: 'var(--el-color-danger)',
+      disabled: !normalizedRow.canCancel
+    },
+    {
+      key: 'delete',
+      label: '鍒犻櫎',
+      icon: 'ri:delete-bin-6-line',
+      color: 'var(--el-color-danger)',
+      disabled: !normalizedRow.canDelete
+    }
+  ]
+}
+
+export function buildOutStockPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeOutStockRow(record))
+}
+
+export function buildOutStockReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = OUT_STOCK_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: OUT_STOCK_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...OUT_STOCK_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/out-stock/outStockTable.columns.js b/rsf-design/src/views/orders/out-stock/outStockTable.columns.js
new file mode 100644
index 0000000..088b96c
--- /dev/null
+++ b/rsf-design/src/views/orders/out-stock/outStockTable.columns.js
@@ -0,0 +1,113 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getOutStockActionList } from './outStockPage.helpers'
+
+export function createOutStockTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '鍑哄簱鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'poCode',
+      label: 'PO鍗曞彿',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.poCode || '--'
+    },
+    {
+      prop: 'typeLabel',
+      label: '鍑哄簱绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeLabel || '--'
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeLabel || '--'
+    },
+    {
+      prop: 'customerName',
+      label: '瀹㈡埛',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.customerName || '--'
+    },
+    {
+      prop: 'saleOrgName',
+      label: '閿�鍞粍缁�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.saleOrgName || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '搴斿嚭鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插嚭鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'logisNo',
+      label: '鐗╂祦鍗曞彿',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.logisNo || '--'
+    },
+    {
+      prop: 'rleStatusText',
+      label: '閲婃斁鐘舵��',
+      width: 110,
+      formatter: (row) =>
+        h(ElTag, { type: row.rleStatusTagType || 'info', effect: 'light' }, () => row.rleStatusText || '--')
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鍗曟嵁鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getOutStockActionList(row),
+          onClick: (item) => handleActionClick?.(item, row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/preparation-item/index.vue b/rsf-design/src/views/orders/preparation-item/index.vue
new file mode 100644
index 0000000..07a12ef
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation-item/index.vue
@@ -0,0 +1,300 @@
+<template>
+  <div class="preparation-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="resolvedPreviewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <OutStockItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useRoute } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportPreparationItemReport,
+    fetchGetPreparationItemDetail,
+    fetchGetPreparationItemMany,
+    fetchPreparationItemPage
+  } from '@/api/preparation-item'
+  import { createOutStockItemTableColumns } from '../out-stock-item/outStockItemTable.columns.js'
+  import OutStockItemDetailDrawer from '../out-stock-item/modules/out-stock-item-detail-drawer.vue'
+  import {
+    PREPARATION_ITEM_REPORT_STYLE,
+    PREPARATION_ITEM_REPORT_TITLE,
+    buildPreparationItemPageQueryParams,
+    buildPreparationItemPrintRows,
+    buildPreparationItemReportMeta,
+    buildPreparationItemSearchParams,
+    createPreparationItemSearchState,
+    getPreparationItemPaginationKey,
+    getPreparationItemReportColumns,
+    normalizePreparationItemRow
+  } from './preparationItemPage.helpers.js'
+
+  defineOptions({ name: 'PreparationItem' })
+
+  const route = useRoute()
+  const userStore = useUserStore()
+  const initialOrderId = route.query.orderId || route.query.id
+  const searchForm = ref(
+    createPreparationItemSearchState({
+      orderId: initialOrderId !== undefined ? Number(initialOrderId) || '' : ''
+    })
+  )
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = PREPARATION_ITEM_REPORT_TITLE
+  const reportColumns = getPreparationItemReportColumns()
+  const reportQueryParams = computed(() => buildPreparationItemSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鏂欏崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О'
+      }
+    },
+    {
+      label: '澶囨枡鍗旾D',
+      key: 'orderId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ鏂欏崟ID'
+      }
+    },
+    {
+      label: '澶囨枡鍗曞彿',
+      key: 'orderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鏂欏崟鍙�'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetPreparationItemDetail(row.id), {}, {
+        timeoutMessage: '澶囨枡鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      detailData.value = normalizePreparationItemRow({
+        ...row,
+        ...(detail || {})
+      })
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇澶囨枡鍗曟槑缁嗚鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchPreparationItemPage,
+      apiParams: buildPreparationItemPageQueryParams(searchForm.value),
+      paginationKey: getPreparationItemPaginationKey(),
+      columnsFactory: () => createOutStockItemTableColumns({ handleActionClick: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizePreparationItemRow(item)) : []
+    }
+  })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildPreparationItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    const resetSeed =
+      initialOrderId !== undefined ? { orderId: Number(initialOrderId) || '' } : {}
+    Object.assign(searchForm.value, createPreparationItemSearchState(resetSeed))
+    resetSearchParams(buildPreparationItemPageQueryParams(createPreparationItemSearchState(resetSeed)))
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetPreparationItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchPreparationItemPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'preparation-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportPreparationItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildPreparationItemPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: {
+        ...PREPARATION_ITEM_REPORT_STYLE
+      }
+    })
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildPreparationItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || PREPARATION_ITEM_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js b/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js
new file mode 100644
index 0000000..93afb51
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation-item/preparationItemPage.helpers.js
@@ -0,0 +1,111 @@
+import {
+  buildOutStockItemSearchParams,
+  normalizeOutStockItemRow
+} from '../out-stock-item/outStockItemPage.helpers.js'
+
+export const PREPARATION_ITEM_REPORT_TITLE = '澶囨枡鍗曟槑缁嗘姤琛�'
+export const PREPARATION_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+export function createPreparationItemSearchState(seed = {}) {
+  return {
+    condition: '',
+    orderId: '',
+    orderCode: '',
+    poCode: '',
+    platItemId: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    splrBatch: '',
+    trackCode: '',
+    barcode: '',
+    fieldsIndex: '',
+    status: '',
+    ...seed
+  }
+}
+
+export function getPreparationItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildPreparationItemSearchParams(params = {}) {
+  const result = {
+    ...buildOutStockItemSearchParams(params)
+  }
+
+  if (params.orderId !== '' && params.orderId !== undefined && params.orderId !== null) {
+    const numericValue = Number(params.orderId)
+    if (!Number.isNaN(numericValue)) {
+      result.orderId = numericValue
+    }
+  }
+
+  return result
+}
+
+export function buildPreparationItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildPreparationItemSearchParams(params)
+  }
+}
+
+export function normalizePreparationItemRow(record = {}) {
+  return normalizeOutStockItemRow(record)
+}
+
+export function getPreparationItemReportColumns() {
+  return [
+    { key: 'orderCode', label: '澶囨枡鍗曞彿' },
+    { key: 'poCode', label: 'PO鍗曞彿' },
+    { key: 'platItemId', label: '骞冲彴琛屽彿' },
+    { key: 'matnrCode', label: '鐗╂枡缂栫爜' },
+    { key: 'maktx', label: '鐗╂枡鍚嶇О' },
+    { key: 'batch', label: '鎵规' },
+    { key: 'splrBatch', label: '渚涘簲鍟嗘壒娆�' },
+    { key: 'stockUnit', label: '搴撳瓨鍗曚綅' },
+    { key: 'anfme', label: '鏁伴噺' },
+    { key: 'workQty', label: '鎵ц鏁伴噺' },
+    { key: 'qty', label: '宸插嚭鏁伴噺' },
+    { key: 'fieldsIndex', label: '瀛楁绱㈠紩' },
+    { key: 'statusText', label: '鐘舵��' },
+    { key: 'updateTimeText', label: '鏇存柊鏃堕棿' },
+    { key: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildPreparationItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizePreparationItemRow(record))
+}
+
+export function buildPreparationItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = PREPARATION_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: PREPARATION_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...PREPARATION_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/preparation/index.vue b/rsf-design/src/views/orders/preparation/index.vue
new file mode 100644
index 0000000..d918f5b
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/index.vue
@@ -0,0 +1,385 @@
+<template>
+  <div class="preparation-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ListExportPrint
+              :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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <PreparationDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchCancelPreparation,
+    fetchCompletePreparation,
+    fetchDeletePreparation,
+    fetchExportPreparationReport,
+    fetchGetPreparationDetail,
+    fetchGetPreparationMany,
+    fetchPreparationItemPage,
+    fetchPreparationPage
+  } from '@/api/preparation'
+  import PreparationDetailDrawer from './modules/preparation-detail-drawer.vue'
+  import { createPreparationTableColumns } from './preparationTable.columns'
+  import {
+    buildPreparationDetailQueryParams,
+    buildPreparationPageQueryParams,
+    buildPreparationPrintRows,
+    buildPreparationReportMeta,
+    buildPreparationSearchParams,
+    createPreparationDetailItemColumns,
+    createPreparationSearchState,
+    normalizePreparationItemRow,
+    normalizePreparationRow,
+    PREPARATION_REPORT_STYLE,
+    PREPARATION_REPORT_TITLE
+  } from './preparationPage.helpers'
+
+  defineOptions({ name: 'Preparation' })
+
+  const userStore = useUserStore()
+  const reportTitle = PREPARATION_REPORT_TITLE
+  const searchForm = ref(createPreparationSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activePreparationId = ref(null)
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildPreparationSearchParams(searchForm.value))
+  const detailColumns = computed(() => createPreparationDetailItemColumns())
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ鏂欏崟鍙�/PO鍗曞彿/瀹㈡埛' }
+    },
+    {
+      label: '澶囨枡鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ鏂欏崟鍙�' }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'poCode',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏� PO 鍗曞彿' }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�' }
+    },
+    {
+      label: '鍗曟嵁鐘舵��',
+      key: 'exceStatus',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ョ姸鎬�' }
+    },
+    {
+      label: '閲婃斁鐘舵��',
+      key: 'rleStatus',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ラ噴鏀剧姸鎬�' }
+    },
+    {
+      label: '瀹㈡埛',
+      key: 'customerName',
+      type: 'input',
+      props: { clearable: true, placeholder: '璇疯緭鍏ュ鎴�' }
+    }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function openDetail(row) {
+    activePreparationId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function loadDetailResources() {
+    if (!activePreparationId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(fetchGetPreparationDetail(activePreparationId.value), {}, {
+          timeoutMessage: '澶囨枡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }),
+        guardRequestWithMessage(
+          fetchPreparationItemPage(
+            buildPreparationDetailQueryParams({
+              orderId: activePreparationId.value,
+              current: detailPagination.current,
+              pageSize: detailPagination.size
+            })
+          ),
+          {
+            records: [],
+            total: 0,
+            current: detailPagination.current,
+            size: detailPagination.size
+          },
+          {
+            timeoutMessage: '澶囨枡鍗曟槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+          }
+        )
+      ])
+
+      detailData.value = normalizePreparationRow(detailResponse)
+      detailTableData.value = Array.isArray(itemResponse?.records)
+        ? itemResponse.records.map((item) => normalizePreparationItemRow(item))
+        : []
+      updatePaginationState(
+        detailPagination,
+        itemResponse,
+        detailPagination.current,
+        detailPagination.size
+      )
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) {
+      return
+    }
+
+    try {
+      if (action.key === 'view') {
+        await openDetail(row)
+        return
+      }
+
+      if (action.key === 'print') {
+        await handlePrint({ ids: [row.id], pageSize: 1 })
+        return
+      }
+
+      if (action.key === 'complete') {
+        await ElMessageBox.confirm(`纭畾瀹屾垚澶囨枡鍗� ${row.code || ''} 鍚楋紵`, '瀹屾垚纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchCompletePreparation(row.id)
+        ElMessage.success('澶囨枡鍗曞凡瀹屾垚')
+        await refreshData()
+        if (detailDrawerVisible.value && activePreparationId.value === row.id) {
+          await loadDetailResources()
+        }
+        return
+      }
+
+      if (action.key === 'cancel') {
+        await ElMessageBox.confirm(`纭畾鍙栨秷澶囨枡鍗� ${row.code || ''} 鍚楋紵`, '鍙栨秷纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchCancelPreparation(row.id)
+        ElMessage.success('澶囨枡鍗曞凡鍙栨秷')
+        await refreshData()
+        if (detailDrawerVisible.value && activePreparationId.value === row.id) {
+          await loadDetailResources()
+        }
+        return
+      }
+
+      if (action.key === 'delete') {
+        await ElMessageBox.confirm(`纭畾鍒犻櫎澶囨枡鍗� ${row.code || ''} 鍚楋紵`, '鍒犻櫎纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchDeletePreparation(row.id)
+        ElMessage.success('澶囨枡鍗曞凡鍒犻櫎')
+        await refreshData()
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') {
+        return
+      }
+      ElMessage.error(error?.message || '澶囨枡鍗曟搷浣滃け璐�')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchPreparationPage,
+      apiParams: buildPreparationPageQueryParams(searchForm.value),
+      columnsFactory: () => createPreparationTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizePreparationRow(item)) : []
+    }
+  })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildPreparationPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createPreparationSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'preparation.xlsx',
+    requestExport: (payload) =>
+      fetchExportPreparationReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetPreparationMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchPreparationPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize:
+            Number(pagination.total) > 0
+              ? Number(pagination.total)
+              : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildPreparationPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...PREPARATION_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildPreparationReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || PREPARATION_REPORT_STYLE.orientation
+    })
+  )
+</script>
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
new file mode 100644
index 0000000..8e2c335
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/modules/preparation-detail-drawer.vue
@@ -0,0 +1,67 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="澶囨枡鍗曡鎯�"
+    size="88%"
+    @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>
+
+      <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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="$emit('size-change', $event)"
+        @pagination:current-change="$emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'PreparationDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/preparation/preparationPage.helpers.js b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
new file mode 100644
index 0000000..5a42752
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/preparationPage.helpers.js
@@ -0,0 +1,250 @@
+const PREPARATION_STATUS_META = {
+  8: { text: '鍙栨秷', type: 'danger' },
+  10: { text: '鍒濆鍖�', type: 'info' },
+  11: { text: '寰呭鐞�', type: 'warning' },
+  13: { text: '鐢熸垚宸ヤ綔妗�', type: 'primary' },
+  14: { text: '浣滀笟涓�', type: 'warning' },
+  15: { text: '宸插畬鎴�', type: 'success' }
+}
+
+const PREPARATION_RLE_STATUS_META = {
+  0: { text: '姝e父', type: 'success' },
+  1: { text: '宸查噴鏀�', type: 'warning' }
+}
+
+export const PREPARATION_REPORT_TITLE = '澶囨枡鍗曟姤琛�'
+export const PREPARATION_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeStatusMeta(exceStatus, exceStatusText) {
+  if (exceStatusText) {
+    return PREPARATION_STATUS_META[Number(exceStatus)] || {
+      text: exceStatusText,
+      type: 'info'
+    }
+  }
+
+  return PREPARATION_STATUS_META[Number(exceStatus)] || {
+    text: normalizeText(exceStatus) || '--',
+    type: 'info'
+  }
+}
+
+function normalizeRleStatusMeta(rleStatus, rleStatusText) {
+  if (rleStatusText) {
+    return PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
+      text: rleStatusText,
+      type: 'info'
+    }
+  }
+
+  return PREPARATION_RLE_STATUS_META[Number(rleStatus)] || {
+    text: normalizeText(rleStatus) || '--',
+    type: 'info'
+  }
+}
+
+export function createPreparationSearchState() {
+  return {
+    condition: '',
+    code: '',
+    poCode: '',
+    wkType: '',
+    exceStatus: '',
+    rleStatus: '',
+    logisNo: '',
+    customerName: '',
+    saleOrgName: '',
+    memo: ''
+  }
+}
+
+export function buildPreparationSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'code',
+    'poCode',
+    'wkType',
+    'logisNo',
+    'customerName',
+    'saleOrgName',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = normalizeNumber(params.exceStatus)
+  }
+
+  if (params.rleStatus !== '' && params.rleStatus !== undefined && params.rleStatus !== null) {
+    result.rleStatus = normalizeNumber(params.rleStatus)
+  }
+
+  return result
+}
+
+export function buildPreparationPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildPreparationSearchParams(params)
+  }
+}
+
+export function buildPreparationDetailQueryParams(params = {}) {
+  return {
+    orderId: Number(params.orderId) || 0,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizePreparationRow(record = {}) {
+  const statusMeta = normalizeStatusMeta(
+    record.exceStatus,
+    record['exceStatus$'] || record.exceStatusText
+  )
+  const rleStatusMeta = normalizeRleStatusMeta(
+    record.rleStatus,
+    record['rleStatus$'] || record.rleStatusText
+  )
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: normalizeText(record.code) || '--',
+    poCode: normalizeText(record.poCode) || '--',
+    typeLabel: normalizeText(record['type$'] || record.type) || '--',
+    wkTypeLabel: normalizeText(record['wkType$'] || record.wkType) || '--',
+    exceStatusText: statusMeta.text,
+    exceStatusTagType: statusMeta.type,
+    rleStatusText: rleStatusMeta.text,
+    rleStatusTagType: rleStatusMeta.type,
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    logisNo: normalizeText(record.logisNo) || '--',
+    saleOrgName: normalizeText(record.saleOrgName) || '--',
+    saleUserName: normalizeText(record.saleUserName) || '--',
+    businessTimeText:
+      normalizeText(record['businessTime$'] || record.businessTimeText || record.businessTime) ||
+      '--',
+    customerId: normalizeText(record.customerId) || '--',
+    customerName: normalizeText(record.customerName) || '--',
+    stockOrgName: normalizeText(record.stockOrgName) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText:
+      normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText:
+      normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--',
+    canComplete: Number(record.exceStatus) !== 15,
+    canCancel: Number(record.exceStatus) === 10,
+    canDelete: Number(record.exceStatus) !== 15
+  }
+}
+
+export function normalizePreparationItemRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    maktx: normalizeText(record.maktx) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    splrName: normalizeText(record.splrName) || '--',
+    anfme: record.anfme ?? '--',
+    qty: record.qty ?? '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getPreparationActionList(row = {}) {
+  const normalizedRow = normalizePreparationRow(row)
+  return [
+    { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:eye-line' },
+    { key: 'print', label: '鎵撳嵃', icon: 'ri:printer-line' },
+    {
+      key: 'complete',
+      label: '瀹屾垚',
+      icon: 'ri:check-line',
+      color: 'var(--el-color-success)',
+      disabled: !normalizedRow.canComplete
+    },
+    {
+      key: 'cancel',
+      label: '鍙栨秷',
+      icon: 'ri:close-circle-line',
+      color: 'var(--el-color-danger)',
+      disabled: !normalizedRow.canCancel
+    },
+    {
+      key: 'delete',
+      label: '鍒犻櫎',
+      icon: 'ri:delete-bin-6-line',
+      color: 'var(--el-color-danger)',
+      disabled: !normalizedRow.canDelete
+    }
+  ]
+}
+
+export function createPreparationDetailItemColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'maktx', label: '鐗╂枡鍚嶇О', minWidth: 180, showOverflowTooltip: true },
+    { prop: 'splrBatch', label: '渚涘簲鍟嗘壒娆�', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'splrName', label: '渚涘簲鍟�', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'anfme', label: '搴斿嚭鏁伴噺', width: 100, align: 'right' },
+    { prop: 'qty', label: '宸插嚭鏁伴噺', width: 100, align: 'right' },
+    { prop: 'memo', label: '澶囨敞', minWidth: 160, showOverflowTooltip: true }
+  ]
+}
+
+export function buildPreparationPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizePreparationRow(record))
+}
+
+export function buildPreparationReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = PREPARATION_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: PREPARATION_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...PREPARATION_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/preparation/preparationTable.columns.js b/rsf-design/src/views/orders/preparation/preparationTable.columns.js
new file mode 100644
index 0000000..40bf81f
--- /dev/null
+++ b/rsf-design/src/views/orders/preparation/preparationTable.columns.js
@@ -0,0 +1,55 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getPreparationActionList } from './preparationPage.helpers'
+
+export function createPreparationTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'code', label: '澶囨枡鍗曞彿', minWidth: 170, showOverflowTooltip: true },
+    { prop: 'poCode', label: 'PO鍗曞彿', minWidth: 150, showOverflowTooltip: true },
+    { prop: 'typeLabel', label: '鍗曟嵁绫诲瀷', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'wkTypeLabel', label: '涓氬姟绫诲瀷', minWidth: 130, showOverflowTooltip: true },
+    { prop: 'customerName', label: '瀹㈡埛', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'saleOrgName', label: '閿�鍞粍缁�', minWidth: 150, showOverflowTooltip: true },
+    { prop: 'anfme', label: '搴斿嚭鏁伴噺', width: 100, align: 'right' },
+    { prop: 'workQty', label: '鎵ц鏁伴噺', width: 100, align: 'right' },
+    { prop: 'qty', label: '宸插嚭鏁伴噺', width: 100, align: 'right' },
+    { prop: 'logisNo', label: '鐗╂祦鍗曞彿', minWidth: 140, showOverflowTooltip: true },
+    {
+      prop: 'rleStatusText',
+      label: '閲婃斁鐘舵��',
+      width: 110,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.rleStatusTagType || 'info', effect: 'light' },
+          () => row.rleStatusText || '--'
+        )
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鍗曟嵁鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.exceStatusTagType || 'info', effect: 'light' },
+          () => row.exceStatusText || '--'
+        )
+    },
+    { prop: 'updateTimeText', label: '鏇存柊鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getPreparationActionList(row),
+          onClick: (item) => handleActionClick?.(item, row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/purchase-item/index.vue b/rsf-design/src/views/orders/purchase-item/index.vue
new file mode 100644
index 0000000..8ded304
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase-item/index.vue
@@ -0,0 +1,370 @@
+<template>
+  <div class="purchase-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <PurchaseItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail-data="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportPurchaseItemReport,
+    fetchPurchaseItemDetail,
+    fetchPurchaseItemMany,
+    fetchPurchaseItemPage
+  } from '@/api/purchase-item'
+  import PurchaseItemDetailDrawer from './modules/purchase-item-detail-drawer.vue'
+  import { createPurchaseItemTableColumns } from './purchaseItemTable.columns.js'
+  import {
+    PURCHASE_ITEM_REPORT_STYLE,
+    PURCHASE_ITEM_REPORT_TITLE,
+    buildPurchaseItemPageQueryParams,
+    buildPurchaseItemPrintRows,
+    buildPurchaseItemReportMeta,
+    buildPurchaseItemSearchParams,
+    createPurchaseItemSearchState,
+    getPurchaseItemPaginationKey,
+    normalizePurchaseItemRow
+  } from './purchaseItemPage.helpers.js'
+
+  defineOptions({ name: 'PurchaseItem' })
+
+  const userStore = useUserStore()
+  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 searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ富鍗曟爣璇�/鐗╂枡缂栫爜/渚涘簲鍟嗕俊鎭�'
+      }
+    },
+    {
+      label: '涓诲崟鏍囪瘑',
+      key: 'purchaseId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ富鍗曟爣璇�'
+      }
+    },
+    {
+      label: 'ERP琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏RP琛屽彿'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'matnrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鍗曚綅',
+      key: 'unit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟浣�'
+      }
+    },
+    {
+      label: '鏁伴噺',
+      key: 'anfme',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ暟閲�'
+      }
+    },
+    {
+      label: '宸叉敹鏁伴噺',
+      key: 'qty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ凡鏀舵暟閲�'
+      }
+    },
+    {
+      label: '鏍囧噯鍖呰',
+      key: 'nromQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ爣鍑嗗寘瑁�'
+      }
+    },
+    {
+      label: 'ASN鍗曟嵁鏁伴噺',
+      key: 'asnQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏SN鍗曟嵁鏁伴噺'
+      }
+    },
+    {
+      label: '鏉$爜鎵撳嵃鏁伴噺',
+      key: 'printQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ潯鐮佹墦鍗版暟閲�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗗悕绉�',
+      key: 'splrName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗙紪鐮�',
+      key: 'splrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢缂栫爜'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    }
+  ])
+
+  const reportQueryParams = computed(() => buildPurchaseItemSearchParams(searchForm.value))
+
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData } =
+    useTable({
+      core: {
+        apiFn: fetchPurchaseItemPage,
+        apiParams: buildPurchaseItemPageQueryParams(searchForm.value),
+        paginationKey: getPurchaseItemPaginationKey(),
+        columnsFactory: () =>
+          createPurchaseItemTableColumns({
+            handleView: openDetail
+          })
+      },
+      transform: {
+        dataTransformer: (records) =>
+          Array.isArray(records) ? records.map((item) => normalizePurchaseItemRow(item)) : []
+      }
+    })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchPurchaseItemDetail(row.id), {}, {
+        timeoutMessage: 'PO鍗曟槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      detailData.value = normalizePurchaseItemRow({
+        ...row,
+        ...(detail || {})
+      })
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇PO鍗曟槑缁嗚鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildPurchaseItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createPurchaseItemSearchState())
+    resetSearchParams()
+  }
+
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportTitle,
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: {
+        ...PURCHASE_ITEM_REPORT_STYLE
+      }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchPurchaseItemMany(payload.ids)).records
+    }
+
+    return defaultResponseAdapter(
+      await fetchPurchaseItemPage({
+        ...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: 'purchase-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportPurchaseItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildPurchaseItemPrintRows(records),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildPurchaseItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || PURCHASE_ITEM_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(() => {
+    getData()
+  })
+</script>
diff --git a/rsf-design/src/views/orders/purchase-item/modules/purchase-item-detail-drawer.vue b/rsf-design/src/views/orders/purchase-item/modules/purchase-item-detail-drawer.vue
new file mode 100644
index 0000000..8b9bd8f
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase-item/modules/purchase-item-detail-drawer.vue
@@ -0,0 +1,72 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="PO鍗曟槑缁嗚鎯�"
+    size="72%"
+    destroy-on-close
+    append-to-body
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElSkeleton :loading="loading" animated :rows="10">
+        <div class="flex min-h-full flex-col gap-4 pr-2">
+          <ElDescriptions title="鍩虹淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="鏄庣粏ID">{{ displayData.id ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="涓诲崟鏍囪瘑">{{ displayData.purchaseId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="ERP琛屽彿">{{ displayData.platItemId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ displayData.matnrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ displayData.matnrName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍗曚綅">{{ displayData.unit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">
+              <ElTag :type="displayData.statusType || 'info'" effect="light">
+                {{ displayData.statusText || '--' }}
+              </ElTag>
+            </ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="2">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="鏁伴噺淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="鏁伴噺">{{ displayData.anfme ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ displayData.qty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏍囧噯鍖呰">{{ displayData.nromQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="ASN鍗曟嵁鏁伴噺">{{ displayData.asnQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏉$爜鎵撳嵃鏁伴噺">{{ displayData.printQty ?? '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="渚涘簲鍟嗕俊鎭�" :column="3" border>
+            <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ displayData.splrName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ displayData.splrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ displayData.splrBatch || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="瀹¤淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="淇敼浜�">{{ displayData.updateByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="淇敼鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </div>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import { normalizePurchaseItemRow } from '../purchaseItemPage.helpers.js'
+
+  defineOptions({ name: 'PurchaseItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizePurchaseItemRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/purchase-item/purchaseItemPage.helpers.js b/rsf-design/src/views/orders/purchase-item/purchaseItemPage.helpers.js
new file mode 100644
index 0000000..d7793f8
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase-item/purchaseItemPage.helpers.js
@@ -0,0 +1,183 @@
+export const PURCHASE_ITEM_REPORT_TITLE = 'PO鍗曟槑缁嗘姤琛�'
+export const PURCHASE_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+function normalizeText(value, fallback = '--') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  const text = String(value).trim()
+  return text || fallback
+}
+
+function normalizeNumber(value, fallback = '--') {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function toSearchValue(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const text = String(value).trim()
+  return text || void 0
+}
+
+function toSearchNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return void 0
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? void 0 : parsed
+}
+
+export function createPurchaseItemSearchState() {
+  return {
+    condition: '',
+    purchaseId: '',
+    platItemId: '',
+    matnrCode: '',
+    matnrName: '',
+    unit: '',
+    anfme: '',
+    qty: '',
+    nromQty: '',
+    asnQty: '',
+    printQty: '',
+    splrName: '',
+    splrCode: '',
+    splrBatch: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getPurchaseItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getPurchaseItemStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getPurchaseItemStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildPurchaseItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'platItemId',
+    'matnrCode',
+    'matnrName',
+    'unit',
+    'splrName',
+    'splrCode',
+    'splrBatch',
+    'memo'
+  ].forEach((key) => {
+    const value = toSearchValue(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['purchaseId', 'anfme', 'qty', 'nromQty', 'asnQty', 'printQty', 'status'].forEach((key) => {
+    const value = toSearchNumber(params[key])
+    if (value !== void 0) {
+      result[key] = value
+    }
+  })
+
+  return result
+}
+
+export function buildPurchaseItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildPurchaseItemSearchParams(params)
+  }
+}
+
+export function normalizePurchaseItemRow(record = {}) {
+  const statusMeta = getPurchaseItemStatusMeta(record.statusBool ?? record.status)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    purchaseId: normalizeNumber(record.purchaseId),
+    platItemId: normalizeText(record.platItemId),
+    matnrCode: normalizeText(record.matnrCode),
+    matnrName: normalizeText(record.matnrName),
+    unit: normalizeText(record.unit),
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    nromQty: normalizeNumber(record.nromQty),
+    asnQty: normalizeNumber(record.asnQty),
+    printQty: normalizeNumber(record.printQty),
+    splrName: normalizeText(record.splrName),
+    splrCode: normalizeText(record.splrCode),
+    splrBatch: normalizeText(record.splrBatch),
+    statusText: normalizeText(record['status$'] || record.statusText || statusMeta.text),
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo),
+    createByText: normalizeText(record['createBy$'] || record.createByText || '--'),
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime || '--'),
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText || '--'),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime || '--')
+  }
+}
+
+export function buildPurchaseItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizePurchaseItemRow(record))
+}
+
+export function buildPurchaseItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = PURCHASE_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: PURCHASE_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...PURCHASE_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/purchase-item/purchaseItemTable.columns.js b/rsf-design/src/views/orders/purchase-item/purchaseItemTable.columns.js
new file mode 100644
index 0000000..2c999a6
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase-item/purchaseItemTable.columns.js
@@ -0,0 +1,161 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import { getPurchaseItemStatusMeta } from './purchaseItemPage.helpers.js'
+
+function renderStatus(row) {
+  const statusMeta = getPurchaseItemStatusMeta(row.statusBool ?? row.status)
+  return h(
+    ElTag,
+    {
+      type: statusMeta.type,
+      effect: 'light'
+    },
+    () => statusMeta.text
+  )
+}
+
+export function createPurchaseItemTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'purchaseId',
+      label: '涓诲崟鏍囪瘑',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'platItemId',
+      label: 'ERP琛屽彿',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrName',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 200,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'nromQty',
+      label: '鏍囧噯鍖呰',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'asnQty',
+      label: 'ASN鍗曟嵁鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'printQty',
+      label: '鏉$爜鎵撳嵃鏁伴噺',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 92,
+      align: 'center',
+      formatter: renderStatus
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateByText',
+      label: '淇敼浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '淇敼鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'action',
+      label: '鎿嶄綔',
+      width: 96,
+      fixed: 'right',
+      align: 'center',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
+
+export { ArtButtonTable }
diff --git a/rsf-design/src/views/orders/purchase/index.vue b/rsf-design/src/views/orders/purchase/index.vue
new file mode 100644
index 0000000..35b855d
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase/index.vue
@@ -0,0 +1,483 @@
+<template>
+  <div class="purchase-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板PO鍗�</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="deletableSelectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <PurchaseDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :purchase-data="currentPurchaseData"
+        :type-options="typeOptions"
+        :wk-type-options="wkTypeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <PurchaseDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :items-loading="detailItemsLoading"
+        :detail="detailData"
+        :item-rows="detailItemRows"
+        :item-columns="purchaseItemColumns"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    fetchDeletePurchase,
+    fetchExportPurchaseReport,
+    fetchPurchaseDetail,
+    fetchPurchaseItemPage,
+    fetchPurchaseMany,
+    fetchPurchasePage,
+    fetchSavePurchase,
+    fetchUpdatePurchase
+  } from '@/api/purchase'
+  import PurchaseDialog from './modules/purchase-dialog.vue'
+  import PurchaseDetailDrawer from './modules/purchase-detail-drawer.vue'
+  import { createPurchaseTableColumns } from './purchaseTable.columns'
+  import {
+    PURCHASE_REPORT_STYLE,
+    PURCHASE_REPORT_TITLE,
+    buildPurchaseDialogModel,
+    buildPurchasePageQueryParams,
+    buildPurchasePrintRows,
+    buildPurchaseReportMeta,
+    buildPurchaseSavePayload,
+    buildPurchaseSearchParams,
+    createPurchaseItemColumns,
+    createPurchaseSearchState,
+    getPurchasePaginationKey,
+    normalizePurchaseDetailRecord,
+    normalizePurchaseItemRow,
+    normalizePurchaseListRow,
+    resolveDictOptions
+  } from './purchasePage.helpers'
+
+  defineOptions({ name: 'Purchase' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const reportTitle = PURCHASE_REPORT_TITLE
+  const searchForm = ref(createPurchaseSearchState())
+  const typeOptions = ref([])
+  const wkTypeOptions = ref([])
+  const exceStatusOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailItemsLoading = ref(false)
+  const detailData = ref({})
+  const detailItemRows = ref([])
+  const purchaseItemColumns = createPurchaseItemColumns()
+  let handleDeleteAction = null
+
+  const reportQueryParams = computed(() => buildPurchaseSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿/鏉ユ簮鍗曞彿/ERP鍗曞彿'
+      }
+    },
+    {
+      label: 'PO鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏O鍗曞彿'
+      }
+    },
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: typeOptions.value
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: wkTypeOptions.value
+      }
+    },
+    {
+      label: '鏉ユ簮鍗曞彿',
+      key: 'source',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鍙�'
+      }
+    },
+    {
+      label: '鏀惰揣閬撳彛',
+      key: 'channel',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ敹璐ч亾鍙�'
+      }
+    },
+    {
+      label: 'ERP鍗曞彿',
+      key: 'platCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏RP鍗曞彿'
+      }
+    },
+    {
+      label: '椤圭洰缂栫爜',
+      key: 'project',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩紪鐮�'
+      }
+    },
+    {
+      label: '鎵ц鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: exceStatusOptions.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  const canDeletePurchase = (row) => Number(row?.exceStatus) === 0
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    detailItemRows.value = []
+
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchPurchaseDetail(row.id),
+        {},
+        {
+          timeoutMessage: 'PO鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }
+      )
+      detailData.value = normalizePurchaseDetailRecord(detail)
+      await loadDetailItems(row.id)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailItemRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇PO鍗曡鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchPurchaseDetail(row.id),
+        {},
+        {
+          timeoutMessage: 'PO鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }
+      )
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇PO鍗曡鎯呭け璐�')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchPurchasePage,
+      apiParams: buildPurchasePageQueryParams(searchForm.value),
+      paginationKey: getPurchasePaginationKey(),
+      columnsFactory: () =>
+        createPurchaseTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null,
+          canEdit: hasAuth('update'),
+          canDelete: hasAuth('delete'),
+          canDeleteRow: canDeletePurchase
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizePurchaseListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentPurchaseData,
+    selectedRows,
+    handleSelectionChange: handleCrudSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete: handleCrudBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildPurchaseDialogModel(),
+    buildEditModel: (record) => buildPurchaseDialogModel(record),
+    buildSavePayload: (formData) => buildPurchaseSavePayload(formData),
+    saveRequest: fetchSavePurchase,
+    updateRequest: fetchUpdatePurchase,
+    deleteRequest: fetchDeletePurchase,
+    entityName: 'PO鍗�',
+    resolveRecordLabel: (record) => record?.code || record?.source || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  const deletableSelectedRows = computed(() =>
+    selectedRows.value.filter((row) => canDeletePurchase(row))
+  )
+
+  const handleSelectionChange = (rows) => {
+    handleCrudSelectionChange(rows)
+  }
+
+  const handleBatchDelete = async () => {
+    const invalidRows = selectedRows.value.filter((row) => !canDeletePurchase(row))
+    if (invalidRows.length > 0) {
+      ElMessage.warning('浠呮墽琛岀姸鎬佷负鏈紑濮嬬殑PO鍗曞厑璁稿垹闄わ紝璇疯皟鏁村嬀閫夊悗閲嶈瘯')
+      return
+    }
+    await handleCrudBatchDelete()
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({ current: 1, pageSize: 200, dictTypeCode: 'sys_order_type', status: 1 }),
+      { records: [] },
+      { timeoutMessage: '鍗曟嵁绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    typeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadWkTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_business_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '涓氬姟绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    wkTypeOptions.value = resolveDictOptions(defaultResponseAdapter(response).records, { group: 1 })
+  }
+
+  async function loadExceStatusOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_po_exce_status',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '鎵ц鐘舵�侀�夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    exceStatusOptions.value = resolveDictOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadDetailItems(purchaseId) {
+    detailItemsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchPurchaseItemPage({ purchaseId, current: 1, pageSize: 200 }),
+        { records: [] },
+        { timeoutMessage: '閲囪喘鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      detailItemRows.value = defaultResponseAdapter(response).records.map((item) =>
+        normalizePurchaseItemRow(item)
+      )
+    } catch (error) {
+      detailItemRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇閲囪喘鏄庣粏澶辫触')
+    } finally {
+      detailItemsLoading.value = false
+    }
+  }
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response =
+      Array.isArray(payload?.ids) && payload.ids.length > 0
+        ? await fetchPurchaseMany(payload.ids)
+        : await fetchPurchasePage({
+            ...reportQueryParams.value,
+            current: 1,
+            pageSize:
+              Number(pagination.total) > 0
+                ? Number(pagination.total)
+                : Number(payload?.pageSize) || 20
+          })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'purchase.xlsx',
+    requestExport: (payload) =>
+      fetchExportPurchaseReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildPurchasePrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildPurchaseReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || PURCHASE_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildPurchaseSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createPurchaseSearchState())
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    await Promise.allSettled([loadTypeOptions(), loadWkTypeOptions(), loadExceStatusOptions()])
+  })
+</script>
diff --git a/rsf-design/src/views/orders/purchase/modules/purchase-detail-drawer.vue b/rsf-design/src/views/orders/purchase/modules/purchase-detail-drawer.vue
new file mode 100644
index 0000000..4018ab1
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase/modules/purchase-detail-drawer.vue
@@ -0,0 +1,91 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="PO鍗曡鎯�"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="PO鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉ユ簮鍗曞彿">{{ detail.source || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="闇�姹傛暟閲�">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸叉敹鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏀惰揣涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏀惰揣閬撳彛">{{ detail.channel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ERP鍗曞彿">{{ detail.platCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="椤圭洰缂栫爜">{{ detail.project || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="棰勮鍒拌揪鏃堕棿">{{
+            detail.preArrText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁″垝鏀惰揣鏃堕棿">{{
+            detail.startTimeText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁″垝缁撴潫鏃堕棿">{{
+            detail.endTimeText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">{{
+            detail.exceStatusText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+            <ElTag effect="plain">鍏� {{ itemRows.length }} 鏉�</ElTag>
+          </div>
+          <ArtTable :data="itemRows" :columns="itemColumns" :loading="itemsLoading" />
+        </div>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import ArtTable from '@/components/core/tables/art-table/index.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    itemsLoading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    itemRows: { type: Array, default: () => [] },
+    itemColumns: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/purchase/modules/purchase-dialog.vue b/rsf-design/src/views/orders/purchase/modules/purchase-dialog.vue
new file mode 100644
index 0000000..cdc833a
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase/modules/purchase-dialog.vue
@@ -0,0 +1,261 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildPurchaseDialogModel,
+    createPurchaseFormState,
+    getPurchaseStatusOptions
+  } from '../purchasePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    purchaseData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] },
+    wkTypeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createPurchaseFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫PO鍗�' : '鏂板PO鍗�'))
+
+  const rules = computed(() => ({
+    type: [{ required: true, message: '璇烽�夋嫨鍗曟嵁绫诲瀷', trigger: 'change' }],
+    wkType: [{ required: true, message: '璇烽�夋嫨涓氬姟绫诲瀷', trigger: 'change' }],
+    source: [{ required: true, message: '璇疯緭鍏ユ潵婧愬崟鍙�', trigger: 'blur' }],
+    anfme: [{ required: true, message: '璇疯緭鍏ラ渶姹傛暟閲�', trigger: 'blur' }]
+  }))
+
+  const dateProps = {
+    type: 'datetime',
+    valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    format: 'YYYY-MM-DD HH:mm:ss',
+    clearable: true
+  }
+
+  const formItems = computed(() => [
+    {
+      label: '鍗曟嵁绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鍗曟嵁绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.typeOptions
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.wkTypeOptions
+      }
+    },
+    {
+      label: '鏉ユ簮鍗曞彿',
+      key: 'source',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鍙�',
+        clearable: true
+      }
+    },
+    {
+      label: '闇�姹傛暟閲�',
+      key: 'anfme',
+      type: 'input-number',
+      props: {
+        placeholder: '璇疯緭鍏ラ渶姹傛暟閲�',
+        min: 0,
+        precision: 2,
+        controlsPosition: 'right'
+      }
+    },
+    {
+      label: '棰勮鍒拌揪鏃堕棿',
+      key: 'preArr',
+      type: 'datetime',
+      props: {
+        ...dateProps,
+        placeholder: '璇烽�夋嫨棰勮鍒拌揪鏃堕棿'
+      }
+    },
+    {
+      label: '宸叉敹鏁伴噺',
+      key: 'qty',
+      type: 'input-number',
+      props: {
+        placeholder: '璇疯緭鍏ュ凡鏀舵暟閲�',
+        min: 0,
+        precision: 2,
+        controlsPosition: 'right'
+      }
+    },
+    {
+      label: '鏀惰揣涓暟閲�',
+      key: 'workQty',
+      type: 'input-number',
+      props: {
+        placeholder: '璇疯緭鍏ユ敹璐т腑鏁伴噺',
+        min: 0,
+        precision: 2,
+        controlsPosition: 'right'
+      }
+    },
+    {
+      label: '鏀惰揣閬撳彛',
+      key: 'channel',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ敹璐ч亾鍙�',
+        clearable: true
+      }
+    },
+    {
+      label: 'ERP鍗曞彿',
+      key: 'platCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏RP鍗曞彿',
+        clearable: true
+      }
+    },
+    {
+      label: '璁″垝鏀惰揣鏃堕棿',
+      key: 'startTime',
+      type: 'datetime',
+      props: {
+        ...dateProps,
+        placeholder: '璇烽�夋嫨璁″垝鏀惰揣鏃堕棿'
+      }
+    },
+    {
+      label: '璁″垝缁撴潫鏃堕棿',
+      key: 'endTime',
+      type: 'datetime',
+      props: {
+        ...dateProps,
+        placeholder: '璇烽�夋嫨璁″垝缁撴潫鏃堕棿'
+      }
+    },
+    {
+      label: '椤圭洰缂栫爜',
+      key: 'project',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ」鐩紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: getPurchaseStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildPurchaseDialogModel(props.purchaseData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createPurchaseFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.purchaseData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/orders/purchase/purchasePage.helpers.js b/rsf-design/src/views/orders/purchase/purchasePage.helpers.js
new file mode 100644
index 0000000..fcce766
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase/purchasePage.helpers.js
@@ -0,0 +1,314 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const PURCHASE_REPORT_TITLE = 'PO鍗曟姤琛�'
+export const PURCHASE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeDateTimeText(value) {
+  return normalizeText(value)
+}
+
+export function createPurchaseSearchState() {
+  return {
+    condition: '',
+    code: '',
+    type: '',
+    wkType: '',
+    source: '',
+    channel: '',
+    platCode: '',
+    project: '',
+    exceStatus: '',
+    status: ''
+  }
+}
+
+export function createPurchaseFormState() {
+  return {
+    id: void 0,
+    code: '',
+    type: '',
+    wkType: '',
+    source: '',
+    preArr: '',
+    anfme: '',
+    qty: '',
+    workQty: '',
+    channel: '',
+    platCode: '',
+    startTime: '',
+    endTime: '',
+    project: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getPurchasePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getPurchaseStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getPurchaseStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildPurchaseSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    type: normalizeText(params.type),
+    wkType: normalizeText(params.wkType),
+    source: normalizeText(params.source),
+    channel: normalizeText(params.channel),
+    platCode: normalizeText(params.platCode),
+    project: normalizeText(params.project),
+    exceStatus:
+      params.exceStatus !== undefined && params.exceStatus !== null && params.exceStatus !== ''
+        ? normalizeNumber(params.exceStatus)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
+  )
+}
+
+export function buildPurchasePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildPurchaseSearchParams(params)
+  }
+}
+
+export function buildPurchaseSavePayload(formData = {}) {
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: Number(formData.id) }
+      : {}),
+    code: normalizeText(formData.code) || '',
+    type: normalizeText(formData.type) || '',
+    wkType: normalizeText(formData.wkType) || '',
+    source: normalizeText(formData.source) || '',
+    ...(normalizeText(formData.preArr) ? { preArr: normalizeText(formData.preArr) } : {}),
+    ...(formData.anfme !== '' && formData.anfme !== null && formData.anfme !== undefined
+      ? { anfme: Number(formData.anfme) }
+      : {}),
+    ...(formData.qty !== '' && formData.qty !== null && formData.qty !== undefined
+      ? { qty: Number(formData.qty) }
+      : {}),
+    ...(formData.workQty !== '' && formData.workQty !== null && formData.workQty !== undefined
+      ? { workQty: Number(formData.workQty) }
+      : {}),
+    channel: normalizeText(formData.channel) || '',
+    platCode: normalizeText(formData.platCode) || '',
+    ...(normalizeText(formData.startTime) ? { startTime: normalizeText(formData.startTime) } : {}),
+    ...(normalizeText(formData.endTime) ? { endTime: normalizeText(formData.endTime) } : {}),
+    project: normalizeText(formData.project) || '',
+    status:
+      formData.status !== undefined && formData.status !== null && formData.status !== ''
+        ? Number(formData.status)
+        : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function buildPurchaseDialogModel(record = {}) {
+  return {
+    ...createPurchaseFormState(),
+    ...(record.id !== undefined && record.id !== null && record.id !== ''
+      ? { id: Number(record.id) }
+      : {}),
+    code: normalizeText(record.code || ''),
+    type: normalizeText(record.type || ''),
+    wkType: normalizeText(record.wkType || ''),
+    source: normalizeText(record.source || ''),
+    preArr: normalizeDateTimeText(record.preArr$ || record.preArr || ''),
+    anfme: record.anfme ?? '',
+    qty: record.qty ?? '',
+    workQty: record.workQty ?? '',
+    channel: normalizeText(record.channel || ''),
+    platCode: normalizeText(record.platCode || ''),
+    startTime: normalizeDateTimeText(record.startTime$ || record.startTime || ''),
+    endTime: normalizeDateTimeText(record.endTime$ || record.endTime || ''),
+    project: normalizeText(record.project || ''),
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function normalizePurchaseDetailRecord(record = {}) {
+  const statusMeta = getPurchaseStatusMeta(record.statusBool ?? record.status)
+  const exceStatusText = normalizeText(record.exceStatus$ || record.exceStatusText || '')
+  const typeText = normalizeText(record.type$ || record.typeText || record.type || '')
+  const wkTypeText = normalizeText(record.wkType$ || record.wkTypeText || record.wkType || '')
+
+  return {
+    ...record,
+    code: normalizeText(record.code) || '--',
+    typeText: typeText || '--',
+    wkTypeText: wkTypeText || '--',
+    source: normalizeText(record.source) || '--',
+    preArrText: normalizeDateTimeText(record.preArr$ || record.preArr) || '--',
+    anfme: record.anfme ?? '--',
+    qty: record.qty ?? '--',
+    workQty: record.workQty ?? '--',
+    channel: normalizeText(record.channel) || '--',
+    platCode: normalizeText(record.platCode) || '--',
+    startTimeText: normalizeDateTimeText(record.startTime$ || record.startTime) || '--',
+    endTimeText: normalizeDateTimeText(record.endTime$ || record.endTime) || '--',
+    project: normalizeText(record.project) || '--',
+    exceStatusText: exceStatusText || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo) || '--',
+    createByText: normalizeText(record.createBy$ || record.createByText || '') || '--',
+    createTimeText: normalizeDateTimeText(record.createTime$ || record.createTime) || '--',
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || '') || '--',
+    updateTimeText: normalizeDateTimeText(record.updateTime$ || record.updateTime) || '--'
+  }
+}
+
+export function normalizePurchaseListRow(record = {}) {
+  return normalizePurchaseDetailRecord(record)
+}
+
+export function normalizePurchaseItemRow(record = {}) {
+  const statusMeta = getPurchaseStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    purchaseId: record.purchaseId ?? '--',
+    platItemId: normalizeText(record.platItemId) || '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    matnrName: normalizeText(record.matnrName) || '--',
+    unit: normalizeText(record.unit) || '--',
+    anfme: record.anfme ?? '--',
+    qty: record.qty ?? '--',
+    nromQty: record.nromQty ?? '--',
+    asnQty: record.asnQty ?? '--',
+    printQty: record.printQty ?? '--',
+    splrName: normalizeText(record.splrName) || '--',
+    splrCode: normalizeText(record.splrCode) || '--',
+    splrBatch: normalizeText(record.splrBatch) || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildPurchasePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizePurchaseListRow(record))
+}
+
+export function buildPurchaseReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = PURCHASE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: PURCHASE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...PURCHASE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function resolveDictOptions(records = [], options = {}) {
+  const { group } = options
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records
+    .filter((item) => {
+      if (!item || typeof item !== 'object') {
+        return false
+      }
+      if (group === undefined) {
+        return true
+      }
+      return normalizeText(item.group) === normalizeText(group)
+    })
+    .map((item) => {
+      const value = item.value ?? item.id ?? item.dictValue
+      if (value === undefined || value === null || value === '') {
+        return null
+      }
+      return {
+        value: normalizeText(value),
+        label: normalizeText(item.label || item.name || item.dictLabel || value)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function createPurchaseItemColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 70, align: 'center' },
+    { prop: 'platItemId', label: 'ERP琛屽彿', minWidth: 110, showOverflowTooltip: true },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'matnrName', label: '鐗╂枡鍚嶇О', minWidth: 180, showOverflowTooltip: true },
+    { prop: 'unit', label: '鍗曚綅', width: 90, align: 'center' },
+    { prop: 'anfme', label: '鏁伴噺', width: 100, align: 'right' },
+    { prop: 'qty', label: '宸叉敹鏁伴噺', width: 100, align: 'right' },
+    { prop: 'nromQty', label: '鏍囧噯鍖呰', width: 100, align: 'right' },
+    { prop: 'asnQty', label: 'ASN鏁伴噺', width: 100, align: 'right' },
+    { prop: 'printQty', label: '鎵撳嵃鏁伴噺', width: 100, align: 'right' },
+    { prop: 'splrName', label: '渚涘簲鍟嗗悕绉�', minWidth: 150, showOverflowTooltip: true },
+    { prop: 'splrCode', label: '渚涘簲鍟嗙紪鐮�', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'splrBatch', label: '渚涘簲鍟嗘壒娆�', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'statusText', label: '鐘舵��', width: 90, align: 'center' },
+    { prop: 'memo', label: '澶囨敞', minWidth: 160, showOverflowTooltip: true }
+  ]
+}
diff --git a/rsf-design/src/views/orders/purchase/purchaseTable.columns.js b/rsf-design/src/views/orders/purchase/purchaseTable.columns.js
new file mode 100644
index 0000000..03a3bbf
--- /dev/null
+++ b/rsf-design/src/views/orders/purchase/purchaseTable.columns.js
@@ -0,0 +1,179 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getPurchaseStatusMeta } from './purchasePage.helpers'
+
+export function createPurchaseTableColumns({
+  handleView,
+  handleEdit,
+  handleDelete,
+  canEdit = true,
+  canDelete = true,
+  canDeleteRow = () => true
+} = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: 'PO鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'typeText',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeText || '--'
+    },
+    {
+      prop: 'wkTypeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeText || '--'
+    },
+    {
+      prop: 'source',
+      label: '鏉ユ簮鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.source || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '闇�姹傛暟閲�',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸叉敹鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鏀惰揣涓暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'channel',
+      label: '鏀惰揣閬撳彛',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.channel || '--'
+    },
+    {
+      prop: 'platCode',
+      label: 'ERP鍗曞彿',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platCode || '--'
+    },
+    {
+      prop: 'preArrText',
+      label: '棰勮鍒拌揪鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.preArrText || '--'
+    },
+    {
+      prop: 'startTimeText',
+      label: '璁″垝鏀惰揣鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.startTimeText || '--'
+    },
+    {
+      prop: 'endTimeText',
+      label: '璁″垝缁撴潫鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.endTimeText || '--'
+    },
+    {
+      prop: 'project',
+      label: '椤圭洰缂栫爜',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.project || '--'
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.exceStatusText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getPurchaseStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 160,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) => {
+        const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+        if (canEdit && handleEdit) {
+          operations.push({ key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' })
+        }
+
+        if (canDelete && handleDelete && canDeleteRow(row)) {
+          operations.push({
+            key: 'delete',
+            label: '鍒犻櫎',
+            icon: 'ri:delete-bin-5-line',
+            color: 'var(--art-error)'
+          })
+        }
+
+        return h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(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
new file mode 100644
index 0000000..47bff6c
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer-item/index.vue
@@ -0,0 +1,429 @@
+<template>
+  <div class="transfer-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TransferItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportTransferItemReport,
+    fetchTransferItemDetail,
+    fetchTransferItemMany,
+    fetchTransferItemPage
+  } from '@/api/transfer-item'
+  import TransferItemDetailDrawer from './modules/transfer-item-detail-drawer.vue'
+  import { createTransferItemTableColumns } from './transferItemTable.columns.js'
+  import {
+    TRANSFER_ITEM_REPORT_STYLE,
+    TRANSFER_ITEM_REPORT_TITLE,
+    buildTransferItemPageQueryParams,
+    buildTransferItemPrintRows,
+    buildTransferItemReportMeta,
+    buildTransferItemSearchParams,
+    createTransferItemSearchState,
+    getTransferItemPaginationKey,
+    getTransferItemStatusOptions,
+    normalizeTransferItemRow
+  } from './transferItemPage.helpers.js'
+
+  defineOptions({ name: 'TransferItem' })
+
+  const userStore = useUserStore()
+  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 searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ皟鎷ㄥ崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О/澶囨敞'
+      }
+    },
+    {
+      label: '寮�濮嬫椂闂�',
+      key: 'timeStart',
+      type: 'datetime',
+      props: {
+        clearable: true,
+        format: 'YYYY-MM-DD HH:mm:ss',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�'
+      }
+    },
+    {
+      label: '缁撴潫鏃堕棿',
+      key: 'timeEnd',
+      type: 'datetime',
+      props: {
+        clearable: true,
+        format: 'YYYY-MM-DD HH:mm:ss',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    },
+    {
+      label: '璋冩嫧鍗旾D',
+      key: 'transferId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ皟鎷ㄥ崟ID'
+      }
+    },
+    {
+      label: '璋冩嫧鍗曞彿',
+      key: 'transferCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ皟鎷ㄥ崟鍙�'
+      }
+    },
+    {
+      label: '鐗╂枡ID',
+      key: 'matnrId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ墿鏂橧D'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鍗曚綅',
+      key: 'unit',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ崟浣�'
+      }
+    },
+    {
+      label: '鏁伴噺',
+      key: 'anfme',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ暟閲�'
+      }
+    },
+    {
+      label: '宸插畬鎴愭暟閲�',
+      key: 'qty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ュ凡瀹屾垚鏁伴噺'
+      }
+    },
+    {
+      label: '鎵ц鏁伴噺',
+      key: 'workQty',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ墽琛屾暟閲�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟咺D',
+      key: 'splrId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢ID'
+      }
+    },
+    {
+      label: '瑙勬牸',
+      key: 'spec',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鏍�'
+      }
+    },
+    {
+      label: '鍨嬪彿',
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瀷鍙�'
+      }
+    },
+    {
+      label: '瀛楁绱㈠紩',
+      key: 'fieldsIndex',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈电储寮�'
+      }
+    },
+    {
+      label: '骞冲彴琛屽彿',
+      key: 'platItemId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙拌鍙�'
+      }
+    },
+    {
+      label: '瀹㈡埛璁㈠崟鍙�',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴疯鍗曞彿'
+      }
+    },
+    {
+      label: '宸ュ崟鍙�',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ伐鍗曞彿'
+      }
+    },
+    {
+      label: '椤圭洰鍙�',
+      key: 'projectCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩彿'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getTransferItemStatusOptions()
+      }
+    }
+  ])
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  const reportQueryParams = computed(() => buildTransferItemSearchParams(searchForm.value))
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTransferItemPage,
+      apiParams: buildTransferItemPageQueryParams(searchForm.value),
+      paginationKey: getTransferItemPaginationKey(),
+      columnsFactory: () =>
+        createTransferItemTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeTransferItemRow(item)) : []
+    }
+  })
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchTransferItemDetail(row.id), {}, {
+        timeoutMessage: '璋冩嫧鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeTransferItemRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇璋冩嫧鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildTransferItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTransferItemSearchState())
+    resetSearchParams()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchTransferItemMany(payload.ids)).records
+    }
+
+    return defaultResponseAdapter(
+      await fetchTransferItemPage({
+        ...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: 'transfer-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportTransferItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTransferItemPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...TRANSFER_ITEM_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildTransferItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || TRANSFER_ITEM_REPORT_STYLE.orientation
+    })
+  )
+</script>
+
diff --git a/rsf-design/src/views/orders/transfer-item/modules/transfer-item-detail-drawer.vue b/rsf-design/src/views/orders/transfer-item/modules/transfer-item-detail-drawer.vue
new file mode 100644
index 0000000..4a824f6
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer-item/modules/transfer-item-detail-drawer.vue
@@ -0,0 +1,78 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璋冩嫧鏄庣粏璇︽儏"
+    size="72%"
+    destroy-on-close
+    append-to-body
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <ElSkeleton :loading="loading" animated :rows="10">
+        <div class="flex min-h-full flex-col gap-4 pr-2">
+          <ElDescriptions title="鍩虹淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="璋冩嫧鍗旾D">{{ displayData.transferId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="璋冩嫧鍗曞彿">{{ displayData.transferCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">
+              <ElTag :type="displayData.statusType || 'info'" effect="light">
+                {{ displayData.statusText || '--' }}
+              </ElTag>
+            </ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ displayData.splrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟咺D">{{ displayData.splrId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ displayData.splrName || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="3">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="鐗╂枡淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="鐗╂枡ID">{{ displayData.matnrId ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ displayData.matnrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ displayData.maktx || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瑙勬牸">{{ displayData.spec || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍨嬪彿">{{ displayData.model || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵规">{{ displayData.batch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍗曚綅">{{ displayData.unit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏁伴噺">{{ displayData.anfme ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵ц鏁伴噺">{{ displayData.workQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸插畬鎴愭暟閲�">{{ displayData.qty ?? '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="骞冲彴淇℃伅" :column="3" border>
+            <ElDescriptionsItem label="瀛楁绱㈠紩">{{ displayData.fieldsIndex || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="骞冲彴琛屽彿">{{ displayData.platItemId || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ displayData.platOrderCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="宸ュ崟鍙�">{{ displayData.platWorkCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="椤圭洰鍙�">{{ displayData.projectCode || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+
+          <ElDescriptions title="瀹¤淇℃伅" :column="2" border>
+            <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </div>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import { normalizeTransferItemRow } from '../transferItemPage.helpers.js'
+
+  defineOptions({ name: 'TransferItemDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeTransferItemRow(props.detail))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/transfer-item/transferItemPage.helpers.js b/rsf-design/src/views/orders/transfer-item/transferItemPage.helpers.js
new file mode 100644
index 0000000..bce45e0
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer-item/transferItemPage.helpers.js
@@ -0,0 +1,205 @@
+export const TRANSFER_ITEM_REPORT_TITLE = '璋冩嫧鏄庣粏鎶ヨ〃'
+export const TRANSFER_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true,
+  showBorder: true
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+function normalizeText(value, fallback = '--') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  const text = String(value).trim()
+  return text || fallback
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function pushText(target, key, value) {
+  const text = normalizeText(value, '')
+  if (text) {
+    target[key] = text
+  }
+}
+
+function pushNumber(target, key, value) {
+  const numericValue = normalizeNumber(value, void 0)
+  if (numericValue !== void 0) {
+    target[key] = numericValue
+  }
+}
+
+function getStatusMeta(status, statusText) {
+  const numericStatus = Number(status)
+  return STATUS_META[numericStatus] || {
+    text: normalizeText(statusText || status || '--'),
+    type: 'info',
+    bool: false
+  }
+}
+
+export function createTransferItemSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    transferId: '',
+    transferCode: '',
+    matnrId: '',
+    maktx: '',
+    matnrCode: '',
+    unit: '',
+    anfme: '',
+    qty: '',
+    workQty: '',
+    batch: '',
+    splrId: '',
+    spec: '',
+    model: '',
+    fieldsIndex: '',
+    platItemId: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getTransferItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getTransferItemStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getTransferItemStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildTransferItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'timeStart',
+    'timeEnd',
+    'transferCode',
+    'maktx',
+    'matnrCode',
+    'unit',
+    'batch',
+    'spec',
+    'model',
+    'fieldsIndex',
+    'platItemId',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode',
+    'memo'
+  ].forEach((key) => pushText(result, key, params[key]))
+
+  ;['transferId', 'matnrId', 'anfme', 'qty', 'workQty', 'splrId', 'status'].forEach((key) =>
+    pushNumber(result, key, params[key])
+  )
+
+  return result
+}
+
+export function buildTransferItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTransferItemSearchParams(params)
+  }
+}
+
+export function normalizeTransferItemRow(record = {}) {
+  const statusMeta = getStatusMeta(record.statusBool ?? record.status, record['status$'] || record.statusText)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    transferId: normalizeNumber(record.transferId, '--'),
+    transferCode: normalizeText(record.transferCode),
+    matnrId: normalizeNumber(record.matnrId, '--'),
+    maktx: normalizeText(record.maktx),
+    matnrCode: normalizeText(record.matnrCode),
+    unit: normalizeText(record.unit),
+    anfme: normalizeNumber(record.anfme, '--'),
+    qty: normalizeNumber(record.qty, '--'),
+    workQty: normalizeNumber(record.workQty, '--'),
+    batch: normalizeText(record.batch),
+    splrId: normalizeNumber(record.splrId, '--'),
+    splrCode: normalizeText(record.splrCode),
+    splrName: normalizeText(record.splrName),
+    spec: normalizeText(record.spec),
+    model: normalizeText(record.model),
+    fieldsIndex: normalizeText(record.fieldsIndex),
+    platItemId: normalizeText(record.platItemId),
+    platOrderCode: normalizeText(record.platOrderCode),
+    platWorkCode: normalizeText(record.platWorkCode),
+    projectCode: normalizeText(record.projectCode),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record['createBy$'] || record.createByText),
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime, '--'),
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime, '--'),
+    memo: normalizeText(record.memo)
+  }
+}
+
+export function buildTransferItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeTransferItemRow(record))
+}
+
+export function buildTransferItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = TRANSFER_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: TRANSFER_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...TRANSFER_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
diff --git a/rsf-design/src/views/orders/transfer-item/transferItemTable.columns.js b/rsf-design/src/views/orders/transfer-item/transferItemTable.columns.js
new file mode 100644
index 0000000..038fdd8
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer-item/transferItemTable.columns.js
@@ -0,0 +1,164 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTransferItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'transferId',
+      label: '璋冩嫧鍗旾D',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.transferId ?? '--'
+    },
+    {
+      prop: 'transferCode',
+      label: '璋冩嫧鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.transferCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'spec',
+      label: '瑙勬牸',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.spec || '--'
+    },
+    {
+      prop: 'model',
+      label: '鍨嬪彿',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.model || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插畬鎴愭暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'splrCode',
+      label: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrCode || '--'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟嗗悕绉�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrName || '--'
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '瀛楁绱㈠紩',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.fieldsIndex || '--'
+    },
+    {
+      prop: 'platItemId',
+      label: '骞冲彴琛屽彿',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platItemId || '--'
+    },
+    {
+      prop: 'platOrderCode',
+      label: '瀹㈡埛璁㈠崟鍙�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platOrderCode || '--'
+    },
+    {
+      prop: 'platWorkCode',
+      label: '宸ュ崟鍙�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.platWorkCode || '--'
+    },
+    {
+      prop: 'projectCode',
+      label: '椤圭洰鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.projectCode || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 96,
+      fixed: 'right',
+      align: 'center',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
+
diff --git a/rsf-design/src/views/orders/transfer/index.vue b/rsf-design/src/views/orders/transfer/index.vue
new file mode 100644
index 0000000..f85b2d4
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer/index.vue
@@ -0,0 +1,474 @@
+<template>
+  <div class="transfer-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-if="canCreate" type="primary" @click="showDialog('add')" v-ripple>鏂板璋冩嫧鍗�</ElButton>
+            <ElButton
+              v-if="canDelete"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <TransferDialog
+        v-model:visible="dialogVisible"
+        :dialog-type="dialogType"
+        :transfer-data="currentTransferData"
+        :type-options="typeOptions"
+        :area-options="areaOptions"
+        :submit-loading="dialogSubmitting"
+        @submit="handleDialogSubmit"
+      />
+
+      <TransferDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :orders-loading="detailOrdersLoading"
+        :detail="detailData"
+        :order-rows="detailOrderRows"
+        :order-pagination="detailOrderPagination"
+        @size-change="handleDetailSizeChange"
+        @current-change="handleDetailCurrentChange"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  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 { fetchDictDataPage } from '@/api/system-manage'
+  import { fetchWarehouseAreasList } from '@/api/warehouse-areas'
+  import {
+    fetchDeleteTransfer,
+    fetchExportTransferReport,
+    fetchTransferDetail,
+    fetchTransferMany,
+    fetchTransferOrdersPage,
+    fetchTransferPage,
+    fetchTransferPubOutStock,
+    fetchSaveTransfer,
+    fetchUpdateTransfer
+  } from '@/api/transfer'
+  import TransferDialog from './modules/transfer-dialog.vue'
+  import TransferDetailDrawer from './modules/transfer-detail-drawer.vue'
+  import { createTransferTableColumns } from './transferTable.columns.js'
+  import {
+    TRANSFER_REPORT_STYLE,
+    TRANSFER_REPORT_TITLE,
+    buildTransferDetailOrderQueryParams,
+    buildTransferDialogModel,
+    buildTransferPageQueryParams,
+    buildTransferPrintRows,
+    buildTransferReportMeta,
+    buildTransferSavePayload,
+    buildTransferSearchParams,
+    createTransferFormState,
+    createTransferSearchState,
+    getTransferPaginationKey,
+    getTransferSourceOptions,
+    getTransferStatusOptions,
+    getTransferExceStatusOptions,
+    normalizeTransferDetailRecord,
+    normalizeTransferOrderRow,
+    normalizeTransferRow,
+    resolveTransferAreaOptions,
+    resolveTransferTypeOptions
+  } from './transferPage.helpers.js'
+
+  defineOptions({ name: 'Transfer' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const reportTitle = TRANSFER_REPORT_TITLE
+  const searchForm = ref(createTransferSearchState())
+  const typeOptions = ref([])
+  const areaOptions = ref([])
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailOrdersLoading = ref(false)
+  const detailData = ref({})
+  const detailOrderRows = ref([])
+  const activeTransferId = ref(null)
+  const activeTransferCode = ref('')
+  const dialogSubmitting = ref(false)
+  const detailOrderPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const canCreate = computed(() => hasAuth('add'))
+  const canDelete = computed(() => hasAuth('delete'))
+  const canUpdate = computed(() => hasAuth('update'))
+
+  const reportQueryParams = computed(() => buildTransferSearchParams(searchForm.value))
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ崟鍙�/澶囨敞/浠撳簱/搴撳尯' } },
+    { label: '璋冩嫧鍗曞彿', key: 'code', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ皟鎷ㄥ崟鍙�' } },
+    {
+      label: '璋冩嫧绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: { clearable: true, filterable: true, options: typeOptions.value }
+    },
+    {
+      label: '鏉ユ簮',
+      key: 'source',
+      type: 'select',
+      props: { clearable: true, options: getTransferSourceOptions() }
+    },
+    {
+      label: '鎵ц鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: { clearable: true, options: getTransferExceStatusOptions() }
+    },
+    { label: '婧愪粨搴�', key: 'orgWareName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ簮浠撳簱' } },
+    { label: '鐩爣浠撳簱', key: 'tarWareName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洰鏍囦粨搴�' } },
+    { label: '婧愬簱鍖�', key: 'orgAreaName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ簮搴撳尯' } },
+    { label: '鐩爣搴撳尯', key: 'tarAreaName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ洰鏍囧簱鍖�' } },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: { clearable: true, options: getTransferStatusOptions() }
+    },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } }
+  ])
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+    handleCrudSelectionChange(rows)
+  }
+
+  async function loadTransferDetail(transferId) {
+    detailLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchTransferDetail(transferId),
+        {},
+        { timeoutMessage: '璋冩嫧鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      detailData.value = normalizeTransferDetailRecord(response)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function loadTransferOrders(code) {
+    detailOrdersLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchTransferOrdersPage(
+          buildTransferDetailOrderQueryParams({
+            code,
+            current: detailOrderPagination.current,
+            pageSize: detailOrderPagination.size
+          })
+        ),
+        { records: [], total: 0, current: detailOrderPagination.current, size: detailOrderPagination.size },
+        { timeoutMessage: '鍏宠仈鍗曟嵁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      const normalized = defaultResponseAdapter(response)
+      detailOrderRows.value = normalized.records.map((item) => normalizeTransferOrderRow(item))
+      detailOrderPagination.total = Number(normalized.total || 0)
+      detailOrderPagination.current = Number(normalized.current || detailOrderPagination.current || 1)
+      detailOrderPagination.size = Number(normalized.size || detailOrderPagination.size || 20)
+    } catch (error) {
+      detailOrderRows.value = []
+      detailOrderPagination.total = 0
+      ElMessage.error(error?.message || '鑾峰彇鍏宠仈鍗曟嵁澶辫触')
+    } finally {
+      detailOrdersLoading.value = false
+    }
+  }
+
+  async function openDetail(row) {
+    activeTransferId.value = row.id
+    activeTransferCode.value = row.code || ''
+    detailOrderPagination.current = 1
+    detailOrderRows.value = []
+    detailData.value = {}
+    detailDrawerVisible.value = true
+    try {
+      await loadTransferDetail(row.id)
+      activeTransferCode.value = detailData.value.code || row.code || activeTransferCode.value
+      await loadTransferOrders(activeTransferCode.value)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailOrderRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇璋冩嫧鍗曡鎯呭け璐�')
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchTransferDetail(row.id),
+        {},
+        { timeoutMessage: '璋冩嫧鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      showDialog('edit', detail)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇璋冩嫧鍗曡鎯呭け璐�')
+    }
+  }
+
+  async function handlePublish(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佷笅鍙戣皟鎷ㄥ崟銆�${row.code || row.id}銆嶅悧锛焋, '涓嬪彂纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      const response = await fetchTransferPubOutStock({ id: row.id })
+      if (response?.code !== 200 && response?.success !== true) {
+        throw new Error(response?.message || '涓嬪彂鎵ц澶辫触')
+      }
+      ElMessage.success(response?.message || '涓嬪彂鎵ц鎴愬姛')
+      await refreshData()
+      if (detailDrawerVisible.value && activeTransferId.value === row.id) {
+        await loadTransferDetail(row.id)
+        await loadTransferOrders(row.code || activeTransferCode.value)
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '涓嬪彂鎵ц澶辫触')
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    if (action?.key === 'view') {
+      await openDetail(row)
+      return
+    }
+    if (action?.key === 'edit') {
+      if (!canUpdate.value) return
+      await openEditDialog(row)
+      return
+    }
+    if (action?.key === 'delete') {
+      if (!canDelete.value) return
+      await handleDeleteAction?.(row)
+      return
+    }
+    if (action?.key === 'publish') {
+      if (!canUpdate.value) return
+      await handlePublish(row)
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchTransferPage,
+      apiParams: buildTransferPageQueryParams(searchForm.value),
+      paginationKey: getTransferPaginationKey(),
+      columnsFactory: () => createTransferTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeTransferRow(item)) : [])
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentTransferData,
+    selectedRows: crudSelectedRows,
+    handleSelectionChange: handleCrudSelectionChange,
+    showDialog,
+    handleDialogSubmit: handleCrudDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => createTransferFormState(),
+    buildEditModel: (record) => buildTransferDialogModel(record),
+    buildSavePayload: (formData) => buildTransferSavePayload(formData, areaOptions.value),
+    saveRequest: fetchSaveTransfer,
+    updateRequest: fetchUpdateTransfer,
+    deleteRequest: fetchDeleteTransfer,
+    entityName: '璋冩嫧鍗�',
+    resolveRecordLabel: (record) => record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  const handleDeleteAction = handleDelete
+
+  function handleDialogSubmit(formData) {
+    dialogSubmitting.value = true
+    return handleCrudDialogSubmit(formData).finally(() => {
+      dialogSubmitting.value = false
+    })
+  }
+
+  async function loadTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({ current: 1, pageSize: 200, dictTypeCode: 'sys_transfer_type', status: 1 }),
+      { records: [] },
+      { timeoutMessage: '璋冩嫧绫诲瀷閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    typeOptions.value = resolveTransferTypeOptions(defaultResponseAdapter(response).records)
+  }
+
+  async function loadAreaOptions() {
+    const response = await guardRequestWithMessage(
+      fetchWarehouseAreasList(),
+      { records: [] },
+      { timeoutMessage: '搴撳尯閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    areaOptions.value = resolveTransferAreaOptions(defaultResponseAdapter(response).records)
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildTransferSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createTransferSearchState()
+    resetSearchParams()
+  }
+
+  async function handleDetailCurrentChange(current) {
+    detailOrderPagination.current = current
+    await loadTransferOrders(activeTransferCode.value)
+  }
+
+  async function handleDetailSizeChange(size) {
+    detailOrderPagination.size = size
+    detailOrderPagination.current = 1
+    await loadTransferOrders(activeTransferCode.value)
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchTransferMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTransferPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta: rawPreviewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'transfer.xlsx',
+    requestExport: (payload) =>
+      fetchExportTransferReport(Array.isArray(payload?.ids) && payload.ids.length > 0 ? reportQueryParams.value : payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTransferPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          titleAlign: 'center',
+          titleLevel: 'strong',
+          orientation: 'landscape',
+          density: 'compact',
+          showSequence: true
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildTransferReportMeta({
+      previewMeta: rawPreviewMeta.value,
+      count: previewRows.value.length,
+      orientation: rawPreviewMeta.value?.reportStyle?.orientation || TRANSFER_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(async () => {
+    await Promise.allSettled([loadTypeOptions(), loadAreaOptions(), getData()])
+  })
+</script>
diff --git a/rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue b/rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue
new file mode 100644
index 0000000..21fe181
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer/modules/transfer-detail-drawer.vue
@@ -0,0 +1,93 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璋冩嫧鍗曡鎯�"
+    size="1200px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="璋冩嫧鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璋冩嫧绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉ユ簮">
+            <ElTag :type="detail.sourceTagType || 'info'" effect="light">
+              {{ detail.sourceText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愪粨搴�">{{ detail.orgWareName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣浠撳簱">{{ detail.tarWareName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="婧愬簱鍖�">{{ detail.orgAreaName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣搴撳尯">{{ detail.tarAreaName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+            <ElTag effect="plain">鍏� {{ orderRows.length }} 鏉�</ElTag>
+          </div>
+          <ArtTable
+            :loading="ordersLoading"
+            :data="orderRows"
+            :columns="orderColumns"
+            :pagination="orderPagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </div>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import ArtTable from '@/components/core/tables/art-table/index.vue'
+  import { createTransferOrderTableColumns } from '../transferTable.columns.js'
+
+  defineOptions({ name: 'TransferDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    ordersLoading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    orderRows: { type: Array, default: () => [] },
+    orderPagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+
+  const orderColumns = createTransferOrderTableColumns()
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue b/rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue
new file mode 100644
index 0000000..9b14805
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer/modules/transfer-dialog.vue
@@ -0,0 +1,184 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="760px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <div class="mb-3 rounded-lg border border-[var(--art-border-color)] bg-[var(--art-bg-color)] px-3 py-2 text-xs text-[var(--art-text-gray-600)]">
+      璋冩嫧鍗曞彿鐢辩郴缁熺敓鎴愶紝鏂板鏃跺彧闇�缁存姢璋冩嫧绫诲瀷銆佹簮/鐩爣搴撳尯鍜屽娉ㄣ��
+    </div>
+
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" :loading="submitLoading" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, nextTick, reactive, ref, watch } from 'vue'
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTransferDialogModel,
+    createTransferFormState,
+    getTransferStatusOptions
+  } from '../transferPage.helpers.js'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dialogType: { type: String, default: 'add' },
+    transferData: { type: Object, default: () => ({}) },
+    typeOptions: { type: Array, default: () => [] },
+    areaOptions: { type: Array, default: () => [] },
+    submitLoading: { type: Boolean, default: false }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTransferFormState())
+
+  const isEdit = computed(() => props.dialogType === 'edit')
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫璋冩嫧鍗�' : '鏂板璋冩嫧鍗�'))
+
+  const rules = computed(() => ({
+    type: [{ required: true, message: '璇烽�夋嫨璋冩嫧绫诲瀷', trigger: 'change' }],
+    orgAreaId: [{ required: true, message: '璇烽�夋嫨婧愬簱鍖�', trigger: 'change' }],
+    tarAreaId: [{ required: true, message: '璇烽�夋嫨鐩爣搴撳尯', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '璋冩嫧鍗曞彿',
+      key: 'code',
+      type: 'input',
+      span: 24,
+      props: {
+        disabled: true,
+        placeholder: '淇濆瓨鍚庤嚜鍔ㄧ敓鎴�'
+      }
+    },
+    {
+      label: '璋冩嫧绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨璋冩嫧绫诲瀷',
+        clearable: true,
+        filterable: true,
+        options: props.typeOptions
+      }
+    },
+    {
+      label: '婧愬簱鍖�',
+      key: 'orgAreaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨婧愬簱鍖�',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions
+      }
+    },
+    {
+      label: '鐩爣搴撳尯',
+      key: 'tarAreaId',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐩爣搴撳尯',
+        clearable: true,
+        filterable: true,
+        options: props.areaOptions
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: true,
+        options: getTransferStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const loadFormData = () => {
+    Object.assign(form, buildTransferDialogModel(props.transferData))
+  }
+
+  const resetForm = () => {
+    Object.assign(form, createTransferFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.transferData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/orders/transfer/transferPage.helpers.js b/rsf-design/src/views/orders/transfer/transferPage.helpers.js
new file mode 100644
index 0000000..7e06d2c
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer/transferPage.helpers.js
@@ -0,0 +1,347 @@
+const TRANSFER_SOURCE_META = {
+  1: { text: 'ERP绯荤粺', type: 'info' },
+  2: { text: 'WMS绯荤粺鐢熸垚', type: 'primary' },
+  3: { text: 'EXCEL瀵煎叆', type: 'warning' },
+  4: { text: 'QMS绯荤粺', type: 'success' }
+}
+
+const TRANSFER_EXCE_STATUS_META = {
+  0: { text: '鏈墽琛�', type: 'info' },
+  1: { text: '鎵ц涓�', type: 'warning' },
+  2: { text: '鎵ц瀹屾垚', type: 'success' }
+}
+
+const TRANSFER_STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export const TRANSFER_REPORT_TITLE = '璋冩嫧鍗曟姤琛�'
+export const TRANSFER_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) return fallback
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function metaByValue(value, metaMap, fallbackText = '--') {
+  const numericValue = Number(value)
+  return metaMap[numericValue] || { text: normalizeText(value) || fallbackText, type: 'info' }
+}
+
+export function createTransferSearchState() {
+  return {
+    condition: '',
+    code: '',
+    type: '',
+    source: '',
+    exceStatus: '',
+    orgWareName: '',
+    tarWareName: '',
+    orgAreaName: '',
+    tarAreaName: '',
+    memo: '',
+    status: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createTransferFormState() {
+  return {
+    id: void 0,
+    code: '',
+    type: '',
+    source: 2,
+    exceStatus: 0,
+    orgAreaId: void 0,
+    tarAreaId: void 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function buildTransferDialogModel(record = {}) {
+  return {
+    ...createTransferFormState(),
+    ...(record.id !== undefined && record.id !== null && record.id !== '' ? { id: Number(record.id) } : {}),
+    code: normalizeText(record.code || ''),
+    type: record.type !== undefined && record.type !== null && record.type !== '' ? Number(record.type) : '',
+    source: record.source !== undefined && record.source !== null && record.source !== '' ? Number(record.source) : 2,
+    exceStatus:
+      record.exceStatus !== undefined && record.exceStatus !== null && record.exceStatus !== ''
+        ? Number(record.exceStatus)
+        : 0,
+    orgAreaId:
+      record.orgAreaId !== undefined && record.orgAreaId !== null && record.orgAreaId !== ''
+        ? Number(record.orgAreaId)
+        : void 0,
+    tarAreaId:
+      record.tarAreaId !== undefined && record.tarAreaId !== null && record.tarAreaId !== ''
+        ? Number(record.tarAreaId)
+        : void 0,
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function getTransferPaginationKey() {
+  return { current: 'current', size: 'pageSize' }
+}
+
+export function getTransferSourceOptions() {
+  return [
+    { label: 'ERP绯荤粺', value: 1 },
+    { label: 'WMS绯荤粺鐢熸垚', value: 2 },
+    { label: 'EXCEL瀵煎叆', value: 3 },
+    { label: 'QMS绯荤粺', value: 4 }
+  ]
+}
+
+export function getTransferStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getTransferExceStatusOptions() {
+  return [
+    { label: '鏈墽琛�', value: 0 },
+    { label: '鎵ц涓�', value: 1 },
+    { label: '鎵ц瀹屾垚', value: 2 }
+  ]
+}
+
+export function buildTransferSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'orgWareName', 'tarWareName', 'orgAreaName', 'tarAreaName', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) result[key] = value
+  })
+  ;['type', 'source', 'exceStatus', 'status', 'orgWareId', 'tarWareId', 'orgAreaId', 'tarAreaId'].forEach((key) => {
+    if (params[key] !== '' && params[key] !== undefined && params[key] !== null) {
+      result[key] = normalizeNumber(params[key])
+    }
+  })
+  if (params.timeStart !== '' && params.timeStart !== undefined && params.timeStart !== null) {
+    result.timeStart = normalizeText(params.timeStart)
+  }
+  if (params.timeEnd !== '' && params.timeEnd !== undefined && params.timeEnd !== null) {
+    result.timeEnd = normalizeText(params.timeEnd)
+  }
+  return result
+}
+
+export function buildTransferPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTransferSearchParams(params)
+  }
+}
+
+export function buildTransferDetailOrderQueryParams(params = {}) {
+  return {
+    condition: normalizeText(params.code || params.condition),
+    code: normalizeText(params.code || params.condition),
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function buildTransferSavePayload(formData = {}, areaOptions = []) {
+  const optionMap = new Map(
+    (Array.isArray(areaOptions) ? areaOptions : [])
+      .map((item) => {
+        const value = normalizeNumber(item?.value ?? item?.id, void 0)
+        if (value === void 0) return null
+        return [value, item?.raw || item]
+      })
+      .filter(Boolean)
+  )
+
+  const orgAreaId = normalizeNumber(formData.orgAreaId, void 0)
+  const tarAreaId = normalizeNumber(formData.tarAreaId, void 0)
+  const orgArea = optionMap.get(orgAreaId) || {}
+  const tarArea = optionMap.get(tarAreaId) || {}
+  const orgWareId = normalizeNumber(orgArea.warehouseId ?? orgArea.warehouse_id ?? orgArea.warehouseIdValue, void 0)
+  const tarWareId = normalizeNumber(tarArea.warehouseId ?? tarArea.warehouse_id ?? tarArea.warehouseIdValue, void 0)
+
+  return {
+    ...(formData.id !== undefined && formData.id !== null && formData.id !== ''
+      ? { id: normalizeNumber(formData.id) }
+      : {}),
+    ...(normalizeText(formData.code) ? { code: normalizeText(formData.code) } : {}),
+    ...(formData.type !== undefined && formData.type !== null && formData.type !== ''
+      ? { type: normalizeNumber(formData.type) }
+      : {}),
+    ...(formData.source !== undefined && formData.source !== null && formData.source !== ''
+      ? { source: normalizeNumber(formData.source) }
+      : {}),
+    ...(formData.exceStatus !== undefined && formData.exceStatus !== null && formData.exceStatus !== ''
+      ? { exceStatus: normalizeNumber(formData.exceStatus) }
+      : {}),
+    ...(orgAreaId !== void 0 ? { orgAreaId } : {}),
+    ...(tarAreaId !== void 0 ? { tarAreaId } : {}),
+    ...(orgWareId !== void 0 ? { orgWareId } : {}),
+    ...(tarWareId !== void 0 ? { tarWareId } : {}),
+    ...(normalizeText(orgArea.name || orgArea.areaName) ? { orgAreaName: normalizeText(orgArea.name || orgArea.areaName) } : {}),
+    ...(normalizeText(tarArea.name || tarArea.areaName) ? { tarAreaName: normalizeText(tarArea.name || tarArea.areaName) } : {}),
+    ...(normalizeText(orgArea.warehouseId$ || orgArea.warehouseName) ? { orgWareName: normalizeText(orgArea.warehouseId$ || orgArea.warehouseName) } : {}),
+    ...(normalizeText(tarArea.warehouseId$ || tarArea.warehouseName) ? { tarWareName: normalizeText(tarArea.warehouseId$ || tarArea.warehouseName) } : {}),
+    ...(formData.status !== undefined && formData.status !== null && formData.status !== ''
+      ? { status: normalizeNumber(formData.status) }
+      : { status: 1 }),
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+function resolveAreaText(record = {}, key) {
+  return normalizeText(record[`${key}AreaName$`] || record[`${key}AreaName`] || record[`${key}AreaId$`] || record[`${key}AreaId`])
+}
+
+function resolveWarehouseText(record = {}, key) {
+  return normalizeText(record[`${key}WareName$`] || record[`${key}WareName`] || record[`${key}WareId$`] || record[`${key}WareId`])
+}
+
+export function normalizeTransferRow(record = {}) {
+  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, '鏈煡')
+  const exceStatusMeta = metaByValue(record.exceStatus, TRANSFER_EXCE_STATUS_META, record.exceStatusText)
+  const sourceMeta = metaByValue(record.source, TRANSFER_SOURCE_META, record.sourceText)
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: normalizeText(record.code) || '--',
+    typeLabel: normalizeText(record['type$'] || record.type) || '--',
+    sourceText: sourceMeta.text,
+    sourceTagType: sourceMeta.type,
+    exceStatusText: normalizeText(record['exceStatus$'] || record.exceStatusText) || exceStatusMeta.text,
+    exceStatusTagType: exceStatusMeta.type,
+    orgWareName: resolveWarehouseText(record, 'org') || '--',
+    tarWareName: resolveWarehouseText(record, 'tar') || '--',
+    orgAreaName: resolveAreaText(record, 'org') || '--',
+    tarAreaName: resolveAreaText(record, 'tar') || '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function normalizeTransferDetailRecord(record = {}) {
+  return normalizeTransferRow(record)
+}
+
+export function normalizeTransferOrderRow(record = {}) {
+  const statusMeta = metaByValue(record.statusBool ?? record.status, TRANSFER_STATUS_META, '鏈煡')
+  const exceStatusMeta = metaByValue(record.exceStatus, TRANSFER_EXCE_STATUS_META, record.exceStatusText)
+  return {
+    ...record,
+    id: record.id ?? null,
+    code: normalizeText(record.code) || '--',
+    poCode: normalizeText(record.poCode) || '--',
+    typeLabel: normalizeText(record['type$'] || record.type) || '--',
+    wkTypeLabel: normalizeText(record['wkType$'] || record.wkType) || '--',
+    exceStatusText: normalizeText(record['exceStatus$'] || record.exceStatusText) || exceStatusMeta.text,
+    exceStatusTagType: exceStatusMeta.type,
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    qty: record.qty ?? '--',
+    stationId: normalizeText(record.stationId) || '--',
+    businessTimeText: normalizeText(record['businessTime$'] || record.businessTimeText || record.businessTime) || '--',
+    createByText: normalizeText(record['createBy$'] || record.createByText) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText) || '--',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function buildTransferPrintRows(records = []) {
+  return Array.isArray(records) ? records.map((record) => normalizeTransferRow(record)) : []
+}
+
+export function buildTransferOrderPrintRows(records = []) {
+  return Array.isArray(records) ? records.map((record) => normalizeTransferOrderRow(record)) : []
+}
+
+export function buildTransferReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = TRANSFER_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: TRANSFER_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...TRANSFER_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getTransferActionList(row = {}) {
+  const normalizedRow = normalizeTransferRow(row)
+  const actions = [
+    { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:eye-line' },
+    { key: 'edit', label: '缂栬緫', icon: 'ri:pencil-line' }
+  ]
+  if (Number(normalizedRow.exceStatus) === 0) {
+    actions.push({ key: 'publish', label: '涓嬪彂鎵ц', icon: 'ri:send-plane-line' })
+    actions.push({ key: 'delete', label: '鍒犻櫎', icon: 'ri:delete-bin-5-line', color: 'var(--art-error)' })
+  }
+  return actions
+}
+
+export function resolveTransferAreaOptions(records = []) {
+  if (!Array.isArray(records)) return []
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') return null
+      const value = normalizeNumber(item.id ?? item.value, void 0)
+      if (value === void 0) return null
+      return {
+        value,
+        label: normalizeText(item.name || item.areaName || item.code || item.warehouseId$ || item.warehouseName || `搴撳尯 ${value}`),
+        raw: item
+      }
+    })
+    .filter(Boolean)
+}
+
+export function resolveTransferTypeOptions(records = []) {
+  if (!Array.isArray(records)) return []
+  return records
+    .map((item) => {
+      if (!item || typeof item !== 'object') return null
+      const value = normalizeNumber(item.value ?? item.id, void 0)
+      if (value === void 0) return null
+      return {
+        value,
+        label: normalizeText(item.label || item.name || item.dictLabel || `绫诲瀷 ${value}`)
+      }
+    })
+    .filter(Boolean)
+}
diff --git a/rsf-design/src/views/orders/transfer/transferTable.columns.js b/rsf-design/src/views/orders/transfer/transferTable.columns.js
new file mode 100644
index 0000000..aa3760d
--- /dev/null
+++ b/rsf-design/src/views/orders/transfer/transferTable.columns.js
@@ -0,0 +1,199 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getTransferActionList } from './transferPage.helpers.js'
+
+export function createTransferTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '璋冩嫧鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'typeLabel',
+      label: '璋冩嫧绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeLabel || '--'
+    },
+    {
+      prop: 'sourceText',
+      label: '鏉ユ簮',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) =>
+        h(ElTag, { type: row.sourceTagType || 'info', effect: 'light' }, () => row.sourceText || '--')
+    },
+    {
+      prop: 'orgWareName',
+      label: '婧愪粨搴�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.orgWareName || '--'
+    },
+    {
+      prop: 'tarWareName',
+      label: '鐩爣浠撳簱',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.tarWareName || '--'
+    },
+    {
+      prop: 'orgAreaName',
+      label: '婧愬簱鍖�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.orgAreaName || '--'
+    },
+    {
+      prop: 'tarAreaName',
+      label: '鐩爣搴撳尯',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.tarAreaName || '--'
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      minWidth: 120,
+      formatter: (row) =>
+        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 220,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getTransferActionList(row),
+          onClick: (item) => handleActionClick?.(item, row)
+        })
+    }
+  ]
+}
+
+export function createTransferOrderTableColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '鍏宠仈鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'poCode',
+      label: '璋冩嫧鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.poCode || '--'
+    },
+    {
+      prop: 'typeLabel',
+      label: '鍗曟嵁绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeLabel || '--'
+    },
+    {
+      prop: 'wkTypeLabel',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeLabel || '--'
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      minWidth: 120,
+      formatter: (row) =>
+        h(ElTag, { type: row.exceStatusTagType || 'info', effect: 'light' }, () => row.exceStatusText || '--')
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插畬鎴愭暟閲�',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'stationId',
+      label: '绔欑偣',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.stationId || '--'
+    },
+    {
+      prop: 'businessTimeText',
+      label: '涓氬姟鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.businessTimeText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-item-log/index.vue b/rsf-design/src/views/orders/wait-pakin-item-log/index.vue
new file mode 100644
index 0000000..7973ab0
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item-log/index.vue
@@ -0,0 +1,329 @@
+<template>
+  <div class="wait-pakin-item-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WaitPakinItemLogDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportWaitPakinItemLogReport,
+    fetchWaitPakinItemLogDetail,
+    fetchWaitPakinItemLogMany,
+    fetchWaitPakinItemLogPage
+  } from '@/api/wait-pakin-item-log'
+  import WaitPakinItemLogDetailDrawer from './modules/wait-pakin-item-log-detail-drawer.vue'
+  import { createWaitPakinItemLogTableColumns } from './waitPakinItemLogTable.columns'
+  import {
+    WAIT_PAKIN_ITEM_LOG_REPORT_STYLE,
+    WAIT_PAKIN_ITEM_LOG_REPORT_TITLE,
+    buildWaitPakinItemLogPageQueryParams,
+    buildWaitPakinItemLogPrintRows,
+    buildWaitPakinItemLogReportMeta,
+    buildWaitPakinItemLogSearchParams,
+    createWaitPakinItemLogSearchState,
+    getWaitPakinItemLogPaginationKey,
+    getWaitPakinItemLogStatusOptions,
+    normalizeWaitPakinItemLogRow
+  } from './waitPakinItemLogPage.helpers'
+
+  defineOptions({ name: 'WaitPakinItemLog' })
+
+  const userStore = useUserStore()
+  const reportTitle = WAIT_PAKIN_ITEM_LOG_REPORT_TITLE
+  const searchForm = ref(createWaitPakinItemLogSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ棩蹇桰D/缁勬墭鍗旾D/鐗╂枡缂栫爜/璺熻釜鐮�'
+      }
+    },
+    {
+      label: '鏃ュ織ID',
+      key: 'logId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ棩蹇桰D'
+      }
+    },
+    {
+      label: '缁勬墭鍗旾D',
+      key: 'pakinId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟ID'
+      }
+    },
+    {
+      label: '缁勬墭鏄庣粏ID',
+      key: 'pakinItemId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ粍鎵樻槑缁咺D'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'asnCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿'
+      }
+    },
+    {
+      label: 'ASN鏄庣粏ID',
+      key: 'asnItemId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏SN鏄庣粏ID'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '璺熻釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ窡韪爜'
+      }
+    },
+    {
+      label: '鎵规鍙�',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWaitPakinItemLogStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  const reportQueryParams = computed(() => buildWaitPakinItemLogSearchParams(searchForm.value))
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchWaitPakinItemLogPage,
+      apiParams: buildWaitPakinItemLogPageQueryParams(searchForm.value),
+      paginationKey: getWaitPakinItemLogPaginationKey(),
+      columnsFactory: () =>
+        createWaitPakinItemLogTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeWaitPakinItemLogRow(item)) : []
+    }
+  })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeWaitPakinItemLogRow(
+        await guardRequestWithMessage(fetchWaitPakinItemLogDetail(row.id), {}, {
+          timeoutMessage: '缁勬墭鏄庣粏鍘嗗彶妗h鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        })
+      )
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇缁勬墭鏄庣粏鍘嗗彶妗h鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildWaitPakinItemLogPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWaitPakinItemLogSearchState())
+    resetSearchParams()
+  }
+
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportTitle,
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: {
+        ...WAIT_PAKIN_ITEM_LOG_REPORT_STYLE
+      }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchWaitPakinItemLogMany(payload.ids)).records
+    }
+
+    return defaultResponseAdapter(
+      await fetchWaitPakinItemLogPage({
+        ...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: 'wait-pakin-item-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaitPakinItemLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWaitPakinItemLogPrintRows(records),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaitPakinItemLogReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation ||
+        WAIT_PAKIN_ITEM_LOG_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-item-log/modules/wait-pakin-item-log-detail-drawer.vue b/rsf-design/src/views/orders/wait-pakin-item-log/modules/wait-pakin-item-log-detail-drawer.vue
new file mode 100644
index 0000000..e789a2a
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item-log/modules/wait-pakin-item-log-detail-drawer.vue
@@ -0,0 +1,70 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缁勬墭鏄庣粏鍘嗗彶妗h鎯�"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鏃ュ織ID">{{ detail.logId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁勬墭鍗旾D">{{ detail.pakinId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁勬墭鏄庣粏ID">{{ detail.pakinItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.asnCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN鏄庣粏ID">{{ detail.asnItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璺熻釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="鏁伴噺淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸插畬鎴愭暟閲�">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规鍙�">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡ID">{{ detail.matnrId || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogPage.helpers.js b/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogPage.helpers.js
new file mode 100644
index 0000000..e71ae65
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogPage.helpers.js
@@ -0,0 +1,167 @@
+export const WAIT_PAKIN_ITEM_LOG_REPORT_TITLE = '缁勬墭鏄庣粏鍘嗗彶妗f姤琛�'
+export const WAIT_PAKIN_ITEM_LOG_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+function normalizeText(value, fallback = '--') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  const text = String(value).trim()
+  return text || fallback
+}
+
+function normalizeNumber(value, fallback = '--') {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isNaN(numericValue) ? fallback : numericValue
+}
+
+export function createWaitPakinItemLogSearchState() {
+  return {
+    condition: '',
+    logId: '',
+    pakinId: '',
+    pakinItemId: '',
+    asnId: '',
+    asnCode: '',
+    asnItemId: '',
+    trackCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function getWaitPakinItemLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWaitPakinItemLogStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getWaitPakinItemLogStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildWaitPakinItemLogSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'asnCode',
+    'trackCode',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key], '')
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['logId', 'pakinId', 'pakinItemId', 'asnId', 'asnItemId', 'status'].forEach((key) => {
+    if (params[key] === '' || params[key] === null || params[key] === undefined) {
+      return
+    }
+    const numericValue = Number(params[key])
+    if (!Number.isNaN(numericValue)) {
+      result[key] = numericValue
+    }
+  })
+
+  return result
+}
+
+export function buildWaitPakinItemLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaitPakinItemLogSearchParams(params)
+  }
+}
+
+export function normalizeWaitPakinItemLogRow(record = {}) {
+  const statusMeta = getWaitPakinItemLogStatusMeta(record.statusBool ?? record.status)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    logId: normalizeText(record.logId),
+    pakinId: normalizeText(record.pakinId),
+    pakinItemId: normalizeText(record.pakinItemId),
+    asnId: normalizeText(record.asnId),
+    asnCode: normalizeText(record.asnCode),
+    asnItemId: normalizeText(record.asnItemId),
+    trackCode: normalizeText(record.trackCode),
+    maktx: normalizeText(record.maktx),
+    matnrId: normalizeText(record.matnrId),
+    matnrCode: normalizeText(record.matnrCode),
+    anfme: normalizeNumber(record.anfme),
+    workQty: normalizeNumber(record.workQty),
+    qty: normalizeNumber(record.qty),
+    unit: normalizeText(record.unit),
+    batch: normalizeText(record.batch),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo),
+    createByText: normalizeText(record['createBy$'] || record.createByText, '--'),
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime, '--'),
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText, '--'),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime, '--')
+  }
+}
+
+export function buildWaitPakinItemLogPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaitPakinItemLogRow(record))
+}
+
+export function buildWaitPakinItemLogReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAIT_PAKIN_ITEM_LOG_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAIT_PAKIN_ITEM_LOG_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAIT_PAKIN_ITEM_LOG_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogTable.columns.js b/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogTable.columns.js
new file mode 100644
index 0000000..9105aa1
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item-log/waitPakinItemLogTable.columns.js
@@ -0,0 +1,131 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import { getWaitPakinItemLogStatusMeta } from './waitPakinItemLogPage.helpers'
+
+export function createWaitPakinItemLogTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'logId',
+      label: '鏃ュ織ID',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.logId || '--'
+    },
+    {
+      prop: 'pakinId',
+      label: '缁勬墭鍗旾D',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.pakinId || '--'
+    },
+    {
+      prop: 'pakinItemId',
+      label: '缁勬墭鏄庣粏ID',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.pakinItemId || '--'
+    },
+    {
+      prop: 'asnCode',
+      label: 'ASN鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.asnCode || '--'
+    },
+    {
+      prop: 'asnItemId',
+      label: 'ASN鏄庣粏ID',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.asnItemId || '--'
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.trackCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插畬鎴愭暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getWaitPakinItemLogStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 96,
+      fixed: 'right',
+      align: 'center',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-item/index.vue b/rsf-design/src/views/orders/wait-pakin-item/index.vue
new file mode 100644
index 0000000..16e289d
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item/index.vue
@@ -0,0 +1,333 @@
+<template>
+  <div class="wait-pakin-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WaitPakinItemDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchExportWaitPakinItemReport,
+    fetchWaitPakinItemDetail,
+    fetchWaitPakinItemMany,
+    fetchWaitPakinItemPage
+  } from '@/api/wait-pakin-item'
+  import WaitPakinItemDetailDrawer from './modules/wait-pakin-item-detail-drawer.vue'
+  import { createWaitPakinItemTableColumns } from './waitPakinItemTable.columns'
+  import {
+    WAIT_PAKIN_ITEM_REPORT_STYLE,
+    WAIT_PAKIN_ITEM_REPORT_TITLE,
+    buildWaitPakinItemPageQueryParams,
+    buildWaitPakinItemPrintRows,
+    buildWaitPakinItemReportMeta,
+    buildWaitPakinItemSearchParams,
+    createWaitPakinItemSearchState,
+    getWaitPakinItemPaginationKey,
+    getWaitPakinItemStatusOptions,
+    normalizeWaitPakinItemRow
+  } from './waitPakinItemPage.helpers'
+
+  defineOptions({ name: 'WaitPakinItem' })
+
+  const userStore = useUserStore()
+  const reportTitle = WAIT_PAKIN_ITEM_REPORT_TITLE
+  const searchForm = ref(createWaitPakinItemSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟ID/ASN鍗曞彿/鐗╂枡缂栫爜/璺熻釜鐮�'
+      }
+    },
+    {
+      label: '缁勬墭鍗旾D',
+      key: 'pakinId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟ID'
+      }
+    },
+    {
+      label: '璁㈠崟绫诲瀷',
+      key: 'type',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍗曠被鍨�'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'wkType',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�'
+      }
+    },
+    {
+      label: 'ASN鍗曞彿',
+      key: 'asnCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏SN鍗曞彿'
+      }
+    },
+    {
+      label: 'ASN鏄庣粏ID',
+      key: 'asnItemId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏SN鏄庣粏ID'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '璺熻釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ窡韪爜'
+      }
+    },
+    {
+      label: '鎵规鍙�',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿'
+      }
+    },
+    {
+      label: '璐ㄦ缁撴灉',
+      key: 'isptResult',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鍚堟牸', value: 1 },
+          { label: '涓嶅悎鏍�', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWaitPakinItemStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  const reportQueryParams = computed(() => buildWaitPakinItemSearchParams(searchForm.value))
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchWaitPakinItemPage,
+      apiParams: buildWaitPakinItemPageQueryParams(searchForm.value),
+      paginationKey: getWaitPakinItemPaginationKey(),
+      columnsFactory: () =>
+        createWaitPakinItemTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeWaitPakinItemRow(item)) : []
+    }
+  })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchWaitPakinItemDetail(row.id), {}, {
+        timeoutMessage: '缁勬墭鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeWaitPakinItemRow({
+        ...row,
+        ...(detail || {}),
+        extendFields: detail?.extendFields ?? row.extendFields ?? {}
+      })
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇缁勬墭鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildWaitPakinItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWaitPakinItemSearchState())
+    resetSearchParams()
+  }
+
+  const buildPreviewMeta = (rows) => {
+    const now = new Date()
+    return {
+      reportTitle,
+      reportDate: now.toLocaleDateString('zh-CN'),
+      printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length,
+      reportStyle: {
+        ...WAIT_PAKIN_ITEM_REPORT_STYLE
+      }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchWaitPakinItemMany(payload.ids)).records
+    }
+
+    return defaultResponseAdapter(
+      await fetchWaitPakinItemPage({
+        ...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: 'wait-pakin-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaitPakinItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWaitPakinItemPrintRows(records),
+    buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaitPakinItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || WAIT_PAKIN_ITEM_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-item/modules/wait-pakin-item-detail-drawer.vue b/rsf-design/src/views/orders/wait-pakin-item/modules/wait-pakin-item-detail-drawer.vue
new file mode 100644
index 0000000..bc126bc
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item/modules/wait-pakin-item-detail-drawer.vue
@@ -0,0 +1,102 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缁勬墭鏄庣粏璇︽儏"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="缁勬墭鏄庣粏ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁勬墭鍗旾D">{{ detail.pakinId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN鍗曞彿">{{ detail.asnCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="ASN鏄庣粏ID">{{ detail.asnItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁㈠崟绫诲瀷">{{ detail.typeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.wkTypeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璐ㄦ缁撴灉">{{ detail.isptResultText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions title="鐗╂枡淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="鐗╂枡ID">{{ detail.matnrId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璺熻釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规鍙�">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц涓暟閲�">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸插畬鎴愭暟閲�">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍩虹鍗曚綅">{{ detail.baseUnit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀛楁绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璁㈠崟鏉ユ簮">{{ detail.source || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="骞冲彴琛屽彿">{{ detail.platItemId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹㈡埛璁㈠崟鍙�">{{ detail.platOrderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸ュ崟鍙�">{{ detail.platWorkCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="椤圭洰鍙�">{{ detail.projectCode || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElDescriptions v-if="extendFieldEntries.length > 0" title="鎵╁睍瀛楁" :column="2" border>
+          <ElDescriptionsItem
+            v-for="entry in extendFieldEntries"
+            :key="entry.key"
+            :label="entry.key"
+          >
+            {{ entry.value || '--' }}
+          </ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  const extendFieldEntries = computed(() => {
+    const extendFields = props.detail?.extendFields
+    if (!extendFields || typeof extendFields !== 'object' || Array.isArray(extendFields)) {
+      return []
+    }
+    return Object.entries(extendFields)
+      .map(([key, value]) => ({
+        key: String(key ?? '').trim(),
+        value: String(value ?? '').trim()
+      }))
+      .filter((entry) => entry.key)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemPage.helpers.js b/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemPage.helpers.js
new file mode 100644
index 0000000..03fb976
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemPage.helpers.js
@@ -0,0 +1,220 @@
+export const WAIT_PAKIN_ITEM_REPORT_TITLE = '缁勬墭鏄庣粏鎶ヨ〃'
+export const WAIT_PAKIN_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+const ISPT_RESULT_OPTIONS = [
+  { label: '鍚堟牸', value: 1 },
+  { label: '涓嶅悎鏍�', value: 0 }
+]
+
+function normalizeText(value, fallback = '--') {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  const text = String(value).trim()
+  return text || fallback
+}
+
+function normalizeNumber(value, fallback = '--') {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isNaN(numericValue) ? fallback : numericValue
+}
+
+function buildObjectEntries(record = {}) {
+  return Object.fromEntries(
+    Object.entries(record)
+      .filter(([, value]) => value !== undefined && value !== null && value !== '')
+      .map(([key, value]) => [key, normalizeText(value, '')])
+      .filter(([, value]) => value !== '')
+  )
+}
+
+export function createWaitPakinItemSearchState() {
+  return {
+    condition: '',
+    pakinId: '',
+    type: '',
+    wkType: '',
+    asnCode: '',
+    asnItemId: '',
+    matnrCode: '',
+    trackCode: '',
+    batch: '',
+    isptResult: '',
+    status: '',
+    memo: '',
+    fieldsIndex: ''
+  }
+}
+
+export function getWaitPakinItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWaitPakinItemStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getWaitPakinItemIsptResultOptions() {
+  return ISPT_RESULT_OPTIONS
+}
+
+export function getWaitPakinItemStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildWaitPakinItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'type',
+    'asnCode',
+    'matnrCode',
+    'trackCode',
+    'batch',
+    'memo',
+    'fieldsIndex'
+  ].forEach((key) => {
+    const value = normalizeText(params[key], '')
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['pakinId', 'asnItemId', 'wkType', 'isptResult', 'status'].forEach((key) => {
+    if (params[key] === '' || params[key] === null || params[key] === undefined) {
+      return
+    }
+    const numericValue = Number(params[key])
+    if (!Number.isNaN(numericValue)) {
+      result[key] = numericValue
+    }
+  })
+
+  return result
+}
+
+export function buildWaitPakinItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaitPakinItemSearchParams(params)
+  }
+}
+
+export function normalizeWaitPakinItemExtendFields(extendFields = {}) {
+  if (!extendFields || typeof extendFields !== 'object' || Array.isArray(extendFields)) {
+    return {}
+  }
+
+  return buildObjectEntries(extendFields)
+}
+
+export function normalizeWaitPakinItemRow(record = {}) {
+  const extendFields = normalizeWaitPakinItemExtendFields(record.extendFields)
+  const extendFieldEntries = Object.entries(extendFields)
+  const statusMeta = getWaitPakinItemStatusMeta(record.statusBool ?? record.status)
+
+  return {
+    ...record,
+    id: record.id ?? null,
+    pakinId: normalizeText(record.pakinId),
+    typeText: normalizeText(record['type$'] || record.type, '--'),
+    wkTypeText: normalizeText(record['wkType$'] || record.wkType, '--'),
+    source: normalizeText(record.source, '--'),
+    isptResultText: normalizeText(record['isptResult$'] || record.isptResultText, '--'),
+    asnId: normalizeText(record.asnId),
+    asnCode: normalizeText(record.asnCode),
+    asnItemId: normalizeText(record.asnItemId),
+    platItemId: normalizeText(record.platItemId, '--'),
+    platOrderCode: normalizeText(record.platOrderCode, '--'),
+    platWorkCode: normalizeText(record.platWorkCode, '--'),
+    projectCode: normalizeText(record.projectCode, '--'),
+    maktx: normalizeText(record.maktx),
+    matnrId: normalizeText(record.matnrId),
+    matnrCode: normalizeText(record.matnrCode),
+    anfme: normalizeNumber(record.anfme),
+    workQty: normalizeNumber(record.workQty),
+    qty: normalizeNumber(record.qty),
+    unit: normalizeText(record.unit),
+    fieldsIndex: normalizeText(record.fieldsIndex, '--'),
+    batch: normalizeText(record.batch),
+    baseUnit: normalizeText(record.baseUnit, '--'),
+    useOrgId: normalizeText(record.useOrgId, '--'),
+    useOrgName: normalizeText(record.useOrgName, '--'),
+    erpClsId: normalizeText(record.erpClsId, '--'),
+    priceUnitId: normalizeText(record.priceUnitId, '--'),
+    inStockType: normalizeText(record.inStockType, '--'),
+    ownerTypeId: normalizeText(record.ownerTypeId, '--'),
+    ownerId: normalizeText(record.ownerId, '--'),
+    ownerName: normalizeText(record.ownerName, '--'),
+    keeperTypeId: normalizeText(record.keeperTypeId, '--'),
+    keeperId: normalizeText(record.keeperId, '--'),
+    keeperName: normalizeText(record.keeperName, '--'),
+    targetWarehouseId: normalizeText(record.targetWarehouseId, '--'),
+    sourceWarehouseId: normalizeText(record.sourceWarehouseId, '--'),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo),
+    createByText: normalizeText(record['createBy$'] || record.createByText, '--'),
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime, '--'),
+    updateByText: normalizeText(record['updateBy$'] || record.updateByText, '--'),
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime, '--'),
+    extendFields,
+    extendFieldsText: extendFieldEntries.length
+      ? extendFieldEntries.map(([key, value]) => `${key}:${value}`).join('锛�')
+      : '--'
+  }
+}
+
+export function buildWaitPakinItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaitPakinItemRow(record))
+}
+
+export function buildWaitPakinItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAIT_PAKIN_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAIT_PAKIN_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAIT_PAKIN_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemTable.columns.js b/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemTable.columns.js
new file mode 100644
index 0000000..33ea4b5
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-item/waitPakinItemTable.columns.js
@@ -0,0 +1,145 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import { getWaitPakinItemStatusMeta } from './waitPakinItemPage.helpers'
+
+export function createWaitPakinItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'pakinId',
+      label: '缁勬墭鍗旾D',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.pakinId || '--'
+    },
+    {
+      prop: 'asnCode',
+      label: 'ASN鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.asnCode || '--'
+    },
+    {
+      prop: 'asnItemId',
+      label: 'ASN鏄庣粏ID',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.asnItemId || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.trackCode || '--'
+    },
+    {
+      prop: 'typeText',
+      label: '璁㈠崟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.typeText || '--'
+    },
+    {
+      prop: 'wkTypeText',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.wkTypeText || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.workQty ?? '--'
+    },
+    {
+      prop: 'qty',
+      label: '宸插畬鎴愭暟閲�',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.qty ?? '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'isptResultText',
+      label: '璐ㄦ缁撴灉',
+      minWidth: 110,
+      showOverflowTooltip: true,
+      formatter: (row) => row.isptResultText || '--'
+    },
+    {
+      prop: 'extendFieldsText',
+      label: '鎵╁睍瀛楁',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.extendFieldsText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getWaitPakinItemStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 96,
+      fixed: 'right',
+      align: 'center',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-log/index.vue b/rsf-design/src/views/orders/wait-pakin-log/index.vue
new file mode 100644
index 0000000..b543b16
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-log/index.vue
@@ -0,0 +1,339 @@
+<template>
+  <div class="wait-pakin-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ListExportPrint
+              :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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WaitPakinLogDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="loadDetailResources"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  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 {
+    fetchExportWaitPakinLogReport,
+    fetchGetWaitPakinLogDetail,
+    fetchGetWaitPakinLogMany,
+    fetchWaitPakinItemLogPage,
+    fetchWaitPakinLogPage
+  } from '@/api/wait-pakin-log'
+  import WaitPakinLogDetailDrawer from './modules/wait-pakin-log-detail-drawer.vue'
+  import {
+    createWaitPakinItemLogColumns,
+    createWaitPakinLogTableColumns
+  } from './waitPakinLogTable.columns'
+  import {
+    WAIT_PAKIN_LOG_REPORT_STYLE,
+    WAIT_PAKIN_LOG_REPORT_TITLE,
+    buildWaitPakinLogDetailQueryParams,
+    buildWaitPakinLogPageQueryParams,
+    buildWaitPakinLogPrintRows,
+    buildWaitPakinLogReportMeta,
+    buildWaitPakinLogSearchParams,
+    createWaitPakinLogSearchState,
+    getWaitPakinIoStatusOptions,
+    normalizeWaitPakinItemLogRow,
+    normalizeWaitPakinLogRow
+  } from './waitPakinLogPage.helpers'
+
+  defineOptions({ name: 'WaitPakinLog' })
+
+  const userStore = useUserStore()
+  const reportTitle = WAIT_PAKIN_LOG_REPORT_TITLE
+  const searchForm = ref(createWaitPakinLogSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activeLogId = ref(null)
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildWaitPakinLogSearchParams(searchForm.value))
+  const detailColumns = computed(() => createWaitPakinItemLogColumns())
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樼紪鐮�/瀹瑰櫒鐮�'
+      }
+    },
+    {
+      label: '缁勬墭鍗旾D',
+      key: 'pakinId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟ID'
+      }
+    },
+    {
+      label: '缁勬墭缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樼紪鐮�'
+      }
+    },
+    {
+      label: '瀹瑰櫒鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鍣ㄧ爜'
+      }
+    },
+    {
+      label: '缁勬墭鐘舵��',
+      key: 'ioStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWaitPakinIoStatusOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    activeLogId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchWaitPakinLogPage,
+      apiParams: buildWaitPakinLogPageQueryParams(searchForm.value),
+      columnsFactory: () =>
+        createWaitPakinLogTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeWaitPakinLogRow(item)) : []
+    }
+  })
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadDetailResources() {
+    if (!activeLogId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const [detailResponse, itemResponse] = await Promise.all([
+        guardRequestWithMessage(
+          fetchGetWaitPakinLogDetail(activeLogId.value),
+          {},
+          {
+            timeoutMessage: '缁勬墭鍘嗗彶璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+          }
+        ),
+        guardRequestWithMessage(
+          fetchWaitPakinItemLogPage(
+            buildWaitPakinLogDetailQueryParams({
+              logId: activeLogId.value,
+              current: detailPagination.current,
+              pageSize: detailPagination.size
+            })
+          ),
+          {
+            records: [],
+            total: 0,
+            current: detailPagination.current,
+            size: detailPagination.size
+          },
+          {
+            timeoutMessage: '缁勬墭鍘嗗彶鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+          }
+        )
+      ])
+
+      detailData.value = normalizeWaitPakinLogRow(detailResponse)
+      detailTableData.value = Array.isArray(itemResponse?.records)
+        ? itemResponse.records.map((item) => normalizeWaitPakinItemLogRow(item))
+        : []
+      updatePaginationState(
+        detailPagination,
+        itemResponse,
+        detailPagination.current,
+        detailPagination.size
+      )
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailTableData.value = []
+      ElMessage.error(error?.message || '鑾峰彇缁勬墭鍘嗗彶璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildWaitPakinLogSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createWaitPakinLogSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'wait-pakin-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaitPakinLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetWaitPakinLogMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchWaitPakinLogPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize:
+            Number(pagination.total) > 0
+              ? Number(pagination.total)
+              : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildWaitPakinLogPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...WAIT_PAKIN_LOG_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaitPakinLogReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || WAIT_PAKIN_LOG_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-log/modules/wait-pakin-log-detail-drawer.vue b/rsf-design/src/views/orders/wait-pakin-log/modules/wait-pakin-log-detail-drawer.vue
new file mode 100644
index 0000000..d6eef8a
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-log/modules/wait-pakin-log-detail-drawer.vue
@@ -0,0 +1,59 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缁勬墭鍘嗗彶妗h鎯�"
+    size="88%"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="4" border>
+        <ElDescriptionsItem label="缁勬墭鍗旾D">{{ detail.pakinId ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="缁勬墭缂栫爜">{{ detail.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀹瑰櫒鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="缁勬墭鐘舵��">{{ detail.ioStatusText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="缁勬墭鏁伴噺">{{ detail.anfme ?? 0 }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="$emit('size-change', $event)"
+        @pagination:current-change="$emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'WaitPakinLogDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogPage.helpers.js b/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogPage.helpers.js
new file mode 100644
index 0000000..7489fa2
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogPage.helpers.js
@@ -0,0 +1,165 @@
+export const WAIT_PAKIN_LOG_REPORT_TITLE = '缁勬墭鍘嗗彶妗f姤琛�'
+export const WAIT_PAKIN_LOG_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const IO_STATUS_MAP = {
+  0: { label: '寰呭叆搴�', tagType: 'info' },
+  1: { label: '鍏ュ簱涓�', tagType: 'warning' },
+  2: { label: '浠诲姟鎵ц涓�', tagType: 'warning' },
+  3: { label: '浠诲姟瀹屾垚', tagType: 'success' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function getIoStatusConfig(status, statusText) {
+  const numericStatus = Number(status)
+  const fallback = IO_STATUS_MAP[numericStatus] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+export function createWaitPakinLogSearchState() {
+  return {
+    condition: '',
+    pakinId: '',
+    code: '',
+    barcode: '',
+    ioStatus: ''
+  }
+}
+
+export function buildWaitPakinLogSearchParams(params = {}) {
+  const result = {}
+
+  ;['condition', 'code', 'barcode'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.pakinId !== '' && params.pakinId !== undefined && params.pakinId !== null) {
+    result.pakinId = Number(params.pakinId)
+  }
+
+  if (params.ioStatus !== '' && params.ioStatus !== undefined && params.ioStatus !== null) {
+    result.ioStatus = Number(params.ioStatus)
+  }
+
+  return result
+}
+
+export function buildWaitPakinLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaitPakinLogSearchParams(params)
+  }
+}
+
+export function buildWaitPakinLogDetailQueryParams(params = {}) {
+  return {
+    logId: params.logId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeWaitPakinLogRow(record = {}) {
+  const ioStatusConfig = getIoStatusConfig(record.ioStatus, record['ioStatus$'])
+  return {
+    ...record,
+    id: record.id ?? null,
+    pakinId: record.pakinId ?? '-',
+    code: record.code || '-',
+    barcode: record.barcode || '-',
+    anfme: normalizeNumber(record.anfme),
+    ioStatusText: ioStatusConfig.label,
+    ioStatusTagType: ioStatusConfig.tagType,
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    statusText:
+      record.statusBool === true || Number(record.status) === 1
+        ? '姝e父'
+        : record.statusBool === false || Number(record.status) === 0
+          ? '鍐荤粨'
+          : '-',
+    memo: record.memo || '-'
+  }
+}
+
+export function normalizeWaitPakinItemLogRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? null,
+    pakinId: record.pakinId ?? '-',
+    pakinItemId: record.pakinItemId ?? '-',
+    asnId: record.asnId ?? '-',
+    asnCode: record.asnCode || '-',
+    asnItemId: record.asnItemId ?? '-',
+    trackCode: record.trackCode || '-',
+    maktx: record.maktx || '-',
+    matnrCode: record.matnrCode || '-',
+    anfme: normalizeNumber(record.anfme),
+    workQty: normalizeNumber(record.workQty),
+    unit: record.unit || '-',
+    qty: normalizeNumber(record.qty),
+    batch: record.batch || '-',
+    updateByText: record['updateBy$'] || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function buildWaitPakinLogPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaitPakinLogRow(record))
+}
+
+export function buildWaitPakinLogReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAIT_PAKIN_LOG_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAIT_PAKIN_LOG_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAIT_PAKIN_LOG_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getWaitPakinIoStatusOptions() {
+  return Object.entries(IO_STATUS_MAP).map(([value, item]) => ({
+    label: item.label,
+    value: Number(value)
+  }))
+}
diff --git a/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogTable.columns.js b/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogTable.columns.js
new file mode 100644
index 0000000..0659cd5
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin-log/waitPakinLogTable.columns.js
@@ -0,0 +1,137 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createWaitPakinLogTableColumns({ handleView }) {
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'pakinId',
+      label: '缁勬墭鍗旾D',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'code',
+      label: '缁勬墭缂栫爜',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'barcode',
+      label: '瀹瑰櫒鐮�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '缁勬墭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'ioStatusText',
+      label: '缁勬墭鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(ElTag, { type: row.ioStatusTagType || 'info', effect: 'light' }, () => row.ioStatusText)
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView(row)
+        })
+    }
+  ]
+}
+
+export function createWaitPakinItemLogColumns() {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'asnCode',
+      label: 'ASN鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'trackCode',
+      label: '璺熻釜鐮�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸插畬鎴愭暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100
+    },
+    {
+      prop: 'batch',
+      label: '鎵规鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wait-pakin/index.vue b/rsf-design/src/views/orders/wait-pakin/index.vue
new file mode 100644
index 0000000..43bf924
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin/index.vue
@@ -0,0 +1,381 @@
+<template>
+  <div class="wait-pakin-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton
+              type="primary"
+              :disabled="generatableSelectedRows.length === 0"
+              @click="openGenerateTaskDialog(generatableSelectedRows)"
+              v-ripple
+            >
+              鐢熸垚浠诲姟
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <WaitPakinDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :items-loading="detailItemsLoading"
+        :detail="detailData"
+        :item-rows="detailItemRows"
+        :item-columns="waitPakinItemColumns"
+      />
+
+      <WaitPakinSiteDialog
+        v-model:visible="siteDialogVisible"
+        :selected-rows="taskSourceRows"
+        @select="handleSelectSite"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    fetchDeleteWaitPakin,
+    fetchExportWaitPakinReport,
+    fetchMergeWaitPakinTasks,
+    fetchWaitPakinDetail,
+    fetchWaitPakinItemPage,
+    fetchWaitPakinMany,
+    fetchWaitPakinPage
+  } from '@/api/wait-pakin'
+  import WaitPakinDetailDrawer from './modules/wait-pakin-detail-drawer.vue'
+  import WaitPakinSiteDialog from './modules/wait-pakin-site-dialog.vue'
+  import { createWaitPakinTableColumns } from './waitPakinTable.columns'
+  import {
+    WAIT_PAKIN_REPORT_STYLE,
+    WAIT_PAKIN_REPORT_TITLE,
+    buildWaitPakinMergePayload,
+    buildWaitPakinPageQueryParams,
+    buildWaitPakinPrintRows,
+    buildWaitPakinReportMeta,
+    buildWaitPakinSearchParams,
+    createWaitPakinItemColumns,
+    createWaitPakinSearchState,
+    getWaitPakinIoStatusOptions,
+    getWaitPakinPaginationKey,
+    getWaitPakinStatusOptions,
+    normalizeWaitPakinDetailRecord,
+    normalizeWaitPakinItemRow,
+    normalizeWaitPakinListRow
+  } from './waitPakinPage.helpers'
+
+  defineOptions({ name: 'WaitPakin' })
+
+  const userStore = useUserStore()
+
+  const reportTitle = WAIT_PAKIN_REPORT_TITLE
+  const searchForm = ref(createWaitPakinSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailItemsLoading = ref(false)
+  const detailData = ref({})
+  const detailItemRows = ref([])
+  const waitPakinItemColumns = createWaitPakinItemColumns()
+  const siteDialogVisible = ref(false)
+  const taskSourceRows = ref([])
+
+  const reportQueryParams = computed(() => buildWaitPakinSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟鍙�/瀹瑰櫒鐮�'
+      }
+    },
+    {
+      label: '缁勬墭鍗曞彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粍鎵樺崟鍙�'
+      }
+    },
+    {
+      label: '瀹瑰櫒鐮�',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鍣ㄧ爜'
+      }
+    },
+    {
+      label: '缁勬墭鐘舵��',
+      key: 'ioStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWaitPakinIoStatusOptions()
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getWaitPakinStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  const canGenerateTask = (row) => Number(row?.ioStatus) === 1
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshRemove,
+    refreshUpdate
+  } = useTable({
+    core: {
+      apiFn: fetchWaitPakinPage,
+      apiParams: buildWaitPakinPageQueryParams(searchForm.value),
+      paginationKey: getWaitPakinPaginationKey(),
+      columnsFactory: () =>
+        createWaitPakinTableColumns({
+          handleView: openDetail,
+          handleDelete,
+          handleGenerateTask: openGenerateTaskDialog,
+          canDelete: true,
+          canGenerateTask
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeWaitPakinListRow(item))
+      }
+    }
+  })
+
+  const generatableSelectedRows = computed(() =>
+    selectedRows.value.filter((row) => canGenerateTask(row))
+  )
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  async function loadDetailItems(pakinId) {
+    detailItemsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWaitPakinItemPage({ pakinId, current: 1, pageSize: 200 }),
+        { records: [] },
+        { timeoutMessage: '缁勬墭鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      detailItemRows.value = defaultResponseAdapter(response).records.map((item) =>
+        normalizeWaitPakinItemRow(item)
+      )
+    } finally {
+      detailItemsLoading.value = false
+    }
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    detailItemRows.value = []
+    try {
+      const detail = await guardRequestWithMessage(
+        fetchWaitPakinDetail(row.id),
+        {},
+        { timeoutMessage: '缁勬墭鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+      detailData.value = normalizeWaitPakinDetailRecord(detail)
+      await loadDetailItems(row.id)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      detailItemRows.value = []
+      ElMessage.error(error?.message || '鑾峰彇缁勬墭鍗曡鎯呭け璐�')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(
+        `纭畾瑕佸垹闄ょ粍鎵樺崟銆�${row.code || row.barcode || row.id}銆嶅悧锛焋,
+        '鍒犻櫎纭',
+        {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        }
+      )
+      await fetchDeleteWaitPakin(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  function openGenerateTaskDialog(rows) {
+    if (!Array.isArray(rows) || rows.length === 0) {
+      ElMessage.warning('璇峰厛閫夋嫨鍙敓鎴愪换鍔$殑缁勬墭鍗�')
+      return
+    }
+    const validRows = rows.filter((row) => canGenerateTask(row))
+    if (validRows.length === 0) {
+      ElMessage.warning('浠呪�滃叆搴撲腑鈥濈殑缁勬墭鍗曟敮鎸佺敓鎴愪换鍔�')
+      return
+    }
+    taskSourceRows.value = validRows
+    siteDialogVisible.value = true
+  }
+
+  async function handleSelectSite(siteRow) {
+    try {
+      await fetchMergeWaitPakinTasks(buildWaitPakinMergePayload(taskSourceRows.value, siteRow.id))
+      ElMessage.success('鐢熸垚浠诲姟鎴愬姛')
+      siteDialogVisible.value = false
+      taskSourceRows.value = []
+      selectedRows.value = []
+      await refreshUpdate()
+    } catch (error) {
+      ElMessage.error(error?.message || '鐢熸垚浠诲姟澶辫触')
+    }
+  }
+
+  const buildPreviewDialogMeta = (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
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    const response =
+      Array.isArray(payload?.ids) && payload.ids.length > 0
+        ? await fetchWaitPakinMany(payload.ids)
+        : await fetchWaitPakinPage({
+            ...reportQueryParams.value,
+            current: 1,
+            pageSize:
+              Number(pagination.total) > 0
+                ? Number(pagination.total)
+                : Number(payload?.pageSize) || 20
+          })
+    return defaultResponseAdapter(response).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'wait-pakin.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaitPakinReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWaitPakinPrintRows(records),
+    buildPreviewMeta: buildPreviewDialogMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaitPakinReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation:
+        previewMeta.value?.reportStyle?.orientation || WAIT_PAKIN_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildWaitPakinSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWaitPakinSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-detail-drawer.vue b/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-detail-drawer.vue
new file mode 100644
index 0000000..e154b62
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-detail-drawer.vue
@@ -0,0 +1,78 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缁勬墭鍗曡鎯�"
+    size="1180px"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="12" animated />
+      </div>
+      <div v-else class="space-y-4">
+        <ElDescriptions title="鍩虹淇℃伅" :column="2" border>
+          <ElDescriptionsItem label="缁勬墭鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹瑰櫒鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁勬墭鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁勬墭鐘舵��">{{
+            detail.ioStatusText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏄惁涓嶈壇鍝�">{{
+            detail.flagDefectText || '--'
+          }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">
+              {{ detail.statusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <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>
+            <ElTag effect="plain">鍏� {{ itemRows.length }} 鏉�</ElTag>
+          </div>
+          <ArtTable :data="itemRows" :columns="itemColumns" :loading="itemsLoading" />
+        </div>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+  import ArtTable from '@/components/core/tables/art-table/index.vue'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    itemsLoading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    itemRows: { type: Array, default: () => [] },
+    itemColumns: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visible = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+
+  function handleVisibleChange(value) {
+    visible.value = value
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-site-dialog.vue b/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-site-dialog.vue
new file mode 100644
index 0000000..196b61b
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin/modules/wait-pakin-site-dialog.vue
@@ -0,0 +1,189 @@
+<template>
+  <ElDialog
+    title="閫夋嫨绔欑偣"
+    :model-value="visible"
+    width="1080px"
+    align-center
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="space-y-4">
+      <div
+        class="rounded border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-4 py-3 text-sm"
+      >
+        宸查�夌粍鎵樺崟 {{ selectedRows.length }} 鏉�
+      </div>
+
+      <ArtSearchBar
+        v-model="searchForm"
+        :items="searchItems"
+        @search="handleSearch"
+        @reset="handleReset"
+      />
+
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columnsWithButton"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </div>
+  </ElDialog>
+</template>
+
+<script setup>
+  import { computed, h, ref, watch } from 'vue'
+  import { ElButton } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchDeviceSitePage } from '@/api/device-site'
+  import {
+    buildWaitPakinSiteSearchParams,
+    createWaitPakinSiteSearchState,
+    normalizeWaitPakinSiteRow
+  } from '../waitPakinPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    selectedRows: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'select'])
+
+  const searchForm = ref(createWaitPakinSiteSearchState())
+
+  const searchItems = computed(() => [
+    {
+      label: '绔欑偣鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐瑰悕绉�'
+      }
+    },
+    {
+      label: '绔欑偣',
+      key: 'site',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ珯鐐�'
+      }
+    },
+    {
+      label: '璁惧浣�',
+      key: 'deviceSite',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ澶囦綅'
+      }
+    }
+  ])
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchDeviceSitePage,
+      apiParams: {
+        current: 1,
+        pageSize: 20,
+        type: 1
+      },
+      paginationKey: {
+        current: 'current',
+        size: 'pageSize'
+      },
+      columnsFactory: () => [
+        { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+        { prop: 'name', label: '绔欑偣鍚嶇О', minWidth: 140, showOverflowTooltip: true },
+        { prop: 'site', label: '绔欑偣', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'deviceSite', label: '璁惧浣�', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'wcsCode', label: 'WCS缂栫爜', minWidth: 140, showOverflowTooltip: true },
+        { prop: 'label', label: '鏍囩', minWidth: 120, showOverflowTooltip: true },
+        { prop: 'updateTimeText', label: '鏇存柊鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+        {
+          prop: 'operation',
+          label: '鎿嶄綔',
+          width: 110,
+          align: 'center',
+          formatter: (row) => ({
+            label: '閫夋嫨',
+            type: 'primary',
+            plain: true,
+            onClick: () => emit('select', row)
+          })
+        }
+      ]
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeWaitPakinSiteRow(item))
+      }
+    }
+  })
+
+  const columnsWithButton = computed(() =>
+    columns.value.map((column) => {
+      if (column.prop !== 'operation') {
+        return column
+      }
+      return {
+        ...column,
+        formatter: (row) => {
+          const action = column.formatter(row)
+          return h(
+            ElButton,
+            {
+              type: action.type,
+              plain: action.plain,
+              onClick: action.onClick
+            },
+            () => action.label
+          )
+        }
+      }
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildWaitPakinSiteSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createWaitPakinSiteSearchState())
+    resetSearchParams()
+  }
+
+  function handleVisibleChange(value) {
+    emit('update:visible', value)
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        getData()
+      }
+    }
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wait-pakin/waitPakinPage.helpers.js b/rsf-design/src/views/orders/wait-pakin/waitPakinPage.helpers.js
new file mode 100644
index 0000000..9c89d6e
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin/waitPakinPage.helpers.js
@@ -0,0 +1,253 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success', bool: true },
+  0: { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+const IO_STATUS_OPTIONS = [
+  { label: '寰呭叆搴�', value: 0 },
+  { label: '鍏ュ簱涓�', value: 1 },
+  { label: '浠诲姟鎵ц涓�', value: 2 },
+  { label: '浠诲姟瀹屾垚', value: 3 }
+]
+
+export const WAIT_PAKIN_REPORT_TITLE = '缁勬墭鍗曟姤琛�'
+export const WAIT_PAKIN_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'portrait',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+function normalizeObjectEntries(record = {}) {
+  return Object.fromEntries(
+    Object.entries(record)
+      .filter(([, value]) => value !== undefined && value !== null && value !== '')
+      .map(([key, value]) => [key, normalizeText(value)])
+  )
+}
+
+export function createWaitPakinSearchState() {
+  return {
+    condition: '',
+    code: '',
+    barcode: '',
+    ioStatus: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function getWaitPakinPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWaitPakinStatusOptions() {
+  return [
+    { label: '姝e父', value: 1 },
+    { label: '鍐荤粨', value: 0 }
+  ]
+}
+
+export function getWaitPakinIoStatusOptions() {
+  return IO_STATUS_OPTIONS
+}
+
+export function getWaitPakinStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return STATUS_META[1]
+  }
+  if (status === false || Number(status) === 0) {
+    return STATUS_META[0]
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildWaitPakinSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    barcode: normalizeText(params.barcode),
+    ioStatus:
+      params.ioStatus !== undefined && params.ioStatus !== null && params.ioStatus !== ''
+        ? normalizeNumber(params.ioStatus)
+        : void 0,
+    status:
+      params.status !== undefined && params.status !== null && params.status !== ''
+        ? normalizeNumber(params.status)
+        : void 0,
+    memo: normalizeText(params.memo)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(
+      ([, value]) => value !== '' && value !== void 0 && value !== null
+    )
+  )
+}
+
+export function buildWaitPakinPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaitPakinSearchParams(params)
+  }
+}
+
+export function normalizeWaitPakinDetailRecord(record = {}) {
+  const statusMeta = getWaitPakinStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    code: normalizeText(record.code) || '--',
+    barcode: normalizeText(record.barcode) || '--',
+    anfme: record.anfme ?? '--',
+    ioStatus: record.ioStatus ?? void 0,
+    ioStatusText: normalizeText(record.ioStatus$ || record.ioStatusText) || '--',
+    flagDefectText:
+      record.flagDefect === 1 || record.flagDefect === '1'
+        ? '鏄�'
+        : record.flagDefect === 0 || record.flagDefect === '0'
+          ? '鍚�'
+          : '--',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool !== void 0 ? Boolean(record.statusBool) : statusMeta.bool,
+    memo: normalizeText(record.memo) || '--',
+    createByText: normalizeText(record.createBy$ || record.createByText || '') || '--',
+    createTimeText: normalizeText(record.createTime$ || record.createTime || '') || '--',
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || '') || '--',
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '') || '--'
+  }
+}
+
+export function normalizeWaitPakinListRow(record = {}) {
+  return normalizeWaitPakinDetailRecord(record)
+}
+
+export function normalizeWaitPakinItemRow(record = {}) {
+  const extendFields =
+    record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  return {
+    ...record,
+    pakinId: record.pakinId ?? '--',
+    maktx: normalizeText(record.maktx) || '--',
+    matnrId: record.matnrId ?? '--',
+    matnrCode: normalizeText(record.matnrCode) || '--',
+    asnCode: normalizeText(record.asnCode) || '--',
+    trackCode: normalizeText(record.trackCode) || '--',
+    anfme: record.anfme ?? '--',
+    workQty: record.workQty ?? '--',
+    unit: normalizeText(record.unit) || '--',
+    qty: record.qty ?? '--',
+    batch: normalizeText(record.batch) || '--',
+    isptResultText: normalizeText(record.isptResult$ || record.isptResultText) || '--',
+    memo: normalizeText(record.memo) || '--',
+    extendFieldsText: Object.keys(extendFields).length
+      ? Object.entries(extendFields)
+          .map(([key, value]) => `${normalizeText(key)}:${normalizeText(value)}`)
+          .join('锛�')
+      : '--'
+  }
+}
+
+export function buildWaitPakinPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaitPakinListRow(record))
+}
+
+export function buildWaitPakinReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAIT_PAKIN_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAIT_PAKIN_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAIT_PAKIN_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function createWaitPakinItemColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 70, align: 'center' },
+    { prop: 'maktx', label: '鐗╂枡鍚嶇О', minWidth: 180, showOverflowTooltip: true },
+    { prop: 'matnrCode', label: '鐗╂枡缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'asnCode', label: 'ASN鍗曞彿', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'trackCode', label: '璺熻釜鐮�', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'anfme', label: '鏁伴噺', width: 100, align: 'right' },
+    { prop: 'workQty', label: '鎵ц涓暟閲�', width: 110, align: 'right' },
+    { prop: 'qty', label: '宸插畬鎴愭暟閲�', width: 110, align: 'right' },
+    { prop: 'unit', label: '鍗曚綅', width: 90, align: 'center' },
+    { prop: 'batch', label: '鎵规鍙�', minWidth: 130, showOverflowTooltip: true },
+    { prop: 'isptResultText', label: '璐ㄦ缁撴灉', minWidth: 100, showOverflowTooltip: true },
+    { prop: 'extendFieldsText', label: '鎵╁睍瀛楁', minWidth: 220, showOverflowTooltip: true }
+  ]
+}
+
+export function buildWaitPakinMergePayload(rows = [], siteId) {
+  return {
+    waitPakins: rows
+      .map((item) => {
+        if (item?.id === undefined || item?.id === null || item?.id === '') {
+          return null
+        }
+        return {
+          id: Number(item.id)
+        }
+      })
+      .filter(Boolean),
+    siteId: Number(siteId)
+  }
+}
+
+export function createWaitPakinSiteSearchState() {
+  return {
+    name: '',
+    site: '',
+    deviceSite: ''
+  }
+}
+
+export function buildWaitPakinSiteSearchParams(params = {}) {
+  return normalizeObjectEntries({
+    type: 1,
+    name: params.name,
+    site: params.site,
+    deviceSite: params.deviceSite
+  })
+}
+
+export function normalizeWaitPakinSiteRow(record = {}) {
+  return {
+    ...record,
+    name: normalizeText(record.name) || '--',
+    site: normalizeText(record.site) || '--',
+    deviceSite: normalizeText(record.deviceSite) || '--',
+    wcsCode: normalizeText(record.wcsCode) || '--',
+    label: normalizeText(record.label) || '--',
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || '') || '--'
+  }
+}
diff --git a/rsf-design/src/views/orders/wait-pakin/waitPakinTable.columns.js b/rsf-design/src/views/orders/wait-pakin/waitPakinTable.columns.js
new file mode 100644
index 0000000..2b278ba
--- /dev/null
+++ b/rsf-design/src/views/orders/wait-pakin/waitPakinTable.columns.js
@@ -0,0 +1,122 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getWaitPakinStatusMeta } from './waitPakinPage.helpers'
+
+export function createWaitPakinTableColumns({
+  handleView,
+  handleDelete,
+  handleGenerateTask,
+  canDelete = true,
+  canGenerateTask = () => true
+} = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '缁勬墭鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.code || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '瀹瑰櫒鐮�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '缁勬墭鏁伴噺',
+      width: 110,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'ioStatusText',
+      label: '缁勬墭鐘舵��',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.ioStatusText || '--'
+    },
+    {
+      prop: 'flagDefectText',
+      label: '涓嶈壇鍝�',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.flagDefectText || '--'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const statusMeta = getWaitPakinStatusMeta(row.statusBool ?? row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 180,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) => {
+        const operations = [{ key: 'view', label: '璇︽儏', icon: 'ri:eye-line' }]
+
+        if (handleGenerateTask && canGenerateTask(row)) {
+          operations.push({ key: 'generate', label: '鐢熸垚浠诲姟', icon: 'ri:play-list-add-line' })
+        }
+
+        if (canDelete && handleDelete) {
+          operations.push({
+            key: 'delete',
+            label: '鍒犻櫎',
+            icon: 'ri:delete-bin-5-line',
+            color: 'var(--art-error)'
+          })
+        }
+
+        return h(ArtButtonMore, {
+          list: operations,
+          onClick: (item) => {
+            if (item.key === 'view') handleView?.(row)
+            if (item.key === 'generate') handleGenerateTask?.([row])
+            if (item.key === 'delete') handleDelete?.(row)
+          }
+        })
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wave-item/index.vue b/rsf-design/src/views/orders/wave-item/index.vue
new file mode 100644
index 0000000..0271f9a
--- /dev/null
+++ b/rsf-design/src/views/orders/wave-item/index.vue
@@ -0,0 +1,202 @@
+<template>
+  <div class="wave-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WaveItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    buildWaveItemPageQueryParams,
+    buildWaveItemPrintRows,
+    buildWaveItemReportMeta,
+    buildWaveItemSearchParams,
+    createWaveItemSearchState,
+    normalizeWaveItemRow
+  } from './waveItemPage.helpers'
+  import { createWaveItemDetailColumns, createWaveItemTableColumns } from './waveItemTable.columns'
+  import { fetchExportWaveItemReport, fetchGetWaveItemDetail, fetchGetWaveItemMany, fetchWaveItemPage } from '@/api/wave-item'
+  import WaveItemDetailDrawer from './modules/wave-item-detail-drawer.vue'
+
+  defineOptions({ name: 'WaveItemOrder' })
+
+  const userStore = useUserStore()
+  const reportTitle = '娉㈡鏄庣粏鎶ヨ〃'
+  const searchForm = ref(createWaveItemSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const activeItemId = ref(null)
+
+  const reportQueryParams = computed(() => buildWaveItemSearchParams(searchForm.value))
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ尝娆″崟鍙�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О' } },
+    { label: '娉㈡鍗曞彿', key: 'waveCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ尝娆″崟鍙�' } },
+    { label: '鍗曟嵁缂栫爜', key: 'orderCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ崟鎹紪鐮�' } },
+    { label: '鐗╂枡缂栫爜', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' } },
+    { label: '鐗╂枡鍚嶇О', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' } },
+    { label: '鎵规', key: 'batch', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' } },
+    { label: '渚涘簲鍟嗘壒娆�', key: 'splrBatch', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规' } },
+    { label: '鍔ㄦ�佸瓧娈电储寮�', key: 'fieldsIndex', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ姩鎬佸瓧娈电储寮�' } },
+    { label: '寮�濮嬫椂闂�', key: 'timeStart', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
+    { label: '缁撴潫鏃堕棿', key: 'timeEnd', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } }
+  ])
+
+  async function openDetail(row) {
+    activeItemId.value = row.id
+    detailDrawerVisible.value = true
+    await loadDetailResource()
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchWaveItemPage,
+      apiParams: buildWaveItemPageQueryParams({
+        ...searchForm.value,
+        pageSize: 20
+      }),
+      columnsFactory: () => createWaveItemTableColumns({ handleActionClick: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeWaveItemRow(item)) : [])
+    }
+  })
+
+  async function loadDetailResource() {
+    if (!activeItemId.value) {
+      return
+    }
+
+    const detailResponse = await guardRequestWithMessage(fetchGetWaveItemDetail(activeItemId.value), {}, { timeoutMessage: '娉㈡鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' })
+    detailData.value = normalizeWaveItemRow(detailResponse)
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildWaveItemPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createWaveItemSearchState()
+    resetSearchParams()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetWaveItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchWaveItemPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta: rawPreviewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'wave-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaveItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWaveItemPrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          titleAlign: 'center',
+          titleLevel: 'strong',
+          orientation: 'landscape',
+          density: 'compact',
+          showSequence: true
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaveItemReportMeta({
+      previewMeta: rawPreviewMeta.value,
+      count: previewRows.value.length,
+      orientation: rawPreviewMeta.value?.reportStyle?.orientation || 'landscape'
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wave-item/modules/wave-item-detail-drawer.vue b/rsf-design/src/views/orders/wave-item/modules/wave-item-detail-drawer.vue
new file mode 100644
index 0000000..a065dcc
--- /dev/null
+++ b/rsf-design/src/views/orders/wave-item/modules/wave-item-detail-drawer.vue
@@ -0,0 +1,55 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娉㈡鏄庣粏璇︽儏"
+    size="80%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="wave-item-detail-scroll">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="娉㈡鍗曞彿">{{ detail.waveCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁缂栫爜">{{ detail.orderCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍔ㄦ�佸瓧娈电储寮�">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴旈厤鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸查厤鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撳瓨鏁伴噺">{{ detail.stockQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鐘舵��">
+            <ElTag :type="detail.exceStatusTagType || 'info'" effect="light">
+              {{ detail.exceStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅" :span="4">{{ detail.stockLocsText || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'WaveItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
+
+<style scoped>
+  .wave-item-detail-scroll {
+    height: calc(100vh - 120px);
+  }
+</style>
diff --git a/rsf-design/src/views/orders/wave-item/waveItemPage.helpers.js b/rsf-design/src/views/orders/wave-item/waveItemPage.helpers.js
new file mode 100644
index 0000000..a8f0500
--- /dev/null
+++ b/rsf-design/src/views/orders/wave-item/waveItemPage.helpers.js
@@ -0,0 +1,165 @@
+export const WAVE_ITEM_REPORT_TITLE = '娉㈡鏄庣粏鎶ヨ〃'
+export const WAVE_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+const WAVE_ITEM_STATUS_MAP = {
+  0: { label: '鏈墽琛�', tagType: 'info' },
+  1: { label: '鎵ц涓�', tagType: 'warning' },
+  2: { label: '鏆傚仠', tagType: 'warning' },
+  3: { label: '宸蹭笅鍙�', tagType: 'primary' },
+  4: { label: '浠诲姟瀹屾垚', tagType: 'success' }
+}
+
+function getItemStatusConfig(status, statusText) {
+  const fallback = WAVE_ITEM_STATUS_MAP[Number(status)] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+function normalizeStockLocs(stockLocs) {
+  if (!stockLocs) {
+    return '[]'
+  }
+  if (typeof stockLocs === 'string') {
+    return stockLocs
+  }
+  try {
+    return JSON.stringify(stockLocs)
+  } catch {
+    return '[]'
+  }
+}
+
+export function createWaveItemSearchState() {
+  return {
+    condition: '',
+    waveId: '',
+    waveCode: '',
+    orderCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    splrBatch: '',
+    unit: '',
+    fieldsIndex: '',
+    status: '',
+    exceStatus: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildWaveItemSearchParams(params = {}) {
+  const result = {}
+  ;[
+    'condition',
+    'waveCode',
+    'orderCode',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'splrBatch',
+    'unit',
+    'fieldsIndex',
+    'timeStart',
+    'timeEnd'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.waveId !== '' && params.waveId !== undefined && params.waveId !== null) {
+    result.waveId = Number(params.waveId)
+  }
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+
+  return result
+}
+
+export function buildWaveItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaveItemSearchParams(params)
+  }
+}
+
+export function normalizeWaveItemRow(record = {}) {
+  const statusConfig = getItemStatusConfig(record.exceStatus, record['exceStatus$'])
+  return {
+    ...record,
+    waveCode: record.waveCode || '-',
+    orderCode: record.orderCode || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    batch: record.batch || '-',
+    splrBatch: record.splrBatch || '-',
+    unit: record.unit || '-',
+    fieldsIndex: record.fieldsIndex || '-',
+    anfme: normalizeNumber(record.anfme),
+    workQty: normalizeNumber(record.workQty),
+    qty: normalizeNumber(record.qty),
+    stockQty: normalizeNumber(record.stockQty),
+    stockLocsText: normalizeStockLocs(record.stockLocs),
+    exceStatusText: statusConfig.label,
+    exceStatusTagType: statusConfig.tagType,
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-'
+  }
+}
+
+export function buildWaveItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaveItemRow(record))
+}
+
+export function buildWaveItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAVE_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAVE_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAVE_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/orders/wave-item/waveItemTable.columns.js b/rsf-design/src/views/orders/wave-item/waveItemTable.columns.js
new file mode 100644
index 0000000..5851c75
--- /dev/null
+++ b/rsf-design/src/views/orders/wave-item/waveItemTable.columns.js
@@ -0,0 +1,180 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+
+export function createWaveItemTableColumns({ handleActionClick }) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'waveCode',
+      label: '娉㈡鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orderCode',
+      label: '鍗曟嵁缂栫爜',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旈厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸查厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'stockQty',
+      label: '搴撳瓨鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'fieldsIndex',
+      label: '鍔ㄦ�佸瓧娈电储寮�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'exceStatusText',
+      label: '鎵ц鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.exceStatusTagType || 'info', effect: 'light' },
+          () => row.exceStatusText
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: [
+            {
+              key: 'view',
+              label: '鏌ョ湅璇︽儏',
+              icon: 'ri:eye-line'
+            }
+          ],
+          onClick: (item) => handleActionClick(item, row)
+        })
+    }
+  ]
+}
+
+export function createWaveItemDetailColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'waveCode',
+      label: '娉㈡鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orderCode',
+      label: '鍗曟嵁缂栫爜',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旈厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸查厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'stockQty',
+      label: '搴撳瓨鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'stockLocsText',
+      label: '搴撲綅',
+      minWidth: 220,
+      showOverflowTooltip: true
+    }
+  ]
+}
diff --git a/rsf-design/src/views/orders/wave/index.vue b/rsf-design/src/views/orders/wave/index.vue
new file mode 100644
index 0000000..eaa0a3f
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/index.vue
@@ -0,0 +1,416 @@
+<template>
+  <div class="wave-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            :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"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WaveDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+
+    <WavePublicTaskDialog
+      v-model:visible="publicTaskDialogVisible"
+      :loading="publicTaskLoading"
+      :submit-loading="publicTaskSubmitting"
+      :wave="publicTaskWave"
+      :data="publicTaskRows"
+      :columns="publicTaskColumns"
+      :pagination="publicTaskPagination"
+      :can-submit="publicTaskCanSubmit"
+      @size-change="handlePublicTaskSizeChange"
+      @current-change="handlePublicTaskCurrentChange"
+      @submit="handleSubmitPublicTask"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, reactive, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  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 {
+    buildWaveDetailQueryParams,
+    buildWavePageQueryParams,
+    buildWavePrintRows,
+    buildWaveReportMeta,
+    buildWaveSearchParams,
+    createWaveSearchState,
+    normalizeWaveItemRow,
+    normalizeWaveRow,
+    WAVE_REPORT_STYLE
+  } from './wavePage.helpers'
+  import {
+    createWaveDetailItemColumns,
+    createWavePreviewItemColumns,
+    createWaveTableColumns
+  } from './waveTable.columns'
+  import {
+    fetchContinueWave,
+    fetchExportWaveReport,
+    fetchGetWaveDetail,
+    fetchGetWaveMany,
+    fetchPauseWave,
+    fetchPublicWaveTask,
+    fetchStopWave,
+    fetchWavePage,
+    fetchWavePreviewPage
+  } from '@/api/wave'
+  import WaveDetailDrawer from './modules/wave-detail-drawer.vue'
+  import WavePublicTaskDialog from './modules/wave-public-task-dialog.vue'
+
+  defineOptions({ name: 'WaveOrder' })
+
+  const userStore = useUserStore()
+  const reportTitle = '娉㈡鍗曟姤琛�'
+  const searchForm = ref(createWaveSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const detailTableData = ref([])
+  const activeWaveId = ref(null)
+  const publicTaskDialogVisible = ref(false)
+  const publicTaskLoading = ref(false)
+  const publicTaskSubmitting = ref(false)
+  const publicTaskWave = ref({})
+  const publicTaskRows = ref([])
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const publicTaskPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildWaveSearchParams(searchForm.value))
+  const detailColumns = computed(() => createWaveDetailItemColumns())
+  const publicTaskColumns = computed(() => createWavePreviewItemColumns())
+  const publicTaskCanSubmit = computed(
+    () => publicTaskRows.value.length > 0 && publicTaskRows.value.every((row) => row.stockLocsText && row.stockLocsText !== '[]')
+  )
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ尝娆″崟鍙�/澶囨敞' } },
+    { label: '娉㈡鍗曞彿', key: 'code', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ尝娆″崟鍙�' } },
+    {
+      label: '娉㈡绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: { clearable: true, options: [{ label: '鎵嬪姩', value: 0 }, { label: '鑷姩', value: 1 }] }
+    },
+    {
+      label: '娉㈡鐘舵��',
+      key: 'exceStatus',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '绛夊緟鎵ц', value: 0 },
+          { label: '姝e湪鎵ц', value: 1 },
+          { label: '鏆傚仠鎵ц', value: 2 },
+          { label: '鎵ц瀹屾垚', value: 3 }
+        ]
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: { clearable: true, options: [{ label: '姝e父', value: 1 }, { label: '绂佺敤', value: 0 }] }
+    },
+    { label: '澶囨敞', key: 'memo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ娉�' } },
+    { label: '寮�濮嬫椂闂�', key: 'timeStart', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
+    { label: '缁撴潫鏃堕棿', key: 'timeEnd', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function openDetail(row) {
+    activeWaveId.value = row.id
+    detailPagination.current = 1
+    detailDrawerVisible.value = true
+    await loadDetailResources()
+  }
+
+  async function openPublicTask(row) {
+    publicTaskWave.value = normalizeWaveRow(row)
+    publicTaskPagination.current = 1
+    publicTaskDialogVisible.value = true
+    await loadPublicTaskResources()
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    try {
+      if (action.key === 'view') {
+        await openDetail(row)
+        return
+      }
+      if (action.key === 'print') {
+        await handlePrint({ ids: [row.id], pageSize: 1 })
+        return
+      }
+      if (action.key === 'publicTask') {
+        await openPublicTask(row)
+        return
+      }
+      if (action.key === 'pause') {
+        await fetchPauseWave(row.id)
+        ElMessage.success('娉㈡宸叉殏鍋�')
+        await refreshData()
+        return
+      }
+      if (action.key === 'continue') {
+        await fetchContinueWave(row.id)
+        ElMessage.success('娉㈡宸茬户缁�')
+        await refreshData()
+        return
+      }
+      if (action.key === 'stop') {
+        await ElMessageBox.confirm(`纭畾缁堟娉㈡鍗� ${row.code || ''} 鍚楋紵`, '缁堟纭', {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        })
+        await fetchStopWave(row.id)
+        ElMessage.success('娉㈡宸茬粓姝�')
+        await refreshData()
+      }
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '娉㈡鎿嶄綔澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchWavePage,
+      apiParams: buildWavePageQueryParams(searchForm.value),
+      columnsFactory: () => createWaveTableColumns({ handleActionClick })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeWaveRow(item)) : [])
+    }
+  })
+
+  async function loadDetailResources() {
+    if (!activeWaveId.value) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const [detailResponse, previewResponse] = await Promise.all([
+        guardRequestWithMessage(fetchGetWaveDetail(activeWaveId.value), {}, { timeoutMessage: '娉㈡鍗曡鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }),
+        guardRequestWithMessage(
+          fetchWavePreviewPage(buildWaveDetailQueryParams({ waveId: activeWaveId.value, current: detailPagination.current, pageSize: detailPagination.size })),
+          { records: [], total: 0, current: detailPagination.current, size: detailPagination.size },
+          { timeoutMessage: '娉㈡棰勮鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+        )
+      ])
+      detailData.value = normalizeWaveRow(detailResponse)
+      detailTableData.value = Array.isArray(previewResponse?.records) ? previewResponse.records.map((item) => normalizeWaveItemRow(item)) : []
+      updatePaginationState(detailPagination, previewResponse, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function loadPublicTaskResources() {
+    if (!publicTaskWave.value?.id) {
+      return
+    }
+
+    publicTaskLoading.value = true
+    try {
+      const previewResponse = await guardRequestWithMessage(
+        fetchWavePreviewPage(buildWaveDetailQueryParams({ waveId: publicTaskWave.value.id, current: publicTaskPagination.current, pageSize: publicTaskPagination.size })),
+        { records: [], total: 0, current: publicTaskPagination.current, size: publicTaskPagination.size },
+        { timeoutMessage: '娉㈡涓嬪彂棰勮鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      publicTaskRows.value = Array.isArray(previewResponse?.records) ? previewResponse.records.map((item) => normalizeWaveItemRow(item)) : []
+      updatePaginationState(publicTaskPagination, previewResponse, publicTaskPagination.current, publicTaskPagination.size)
+    } finally {
+      publicTaskLoading.value = false
+    }
+  }
+
+  async function handleSubmitPublicTask() {
+    try {
+      publicTaskSubmitting.value = true
+      const response = await fetchPublicWaveTask({
+        wave: publicTaskWave.value,
+        waveItem: publicTaskRows.value
+      })
+      const result = defaultResponseAdapter(response)
+      if (result?.code !== 200 && result?.success !== true) {
+        throw new Error(result?.message || '娉㈡涓嬪彂澶辫触')
+      }
+      ElMessage.success(result?.message || '娉㈡宸蹭笅鍙�')
+      publicTaskDialogVisible.value = false
+      await refreshData()
+      if (detailDrawerVisible.value && activeWaveId.value === publicTaskWave.value.id) {
+        await loadDetailResources()
+      }
+    } catch (error) {
+      ElMessage.error(error?.message || '娉㈡涓嬪彂澶辫触')
+    } finally {
+      publicTaskSubmitting.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = { ...searchForm.value, ...params }
+    replaceSearchParams(buildWaveSearchParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createWaveSearchState()
+    resetSearchParams()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    loadDetailResources()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailResources()
+  }
+
+  function handlePublicTaskSizeChange(size) {
+    publicTaskPagination.size = size
+    loadPublicTaskResources()
+  }
+
+  function handlePublicTaskCurrentChange(current) {
+    publicTaskPagination.current = current
+    loadPublicTaskResources()
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetWaveMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchWavePage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta: rawPreviewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'wave.xlsx',
+    requestExport: (payload) =>
+      fetchExportWaveReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildWavePrintRows(records),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        reportDate: now.toLocaleDateString('zh-CN'),
+        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
+        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+        count: rows.length,
+        reportStyle: {
+          ...WAVE_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWaveReportMeta({
+      previewMeta: rawPreviewMeta.value,
+      count: previewRows.value.length,
+      orientation: rawPreviewMeta.value?.reportStyle?.orientation || WAVE_REPORT_STYLE.orientation
+    })
+  )
+</script>
diff --git a/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue b/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue
new file mode 100644
index 0000000..3009a49
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/modules/wave-detail-drawer.vue
@@ -0,0 +1,75 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娉㈡鍗曡鎯�"
+    size="88%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="wave-detail-scroll">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="娉㈡鍗曞彿">{{ detail.code || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="娉㈡绫诲瀷">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="娉㈡鐘舵��">{{ detail.exceStatusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="Number(detail.status) === 1 ? 'success' : 'danger'" effect="light">
+              {{ detail.statusLabel || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴旂洏鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸茬洏鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曟嵁鏁伴噺">{{ detail.orderNum ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍝佺被鏁伴噺">{{ detail.groupQty ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣浣嶇疆">{{ detail.targSite || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎸囧畾绔欑偣">{{ detail.stationId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎸囧畾搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElCard shadow="never" class="border border-[var(--art-border-color)]">
+          <template #header>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">娉㈡棰勮鏄庣粏 - 鐗╂枡缂栫爜</div>
+          </template>
+          <ArtTable
+            :loading="loading"
+            :data="data"
+            :columns="columns"
+            :pagination="pagination"
+            @pagination:size-change="$emit('size-change', $event)"
+            @pagination:current-change="$emit('current-change', $event)"
+          />
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'WaveDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
+
+<style scoped>
+  .wave-detail-scroll {
+    height: calc(100vh - 120px);
+  }
+</style>
diff --git a/rsf-design/src/views/orders/wave/modules/wave-public-task-dialog.vue b/rsf-design/src/views/orders/wave/modules/wave-public-task-dialog.vue
new file mode 100644
index 0000000..28969a6
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/modules/wave-public-task-dialog.vue
@@ -0,0 +1,68 @@
+<template>
+  <ElDialog
+    :model-value="visible"
+    title="娉㈡涓嬪彂浠诲姟"
+    width="88%"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex max-h-[calc(100vh-240px)] flex-col gap-4 overflow-hidden">
+      <ElDescriptions :column="4" border>
+        <ElDescriptionsItem label="娉㈡鍗曞彿">{{ wave.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="娉㈡绫诲瀷">{{ wave.typeLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="娉㈡鐘舵��">{{ wave.exceStatusText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵ц鏁伴噺">{{ wave.workQty ?? '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <ElAlert
+        v-if="!canSubmit"
+        type="warning"
+        :title="warningText"
+        :closable="false"
+        show-icon
+      />
+
+      <ElCard shadow="never" class="border border-[var(--art-border-color)] flex-1 min-h-0">
+        <ArtTable
+          :loading="loading"
+          :data="data"
+          :columns="columns"
+          :pagination="pagination"
+          @pagination:size-change="$emit('size-change', $event)"
+          @pagination:current-change="$emit('current-change', $event)"
+        />
+      </ElCard>
+    </div>
+
+    <template #footer>
+      <div class="flex items-center justify-end gap-3">
+        <ElButton @click="handleVisibleChange(false)">鍏抽棴</ElButton>
+        <ElButton type="primary" :loading="submitLoading" :disabled="!canSubmit" @click="$emit('submit')">
+          涓嬪彂浠诲姟
+        </ElButton>
+      </div>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  defineOptions({ name: 'WavePublicTaskDialog' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    submitLoading: { type: Boolean, default: false },
+    wave: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) },
+    canSubmit: { type: Boolean, default: false },
+    warningText: { type: String, default: '娉㈡棰勮鏁版嵁涓嶅彲鐢紝璇峰厛妫�鏌ュ簱浣嶉厤缃�' }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change', 'submit'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/orders/wave/wavePage.helpers.js b/rsf-design/src/views/orders/wave/wavePage.helpers.js
new file mode 100644
index 0000000..1e3c9f6
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/wavePage.helpers.js
@@ -0,0 +1,312 @@
+export const WAVE_REPORT_TITLE = '娉㈡鍗曟姤琛�'
+export const WAVE_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+const WAVE_STATUS_MAP = {
+  0: { label: '绛夊緟鎵ц', tagType: 'info' },
+  1: { label: '姝e湪鎵ц', tagType: 'warning' },
+  2: { label: '鏆傚仠鎵ц', tagType: 'warning' },
+  3: { label: '鎵ц瀹屾垚', tagType: 'success' }
+}
+
+const WAVE_ITEM_STATUS_MAP = {
+  0: { label: '鏈墽琛�', tagType: 'info' },
+  1: { label: '鎵ц涓�', tagType: 'warning' },
+  2: { label: '鏆傚仠', tagType: 'warning' },
+  3: { label: '宸蹭笅鍙�', tagType: 'primary' },
+  4: { label: '浠诲姟瀹屾垚', tagType: 'success' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function getStatusConfig(status, statusText) {
+  const fallback = WAVE_STATUS_MAP[Number(status)] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+function getItemStatusConfig(status, statusText) {
+  const fallback = WAVE_ITEM_STATUS_MAP[Number(status)] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+function normalizeStockLocs(stockLocs) {
+  if (!stockLocs) {
+    return '[]'
+  }
+  if (typeof stockLocs === 'string') {
+    return stockLocs
+  }
+  try {
+    return JSON.stringify(stockLocs)
+  } catch {
+    return '[]'
+  }
+}
+
+export function createWaveSearchState() {
+  return {
+    condition: '',
+    code: '',
+    type: '',
+    exceStatus: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildWaveSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'code', 'memo', 'timeStart', 'timeEnd'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.type !== '' && params.type !== undefined && params.type !== null) {
+    result.type = Number(params.type)
+  }
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+
+  return result
+}
+
+export function buildWavePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaveSearchParams(params)
+  }
+}
+
+export function buildWaveDetailQueryParams(params = {}) {
+  return {
+    waveId: params.waveId,
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20
+  }
+}
+
+export function normalizeWaveRow(record = {}) {
+  const statusConfig = getStatusConfig(record.exceStatus, record['exceStatus$'])
+  const progress = normalizeNumber(record.anfme) > 0
+    ? Math.min(Math.round((normalizeNumber(record.workQty) / normalizeNumber(record.anfme)) * 100), 100)
+    : 0
+
+  return {
+    ...record,
+    code: record.code || '-',
+    typeLabel: record['type$'] || record.type || '-',
+    exceStatusText: statusConfig.label,
+    exceStatusTagType: statusConfig.tagType,
+    statusLabel: record['status$'] || record.status || '-',
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    workQty: normalizeNumber(record.workQty),
+    orderNum: normalizeNumber(record.orderNum),
+    groupQty: normalizeNumber(record.groupQty),
+    progress,
+    createTimeText: record['createTime$'] || record.createTime || '-',
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createByText: record['createBy$'] || '-',
+    updateByText: record['updateBy$'] || '-',
+    memo: record.memo || '-',
+    targSite: record.targSite || '-',
+    stationId: record.stationId || '-',
+    locCode: record.locCode || '-',
+    canPublicTask: Number(record.exceStatus) === 0,
+    canPause: Number(record.exceStatus) === 1,
+    canContinue: Number(record.exceStatus) === 2,
+    canStop: Number(record.exceStatus) !== 3
+  }
+}
+
+export function normalizeWaveItemRow(record = {}) {
+  const statusConfig = getItemStatusConfig(record.exceStatus, record['exceStatus$'])
+  return {
+    ...record,
+    waveCode: record.waveCode || '-',
+    orderCode: record.orderCode || '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    batch: record.batch || '-',
+    splrBatch: record.splrBatch || '-',
+    unit: record.unit || '-',
+    trackCode: record.trackCode || '-',
+    fieldsIndex: record.fieldsIndex || '-',
+    anfme: normalizeNumber(record.anfme),
+    qty: normalizeNumber(record.qty),
+    workQty: normalizeNumber(record.workQty),
+    stockQty: normalizeNumber(record.stockQty),
+    stockLocsText: normalizeStockLocs(record.stockLocs),
+    statusLabel: record['status$'] || record.status || '-',
+    exceStatusText: statusConfig.label,
+    exceStatusTagType: statusConfig.tagType,
+    updateTimeText: record['updateTime$'] || record.updateTime || '-',
+    createTimeText: record['createTime$'] || record.createTime || '-'
+  }
+}
+
+export function buildWavePrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWaveRow(record))
+}
+
+export function buildWaveReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAVE_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAVE_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAVE_REPORT_STYLE,
+      orientation
+    }
+  }
+}
+
+export function getWaveActionList(row = {}) {
+  const normalizedRow = normalizeWaveRow(row)
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅璇︽儏',
+      icon: 'ri:eye-line'
+    },
+    {
+      key: 'publicTask',
+      label: '涓嬪彂浠诲姟',
+      icon: 'ri:play-circle-line',
+      disabled: !normalizedRow.canPublicTask
+    },
+    {
+      key: 'pause',
+      label: '鏆傚仠',
+      icon: 'ri:pause-circle-line',
+      disabled: !normalizedRow.canPause
+    },
+    {
+      key: 'continue',
+      label: '缁х画',
+      icon: 'ri:play-line',
+      disabled: !normalizedRow.canContinue
+    },
+    {
+      key: 'stop',
+      label: '缁堟',
+      icon: 'ri:stop-circle-line',
+      disabled: !normalizedRow.canStop
+    },
+    {
+      key: 'print',
+      label: '鎵撳嵃',
+      icon: 'ri:printer-line'
+    }
+  ]
+}
+
+export function createWaveItemSearchState() {
+  return {
+    condition: '',
+    waveId: '',
+    waveCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    splrBatch: '',
+    orderCode: '',
+    unit: '',
+    fieldsIndex: '',
+    status: '',
+    exceStatus: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function buildWaveItemSearchParams(params = {}) {
+  const result = {}
+  ;[
+    'condition',
+    'waveCode',
+    'matnrCode',
+    'maktx',
+    'batch',
+    'splrBatch',
+    'orderCode',
+    'unit',
+    'fieldsIndex',
+    'timeStart',
+    'timeEnd'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.waveId !== '' && params.waveId !== undefined && params.waveId !== null) {
+    result.waveId = Number(params.waveId)
+  }
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+
+  if (params.exceStatus !== '' && params.exceStatus !== undefined && params.exceStatus !== null) {
+    result.exceStatus = Number(params.exceStatus)
+  }
+
+  return result
+}
+
+export function buildWaveItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWaveItemSearchParams(params)
+  }
+}
diff --git a/rsf-design/src/views/orders/wave/waveTable.columns.js b/rsf-design/src/views/orders/wave/waveTable.columns.js
new file mode 100644
index 0000000..9b0041a
--- /dev/null
+++ b/rsf-design/src/views/orders/wave/waveTable.columns.js
@@ -0,0 +1,179 @@
+import { h } from 'vue'
+import { ElProgress, ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getWaveActionList } from './wavePage.helpers'
+
+export function createWaveTableColumns({ handleActionClick }) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'code',
+      label: '娉㈡鍗曞彿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeLabel',
+      label: '娉㈡绫诲瀷',
+      width: 110
+    },
+    {
+      prop: 'exceStatusText',
+      label: '娉㈡鐘舵��',
+      width: 120,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.exceStatusTagType || 'info', effect: 'light' },
+          () => row.exceStatusText
+        )
+    },
+    {
+      prop: 'anfme',
+      label: '搴旂洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '宸茬洏鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'orderNum',
+      label: '鍗曟嵁鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'progress',
+      label: '杩涘害',
+      width: 160,
+      formatter: (row) =>
+        h(ElProgress, {
+          percentage: Number(row.progress || 0),
+          strokeWidth: 10,
+          status: row.progress >= 100 ? 'success' : undefined,
+          striped: false,
+          showText: true
+        })
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusLabel',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: Number(row.status) === 1 ? 'success' : 'danger', effect: 'light' },
+          () => row.statusLabel
+        )
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 220,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getWaveActionList(row),
+          onClick: (item) => handleActionClick(item, row)
+        })
+    }
+  ]
+}
+
+export function createWavePreviewItemColumns() {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'waveCode',
+      label: '娉㈡鍙�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'orderCode',
+      label: '鍗曟嵁缂栫爜',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旈厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '宸查厤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'stockQty',
+      label: '搴撳瓨鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'stockLocsText',
+      label: '搴撲綅',
+      minWidth: 220,
+      showOverflowTooltip: true
+    }
+  ]
+}
+
+export function createWaveDetailItemColumns() {
+  return createWavePreviewItemColumns()
+}
diff --git a/rsf-design/src/views/reports/statistic-count/index.vue b/rsf-design/src/views/reports/statistic-count/index.vue
new file mode 100644
index 0000000..74d3070
--- /dev/null
+++ b/rsf-design/src/views/reports/statistic-count/index.vue
@@ -0,0 +1,167 @@
+<template>
+  <div class="statistic-count-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchStatisticCountPage } from '@/api/statistic-count'
+  import {
+    buildStatisticCountPageQueryParams,
+    createStatisticCountSearchState,
+    normalizeStatisticCountRow
+  } from './statisticCountPage.helpers'
+  import { createStatisticCountTableColumns } from './statisticCountTable.columns'
+
+  defineOptions({ name: 'StatisticCount' })
+
+  const loading = ref(false)
+  const tableData = ref([])
+  const searchForm = ref(createStatisticCountSearchState())
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ粺璁℃棩鏈�/鐗╂枡缂栫爜'
+      }
+    },
+    {
+      label: '缁熻鏃ユ湡',
+      key: 'dayTime',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨缁熻鏃ユ湡'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    }
+  ])
+
+  const { columns, columnChecks } = useTableColumns(() => createStatisticCountTableColumns())
+
+  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 loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchStatisticCountPage(
+          buildStatisticCountPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        {
+          timeoutMessage: '鏃ュ嚭鍏ュ簱姹囨�荤粺璁″姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }
+      )
+
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeStatisticCountRow(record))
+        : []
+      updatePaginationState(response)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createStatisticCountSearchState()
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  onMounted(() => {
+    loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/reports/statistic-count/statisticCountPage.helpers.js b/rsf-design/src/views/reports/statistic-count/statisticCountPage.helpers.js
new file mode 100644
index 0000000..26039f0
--- /dev/null
+++ b/rsf-design/src/views/reports/statistic-count/statisticCountPage.helpers.js
@@ -0,0 +1,55 @@
+export const STATISTIC_COUNT_REPORT_TITLE = '鏃ュ嚭鍏ュ簱姹囨�荤粺璁�'
+
+export function createStatisticCountSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    matnrCode: '',
+    maktx: '',
+    batch: ''
+  }
+}
+
+export function buildStatisticCountPageQueryParams(params = {}) {
+  const values = {
+    condition: params.condition,
+    dayTime: params.dayTime,
+    matnrCode: params.matnrCode,
+    maktx: params.maktx,
+    batch: params.batch
+  }
+
+  return Object.entries(values).reduce(
+    (result, [key, value]) => {
+      if (value === undefined || value === null) {
+        return result
+      }
+      if (typeof value === 'string') {
+        const normalized = value.trim()
+        if (!normalized) {
+          return result
+        }
+        result[key] = normalized
+        return result
+      }
+      result[key] = value
+      return result
+    },
+    {
+      current: Number(params.current) || 1,
+      pageSize: Number(params.pageSize || params.size) || 20
+    }
+  )
+}
+
+export function normalizeStatisticCountRow(row = {}) {
+  return {
+    ...row,
+    count: Number(row.count || 0),
+    anfme: Number(row.anfme || 0),
+    inAnfmeCount: Number(row.inAnfmeCount || 0),
+    outAnfmeCount: Number(row.outAnfmeCount || 0),
+    inAnfme: Number(row.inAnfme || 0),
+    outAnfme: Number(row.outAnfme || 0)
+  }
+}
diff --git a/rsf-design/src/views/reports/statistic-count/statisticCountTable.columns.js b/rsf-design/src/views/reports/statistic-count/statisticCountTable.columns.js
new file mode 100644
index 0000000..40f9372
--- /dev/null
+++ b/rsf-design/src/views/reports/statistic-count/statisticCountTable.columns.js
@@ -0,0 +1,44 @@
+export function createStatisticCountTableColumns() {
+  return [
+    {
+      prop: 'id',
+      label: 'ID',
+      minWidth: 80
+    },
+    {
+      prop: 'dayTime',
+      label: '缁熻鏃ユ湡',
+      minWidth: 120
+    },
+    {
+      prop: 'count',
+      label: '璁板綍鏁�',
+      minWidth: 100
+    },
+    {
+      prop: 'inAnfmeCount',
+      label: '鍏ュ簱绗旀暟',
+      minWidth: 110
+    },
+    {
+      prop: 'outAnfmeCount',
+      label: '鍑哄簱绗旀暟',
+      minWidth: 110
+    },
+    {
+      prop: 'anfme',
+      label: '鎬绘暟閲�',
+      minWidth: 110
+    },
+    {
+      prop: 'inAnfme',
+      label: '鍏ュ簱鏁伴噺',
+      minWidth: 110
+    },
+    {
+      prop: 'outAnfme',
+      label: '鍑哄簱鏁伴噺',
+      minWidth: 110
+    }
+  ]
+}
diff --git a/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemPage.helpers.js b/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemPage.helpers.js
new file mode 100644
index 0000000..c8f7fce
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemPage.helpers.js
@@ -0,0 +1,86 @@
+import { getInStatisticTaskStatusMeta, getInStatisticTaskTypeMeta } from '../in-statistic/inStatisticPage.helpers.js'
+
+export const IN_STATISTIC_ITEM_PAGE_TITLE = '鍏ュ簱缁熻鏄庣粏'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function createInStatisticItemSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: ''
+  }
+}
+
+export function getInStatisticItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildInStatisticItemSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    dayTime: normalizeText(params.dayTime),
+    locCode: normalizeText(params.locCode),
+    matnrCode: normalizeText(params.matnrCode),
+    maktx: normalizeText(params.maktx),
+    batch: normalizeText(params.batch),
+    taskType: normalizeNumber(params.taskType, 1),
+    taskStatus: normalizeNumber(params.taskStatus, 100)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildInStatisticItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildInStatisticItemSearchParams(params)
+  }
+}
+
+export function normalizeInStatisticItemRow(record = {}) {
+  const taskTypeMeta = getInStatisticTaskTypeMeta(record.taskType ?? record.task_type)
+  const taskStatusMeta = getInStatisticTaskStatusMeta(record.taskStatus ?? record.task_status)
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    dayTimeText: normalizeText(record.dayTime || record.day_time || ''),
+    taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text),
+    taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info',
+    taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text),
+    taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info',
+    locCode: normalizeText(record.locCode || record.loc_code || ''),
+    barcode: normalizeText(record.barcode || ''),
+    matnrCode: normalizeText(record.matnrCode || record.matnr_code || ''),
+    maktx: normalizeText(record.maktx || ''),
+    batch: normalizeText(record.batch || ''),
+    unit: normalizeText(record.unit || ''),
+    anfme: record.anfme ?? '--',
+    fieldsIndex: normalizeText(record.fieldsIndex || record.fields_index || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    memo: normalizeText(record.memo || '')
+  }
+}
diff --git a/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemTable.columns.js b/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemTable.columns.js
new file mode 100644
index 0000000..4a3cba9
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic-item/inStatisticItemTable.columns.js
@@ -0,0 +1,128 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createInStatisticItemTableColumns({ handleView } = {}) {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
+    {
+      prop: 'dayTimeText',
+      label: '缁熻鏃ユ湡',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.dayTimeText || '--'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'taskTypeText',
+      label: '浠诲姟绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskTypeTagType || 'info', effect: 'light' }, () => row?.taskTypeText || '--')
+    },
+    {
+      prop: 'taskStatusText',
+      label: '浠诲姟鐘舵��',
+      width: 140,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskStatusTagType || 'info', effect: 'light' }, () => row?.taskStatusText || '--')
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/statistics/in-statistic-item/index.vue b/rsf-design/src/views/statistics/in-statistic-item/index.vue
new file mode 100644
index 0000000..923c631
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic-item/index.vue
@@ -0,0 +1,86 @@
+<template>
+  <div class="in-statistic-item-page art-full-height">
+    <ArtSearchBar v-model="searchForm" :items="searchItems" :showExpand="true" @search="handleSearch" @reset="handleReset" />
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+    <InStatisticItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchInStatisticItemPage } from '@/api/in-statistic-item'
+  import {
+    buildInStatisticItemPageQueryParams,
+    buildInStatisticItemSearchParams,
+    createInStatisticItemSearchState,
+    getInStatisticItemPaginationKey,
+    normalizeInStatisticItemRow
+  } from './inStatisticItemPage.helpers'
+  import { createInStatisticItemTableColumns } from './inStatisticItemTable.columns'
+  import InStatisticItemDetailDrawer from './modules/in-statistic-item-detail-drawer.vue'
+
+  defineOptions({ name: 'InStatisticItem' })
+
+  const searchForm = ref(createInStatisticItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�/缂栫爜/鎵规/搴撲綅' } },
+    { label: '缁熻鏃ユ湡', key: 'dayTime', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
+    { label: '搴撲綅', key: 'locCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ簱浣�' } },
+    { label: '鐗╂枡缂栫爜', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' } },
+    { label: '鐗╂枡鍚嶇О', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' } },
+    { label: '鎵规', key: 'batch', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' } }
+  ])
+
+  function openDetail(row) {
+    detailData.value = normalizeInStatisticItemRow(row)
+    detailDrawerVisible.value = true
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchInStatisticItemPage,
+      apiParams: buildInStatisticItemPageQueryParams(searchForm.value),
+      paginationKey: getInStatisticItemPaginationKey(),
+      columnsFactory: () => createInStatisticItemTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeInStatisticItemRow(item)) : [])
+    }
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildInStatisticItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createInStatisticItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/in-statistic-item/modules/in-statistic-item-detail-drawer.vue b/rsf-design/src/views/statistics/in-statistic-item/modules/in-statistic-item-detail-drawer.vue
new file mode 100644
index 0000000..85845bf
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic-item/modules/in-statistic-item-detail-drawer.vue
@@ -0,0 +1,53 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    :title="IN_STATISTIC_ITEM_PAGE_TITLE + '璇︽儏'"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁熻鏃ユ湡">{{ detail.dayTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">
+            <ElTag :type="detail.taskTypeTagType || 'info'" effect="light">{{ detail.taskTypeText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">
+            <ElTag :type="detail.taskStatusTagType || 'info'" effect="light">{{ detail.taskStatusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { IN_STATISTIC_ITEM_PAGE_TITLE } from '../inStatisticItemPage.helpers'
+
+  defineOptions({ name: 'InStatisticItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/in-statistic/inStatisticPage.helpers.js b/rsf-design/src/views/statistics/in-statistic/inStatisticPage.helpers.js
new file mode 100644
index 0000000..65b0c6c
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic/inStatisticPage.helpers.js
@@ -0,0 +1,107 @@
+const TASK_TYPE_META = {
+  1: { text: '鍏ュ簱', type: 'success' }
+}
+
+const TASK_STATUS_META = {
+  98: { text: '鍏ュ簱瀹屾垚', type: 'success' },
+  99: { text: '涓婃姤瀹屾垚', type: 'warning' },
+  100: { text: '搴撳瓨鏇存柊瀹屾垚', type: 'success' },
+  101: { text: '鍒涘缓鍑哄簱浠诲姟', type: 'info' }
+}
+
+export const IN_STATISTIC_PAGE_TITLE = '鍏ュ簱缁熻'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function createInStatisticSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    maktx: '',
+    matnrCode: '',
+    batch: ''
+  }
+}
+
+export function getInStatisticPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildInStatisticSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    dayTime: normalizeText(params.dayTime),
+    maktx: normalizeText(params.maktx),
+    matnrCode: normalizeText(params.matnrCode),
+    batch: normalizeText(params.batch),
+    taskType: normalizeNumber(params.taskType, 1),
+    taskStatus: normalizeNumber(params.taskStatus, 100)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildInStatisticPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildInStatisticSearchParams(params)
+  }
+}
+
+export function getInStatisticTaskTypeMeta(taskType) {
+  if (taskType === null || taskType === undefined || taskType === '') {
+    return { text: '鍏ュ簱', type: 'success' }
+  }
+  return TASK_TYPE_META[Number(taskType)] || { text: String(taskType), type: 'info' }
+}
+
+export function getInStatisticTaskStatusMeta(taskStatus) {
+  if (taskStatus === null || taskStatus === undefined || taskStatus === '') {
+    return { text: '-', type: 'info' }
+  }
+  return TASK_STATUS_META[Number(taskStatus)] || { text: String(taskStatus), type: 'info' }
+}
+
+export function normalizeInStatisticRow(record = {}) {
+  const taskTypeMeta = getInStatisticTaskTypeMeta(record.taskType ?? record.task_type)
+  const taskStatusMeta = getInStatisticTaskStatusMeta(record.taskStatus ?? record.task_status)
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    dayTimeText: normalizeText(record.dayTime || record.day_time || ''),
+    taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text),
+    taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info',
+    taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text),
+    taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info',
+    locCode: normalizeText(record.locCode || record.loc_code || ''),
+    barcode: normalizeText(record.barcode || ''),
+    matnrCode: normalizeText(record.matnrCode || record.matnr_code || ''),
+    maktx: normalizeText(record.maktx || ''),
+    batch: normalizeText(record.batch || ''),
+    unit: normalizeText(record.unit || ''),
+    anfme: record.anfme ?? '--',
+    fieldsIndex: normalizeText(record.fieldsIndex || record.fields_index || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    memo: normalizeText(record.memo || '')
+  }
+}
diff --git a/rsf-design/src/views/statistics/in-statistic/inStatisticTable.columns.js b/rsf-design/src/views/statistics/in-statistic/inStatisticTable.columns.js
new file mode 100644
index 0000000..e0a11a3
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic/inStatisticTable.columns.js
@@ -0,0 +1,86 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createInStatisticTableColumns({ handleView } = {}) {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
+    {
+      prop: 'dayTimeText',
+      label: '缁熻鏃ユ湡',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.dayTimeText || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'taskTypeText',
+      label: '浠诲姟绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskTypeTagType || 'info', effect: 'light' }, () => row?.taskTypeText || '--')
+    },
+    {
+      prop: 'taskStatusText',
+      label: '浠诲姟鐘舵��',
+      width: 140,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskStatusTagType || 'info', effect: 'light' }, () => row?.taskStatusText || '--')
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/statistics/in-statistic/index.vue b/rsf-design/src/views/statistics/in-statistic/index.vue
new file mode 100644
index 0000000..b72dfa9
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic/index.vue
@@ -0,0 +1,91 @@
+<template>
+  <div class="in-statistic-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+    <InStatisticDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchInStatisticPage } from '@/api/in-statistic'
+  import {
+    buildInStatisticPageQueryParams,
+    buildInStatisticSearchParams,
+    createInStatisticSearchState,
+    getInStatisticPaginationKey,
+    normalizeInStatisticRow
+  } from './inStatisticPage.helpers'
+  import { createInStatisticTableColumns } from './inStatisticTable.columns'
+  import InStatisticDetailDrawer from './modules/in-statistic-detail-drawer.vue'
+
+  defineOptions({ name: 'InStatistic' })
+
+  const searchForm = ref(createInStatisticSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�/缂栫爜/鎵规' } },
+    { label: '缁熻鏃ユ湡', key: 'dayTime', type: 'date', props: { clearable: true, type: 'date', valueFormat: 'YYYY-MM-DD' } },
+    { label: '鐗╂枡鍚嶇О', key: 'maktx', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�' } },
+    { label: '鐗╂枡缂栫爜', key: 'matnrCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�' } },
+    { label: '鎵规', key: 'batch', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ壒娆�' } }
+  ])
+
+  function openDetail(row) {
+    detailData.value = normalizeInStatisticRow(row)
+    detailDrawerVisible.value = true
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchInStatisticPage,
+      apiParams: buildInStatisticPageQueryParams(searchForm.value),
+      paginationKey: getInStatisticPaginationKey(),
+      columnsFactory: () => createInStatisticTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeInStatisticRow(item)) : [])
+    }
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildInStatisticSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createInStatisticSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/in-statistic/modules/in-statistic-detail-drawer.vue b/rsf-design/src/views/statistics/in-statistic/modules/in-statistic-detail-drawer.vue
new file mode 100644
index 0000000..2c11312
--- /dev/null
+++ b/rsf-design/src/views/statistics/in-statistic/modules/in-statistic-detail-drawer.vue
@@ -0,0 +1,48 @@
+<template>
+  <ElDrawer :model-value="visible" :title="IN_STATISTIC_PAGE_TITLE + '璇︽儏'" size="72%" @update:model-value="handleVisibleChange">
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁熻鏃ユ湡">{{ detail.dayTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">
+            <ElTag :type="detail.taskTypeTagType || 'info'" effect="light">{{ detail.taskTypeText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">
+            <ElTag :type="detail.taskStatusTagType || 'info'" effect="light">{{ detail.taskStatusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { IN_STATISTIC_PAGE_TITLE } from '../inStatisticPage.helpers'
+
+  defineOptions({ name: 'InStatisticDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/out-statistic-item/index.vue b/rsf-design/src/views/statistics/out-statistic-item/index.vue
new file mode 100644
index 0000000..9d0f0a4
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic-item/index.vue
@@ -0,0 +1,148 @@
+<template>
+  <div class="out-statistic-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <OutStatisticItemDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchOutStatisticItemPage } from '@/api/out-statistic-item'
+  import {
+    buildOutStatisticItemPageQueryParams,
+    buildOutStatisticItemSearchParams,
+    createOutStatisticItemSearchState,
+    getOutStatisticItemPaginationKey,
+    normalizeOutStatisticItemRow
+  } from './outStatisticItemPage.helpers'
+  import { createOutStatisticItemTableColumns } from './outStatisticItemTable.columns'
+  import OutStatisticItemDetailDrawer from './modules/out-statistic-item-detail-drawer.vue'
+
+  defineOptions({ name: 'OutStatisticItem' })
+
+  const searchForm = ref(createOutStatisticItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�/缂栫爜/鎵规/搴撲綅'
+      }
+    },
+    {
+      label: '缁熻鏃ユ湡',
+      key: 'dayTime',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      label: '搴撲綅',
+      key: 'locCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailData.value = normalizeOutStatisticItemRow(row)
+    detailDrawerVisible.value = true
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchOutStatisticItemPage,
+      apiParams: buildOutStatisticItemPageQueryParams(searchForm.value),
+      paginationKey: getOutStatisticItemPaginationKey(),
+      columnsFactory: () =>
+        createOutStatisticItemTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeOutStatisticItemRow(item)) : []
+    }
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildOutStatisticItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createOutStatisticItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/out-statistic-item/modules/out-statistic-item-detail-drawer.vue b/rsf-design/src/views/statistics/out-statistic-item/modules/out-statistic-item-detail-drawer.vue
new file mode 100644
index 0000000..3a55fd5
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic-item/modules/out-statistic-item-detail-drawer.vue
@@ -0,0 +1,57 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    :title="OUT_STATISTIC_ITEM_PAGE_TITLE + '璇︽儏'"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁熻鏃ユ湡">{{ detail.dayTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">
+            <ElTag :type="detail.taskTypeTagType || 'info'" effect="light">
+              {{ detail.taskTypeText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">
+            <ElTag :type="detail.taskStatusTagType || 'info'" effect="light">
+              {{ detail.taskStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { OUT_STATISTIC_ITEM_PAGE_TITLE } from '../outStatisticItemPage.helpers'
+
+  defineOptions({ name: 'OutStatisticItemDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemPage.helpers.js b/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemPage.helpers.js
new file mode 100644
index 0000000..3d1266f
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemPage.helpers.js
@@ -0,0 +1,89 @@
+import {
+  getOutStatisticTaskStatusMeta,
+  getOutStatisticTaskTypeMeta
+} from '../out-statistic/outStatisticPage.helpers.js'
+
+export const OUT_STATISTIC_ITEM_PAGE_TITLE = '鍑哄簱缁熻鏄庣粏'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function createOutStatisticItemSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: ''
+  }
+}
+
+export function getOutStatisticItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildOutStatisticItemSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    dayTime: normalizeText(params.dayTime),
+    locCode: normalizeText(params.locCode),
+    matnrCode: normalizeText(params.matnrCode),
+    maktx: normalizeText(params.maktx),
+    batch: normalizeText(params.batch),
+    taskType: normalizeNumber(params.taskType, 101),
+    taskStatus: normalizeNumber(params.taskStatus, 200)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildOutStatisticItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutStatisticItemSearchParams(params)
+  }
+}
+
+export function normalizeOutStatisticItemRow(record = {}) {
+  const taskTypeMeta = getOutStatisticTaskTypeMeta(record.taskType ?? record.task_type)
+  const taskStatusMeta = getOutStatisticTaskStatusMeta(record.taskStatus ?? record.task_status)
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    dayTimeText: normalizeText(record.dayTime || record.day_time || ''),
+    taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text),
+    taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info',
+    taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text),
+    taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info',
+    locCode: normalizeText(record.locCode || record.loc_code || ''),
+    barcode: normalizeText(record.barcode || ''),
+    matnrCode: normalizeText(record.matnrCode || record.matnr_code || ''),
+    maktx: normalizeText(record.maktx || ''),
+    batch: normalizeText(record.batch || ''),
+    unit: normalizeText(record.unit || ''),
+    anfme: record.anfme ?? '--',
+    fieldsIndex: normalizeText(record.fieldsIndex || record.fields_index || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    memo: normalizeText(record.memo || '')
+  }
+}
diff --git a/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemTable.columns.js b/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemTable.columns.js
new file mode 100644
index 0000000..3fc2c1b
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic-item/outStatisticItemTable.columns.js
@@ -0,0 +1,133 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createOutStatisticItemTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
+    {
+      prop: 'dayTimeText',
+      label: '缁熻鏃ユ湡',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.dayTimeText || '--'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'barcode',
+      label: '鎵樼洏鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.barcode || '--'
+    },
+    {
+      prop: 'taskTypeText',
+      label: '浠诲姟绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskTypeTagType || 'info', effect: 'light' }, () => row?.taskTypeText || '--')
+    },
+    {
+      prop: 'taskStatusText',
+      label: '浠诲姟鐘舵��',
+      width: 140,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskStatusTagType || 'info', effect: 'light' }, () => row?.taskStatusText || '--')
+    },
+    {
+      prop: 'createByText',
+      label: '鍒涘缓浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createByText || '--'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.createTimeText || '--'
+    },
+    {
+      prop: 'updateByText',
+      label: '鏇存柊浜�',
+      minWidth: 120,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateByText || '--'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/statistics/out-statistic/index.vue b/rsf-design/src/views/statistics/out-statistic/index.vue
new file mode 100644
index 0000000..d75320b
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic/index.vue
@@ -0,0 +1,140 @@
+<template>
+  <div class="out-statistic-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <OutStatisticDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useTable } from '@/hooks/core/useTable'
+  import { fetchOutStatisticPage } from '@/api/out-statistic'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import {
+    buildOutStatisticPageQueryParams,
+    buildOutStatisticSearchParams,
+    createOutStatisticSearchState,
+    getOutStatisticPaginationKey,
+    normalizeOutStatisticRow,
+    OUT_STATISTIC_PAGE_TITLE
+  } from './outStatisticPage.helpers'
+  import { createOutStatisticTableColumns } from './outStatisticTable.columns'
+  import OutStatisticDetailDrawer from './modules/out-statistic-detail-drawer.vue'
+
+  defineOptions({ name: 'OutStatistic' })
+
+  const searchForm = ref(createOutStatisticSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�/缂栫爜/鎵规'
+      }
+    },
+    {
+      label: '缁熻鏃ユ湡',
+      key: 'dayTime',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailData.value = normalizeOutStatisticRow(row)
+    detailDrawerVisible.value = true
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchOutStatisticPage,
+      apiParams: buildOutStatisticPageQueryParams(searchForm.value),
+      paginationKey: getOutStatisticPaginationKey(),
+      columnsFactory: () =>
+        createOutStatisticTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeOutStatisticRow(item)) : [])
+    }
+  })
+
+  function handleSearch(params) {
+    replaceSearchParams(buildOutStatisticSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createOutStatisticSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/out-statistic/modules/out-statistic-detail-drawer.vue b/rsf-design/src/views/statistics/out-statistic/modules/out-statistic-detail-drawer.vue
new file mode 100644
index 0000000..5a79746
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic/modules/out-statistic-detail-drawer.vue
@@ -0,0 +1,57 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    :title="OUT_STATISTIC_PAGE_TITLE + '璇︽儏'"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="ID">{{ detail.id ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁熻鏃ユ湡">{{ detail.dayTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟绫诲瀷">
+            <ElTag :type="detail.taskTypeTagType || 'info'" effect="light">
+              {{ detail.taskTypeText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鐘舵��">
+            <ElTag :type="detail.taskStatusTagType || 'info'" effect="light">
+              {{ detail.taskStatusText || '--' }}
+            </ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="搴撲綅">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵樼洏鐮�">{{ detail.barcode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绱㈠紩">{{ detail.fieldsIndex || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓浜�">{{ detail.createByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊浜�">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { OUT_STATISTIC_PAGE_TITLE } from '../outStatisticPage.helpers'
+
+  defineOptions({ name: 'OutStatisticDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/statistics/out-statistic/outStatisticPage.helpers.js b/rsf-design/src/views/statistics/out-statistic/outStatisticPage.helpers.js
new file mode 100644
index 0000000..9ad8a84
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic/outStatisticPage.helpers.js
@@ -0,0 +1,116 @@
+const TASK_TYPE_META = {
+  101: { text: '鍑哄簱', type: 'warning' }
+}
+
+const TASK_STATUS_META = {
+  196: { text: '绛夊緟纭', type: 'info' },
+  197: { text: '绛夊緟瀹瑰櫒鍒拌揪', type: 'info' },
+  198: { text: '鍑哄簱瀹屾垚', type: 'success' },
+  199: { text: '鎾涓�/鐩樼偣涓�/寰呯‘璁�', type: 'warning' },
+  200: { text: '搴撳瓨鏇存柊瀹屾垚', type: 'success' }
+}
+
+export const OUT_STATISTIC_PAGE_TITLE = '鍑哄簱缁熻'
+export const OUT_STATISTIC_REPORT_TITLE = '鍑哄簱缁熻鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : fallback
+}
+
+export function createOutStatisticSearchState() {
+  return {
+    condition: '',
+    dayTime: '',
+    maktx: '',
+    matnrCode: '',
+    batch: ''
+  }
+}
+
+export function getOutStatisticPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildOutStatisticSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    dayTime: normalizeText(params.dayTime),
+    maktx: normalizeText(params.maktx),
+    matnrCode: normalizeText(params.matnrCode),
+    batch: normalizeText(params.batch),
+    taskType: normalizeNumber(params.taskType, 101),
+    taskStatus: normalizeNumber(params.taskStatus, 200)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildOutStatisticPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutStatisticSearchParams(params)
+  }
+}
+
+export function getOutStatisticTaskTypeMeta(taskType) {
+  if (taskType === null || taskType === undefined || taskType === '') {
+    return { text: '鍑哄簱', type: 'warning' }
+  }
+  return TASK_TYPE_META[Number(taskType)] || { text: String(taskType), type: 'info' }
+}
+
+export function getOutStatisticTaskStatusMeta(taskStatus) {
+  if (taskStatus === null || taskStatus === undefined || taskStatus === '') {
+    return { text: '-', type: 'info' }
+  }
+  return TASK_STATUS_META[Number(taskStatus)] || { text: String(taskStatus), type: 'info' }
+}
+
+export function normalizeOutStatisticRow(record = {}) {
+  const taskTypeMeta = getOutStatisticTaskTypeMeta(record.taskType ?? record.task_type)
+  const taskStatusMeta = getOutStatisticTaskStatusMeta(record.taskStatus ?? record.task_status)
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    dayTimeText: normalizeText(record.dayTime || record.day_time || ''),
+    taskTypeText: normalizeText(record.taskTypeText || record['taskType$'] || taskTypeMeta.text),
+    taskTypeTagType: normalizeText(record.taskTypeTagType || taskTypeMeta.type) || 'info',
+    taskStatusText: normalizeText(record.taskStatusText || record['taskStatus$'] || taskStatusMeta.text),
+    taskStatusTagType: normalizeText(record.taskStatusTagType || taskStatusMeta.type) || 'info',
+    locCode: normalizeText(record.locCode || record.loc_code || ''),
+    barcode: normalizeText(record.barcode || ''),
+    matnrCode: normalizeText(record.matnrCode || record.matnr_code || ''),
+    maktx: normalizeText(record.maktx || ''),
+    batch: normalizeText(record.batch || ''),
+    unit: normalizeText(record.unit || ''),
+    anfme: record.anfme ?? '--',
+    fieldsIndex: normalizeText(record.fieldsIndex || record.fields_index || ''),
+    createByText: normalizeText(record.createBy$ || record.createByText || record.createBy || ''),
+    createTimeText: normalizeText(record.createTime$ || record.createTime || ''),
+    updateByText: normalizeText(record.updateBy$ || record.updateByText || record.updateBy || ''),
+    updateTimeText: normalizeText(record.updateTime$ || record.updateTime || ''),
+    memo: normalizeText(record.memo || '')
+  }
+}
+
+export function buildOutStatisticPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeOutStatisticRow(record))
+}
diff --git a/rsf-design/src/views/statistics/out-statistic/outStatisticTable.columns.js b/rsf-design/src/views/statistics/out-statistic/outStatisticTable.columns.js
new file mode 100644
index 0000000..991fd12
--- /dev/null
+++ b/rsf-design/src/views/statistics/out-statistic/outStatisticTable.columns.js
@@ -0,0 +1,91 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createOutStatisticTableColumns({ handleView } = {}) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'id',
+      label: 'ID',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.id ?? '--'
+    },
+    {
+      prop: 'dayTimeText',
+      label: '缁熻鏃ユ湡',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.dayTimeText || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 120,
+      align: 'right',
+      formatter: (row) => row.anfme ?? '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90,
+      align: 'center',
+      formatter: (row) => row.unit || '--'
+    },
+    {
+      prop: 'taskTypeText',
+      label: '浠诲姟绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskTypeTagType || 'info', effect: 'light' }, () => row?.taskTypeText || '--')
+    },
+    {
+      prop: 'taskStatusText',
+      label: '浠诲姟鐘舵��',
+      width: 140,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.taskStatusTagType || 'info', effect: 'light' }, () => row?.taskStatusText || '--')
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/stock/stock-transfer/index.vue b/rsf-design/src/views/stock/stock-transfer/index.vue
new file mode 100644
index 0000000..31d88dc
--- /dev/null
+++ b/rsf-design/src/views/stock/stock-transfer/index.vue
@@ -0,0 +1,312 @@
+<template>
+  <div class="stock-transfer-page art-full-height">
+    <ElCard class="art-table-card mb-4">
+      <template #header>
+        <div class="flex items-center justify-between gap-3">
+          <div class="text-sm font-medium text-[var(--art-text-gray-800)]">搴撲綅杞Щ浠诲姟</div>
+          <div class="text-xs text-[var(--art-text-gray-500)]">婧愬簱浣嶇紪鐮佽揪鍒� 7 浣嶅悗鑷姩鑱旀兂鐩爣搴撲綅骞跺姞杞芥槑缁�</div>
+        </div>
+      </template>
+
+      <div class="flex flex-col gap-4 xl:flex-row xl:items-end">
+        <div class="min-w-0 flex-1">
+          <div class="mb-2 text-sm font-medium text-[var(--art-text-gray-700)]">婧愬簱浣�</div>
+          <ElInput
+            v-model="orgLoc"
+            clearable
+            placeholder="璇疯緭鍏ユ簮搴撲綅缂栫爜"
+            @keyup.enter="handleLoadSourceData"
+          />
+        </div>
+
+        <div class="min-w-0 flex-1">
+          <div class="mb-2 text-sm font-medium text-[var(--art-text-gray-700)]">鐩爣搴撲綅</div>
+          <ElAutocomplete
+            v-model="tarLoc"
+            :fetch-suggestions="queryTargetLoc"
+            :trigger-on-focus="true"
+            :loading="targetLocLoading"
+            clearable
+            placeholder="璇疯緭鍏ョ洰鏍囧簱浣嶇紪鐮�"
+          />
+        </div>
+
+        <div class="flex flex-wrap gap-2">
+          <ElButton :loading="loading" @click="handleLoadSourceData">鍔犺浇鏄庣粏</ElButton>
+          <ElButton type="primary" :disabled="!canGenerateTask" @click="handleGenerateTask">
+            鐢熸垚绉诲簱浠诲姟
+          </ElButton>
+        </div>
+      </div>
+    </ElCard>
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadSourceData">
+        <template #left>
+          <div class="text-sm text-[var(--art-text-gray-500)]">
+            婧愬簱浣嶏細{{ orgLoc || '--' }}锛岀洰鏍囧簱浣嶏細{{ tarLoc || '--' }}
+          </div>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <StockTransferDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref, watch } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchStockTransferEnabledFields,
+    fetchStockTransferMoveTask,
+    fetchStockTransferSourceDetail,
+    fetchStockTransferSourcePage,
+    fetchStockTransferTargetLocPage
+  } from '@/api/stock-transfer'
+  import StockTransferDetailDrawer from './modules/stock-transfer-detail-drawer.vue'
+  import { createStockTransferTableColumns } from './stockTransferTable.columns'
+  import {
+    buildStockTransferSourcePageQueryParams,
+    buildStockTransferTargetLocPageQueryParams,
+    buildStockTransferTaskPayload,
+    normalizeStockTransferDetail,
+    normalizeStockTransferEnabledFields,
+    normalizeStockTransferRow,
+    normalizeStockTransferTargetLocOptions
+  } from './stockTransferPage.helpers'
+
+  defineOptions({ name: 'StockTransfer' })
+
+  const enabledFields = ref([])
+  const orgLoc = ref('')
+  const tarLoc = ref('')
+  const memo = ref('')
+  const loading = ref(false)
+  const targetLocLoading = ref(false)
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const tableData = ref([])
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const canGenerateTask = computed(() => {
+    const sourceLoc = String(orgLoc.value || '').trim()
+    const targetLoc = String(tarLoc.value || '').trim()
+    return sourceLoc.length >= 7 && targetLoc.length > 0 && !loading.value
+  })
+
+  const { columns, columnChecks, resetColumns } = useTableColumns(() =>
+    createStockTransferTableColumns({
+      enabledFields: enabledFields.value,
+      handleView: openDetail
+    })
+  )
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  function normalizeOrgLocValue(value) {
+    return String(value ?? '').trim()
+  }
+
+  function resetTableState() {
+    tableData.value = []
+    pagination.current = 1
+    pagination.total = 0
+  }
+
+  async function loadEnabledFields() {
+    const fields = await guardRequestWithMessage(fetchStockTransferEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeStockTransferEnabledFields(fields)
+    resetColumns()
+    if (normalizeOrgLocValue(orgLoc.value).length >= 7) {
+      await loadSourceData()
+    }
+  }
+
+  async function loadSourceData() {
+    const sourceLoc = normalizeOrgLocValue(orgLoc.value)
+    if (sourceLoc.length < 7) {
+      resetTableState()
+      return
+    }
+
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchStockTransferSourcePage(
+          buildStockTransferSourcePageQueryParams({
+            orgLoc: sourceLoc,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '绉诲簱婧愬簱浣嶆槑缁嗗姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�' }
+      )
+
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeStockTransferRow(record, enabledFields.value))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleLoadSourceData() {
+    if (normalizeOrgLocValue(orgLoc.value).length < 7) {
+      ElMessage.warning('璇疯緭鍏ユ湁鏁堢殑婧愬簱浣�')
+      resetTableState()
+      return
+    }
+    pagination.current = 1
+    loadSourceData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadSourceData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadSourceData()
+  }
+
+  async function queryTargetLoc(queryString, callback) {
+    const sourceLoc = normalizeOrgLocValue(orgLoc.value)
+    if (sourceLoc.length < 7) {
+      callback([])
+      return
+    }
+
+    targetLocLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchStockTransferTargetLocPage(
+          buildStockTransferTargetLocPageQueryParams({
+            orgLoc: sourceLoc,
+            q: queryString,
+            current: 1,
+            pageSize: 50
+          })
+        ),
+        { records: [], total: 0 },
+        { timeoutMessage: '鐩爣搴撲綅鑱旀兂鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      callback(normalizeStockTransferTargetLocOptions(defaultResponseAdapter(response).records))
+    } catch (error) {
+      callback([])
+      ElMessage.error(error?.message || '鐩爣搴撲綅鑱旀兂鍔犺浇澶辫触')
+    } finally {
+      targetLocLoading.value = false
+    }
+  }
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    detailData.value = normalizeStockTransferRow(row, enabledFields.value)
+    try {
+      const detail = await guardRequestWithMessage(fetchStockTransferSourceDetail(row.id), {}, {
+        timeoutMessage: '绉诲簱婧愬簱浣嶆槑缁嗚鎯呭姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      detailData.value = normalizeStockTransferDetail(detail, enabledFields.value)
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function handleGenerateTask() {
+    const sourceLoc = normalizeOrgLocValue(orgLoc.value)
+    const targetLoc = normalizeOrgLocValue(tarLoc.value)
+
+    if (sourceLoc.length < 7) {
+      ElMessage.warning('璇疯緭鍏ユ湁鏁堢殑婧愬簱浣�')
+      return
+    }
+    if (!targetLoc) {
+      ElMessage.warning('璇疯緭鍏ョ洰鏍囧簱浣�')
+      return
+    }
+
+    try {
+      await ElMessageBox.confirm(`纭畾浠� ${sourceLoc} 杞Щ鍒� ${targetLoc} 鍚楋紵`, '鐢熸垚绉诲簱浠诲姟', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchStockTransferMoveTask(
+        buildStockTransferTaskPayload({
+          orgLoc: sourceLoc,
+          tarLoc: targetLoc,
+          memo: memo.value
+        })
+      )
+      ElMessage.success('绉诲簱浠诲姟鐢熸垚鎴愬姛')
+      await loadSourceData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') {
+        return
+      }
+      ElMessage.error(error?.message || '绉诲簱浠诲姟鐢熸垚澶辫触')
+    }
+  }
+
+  const scheduleLoadSourceData = useDebounceFn(() => {
+    pagination.current = 1
+    loadSourceData()
+  }, 300)
+
+  watch(orgLoc, (value) => {
+    tarLoc.value = ''
+    detailDrawerVisible.value = false
+    detailData.value = {}
+    const normalized = normalizeOrgLocValue(value)
+    if (normalized.length < 7) {
+      resetTableState()
+      return
+    }
+    scheduleLoadSourceData()
+  })
+
+  onMounted(async () => {
+    await loadEnabledFields()
+  })
+</script>
diff --git a/rsf-design/src/views/stock/stock-transfer/modules/stock-transfer-detail-drawer.vue b/rsf-design/src/views/stock/stock-transfer/modules/stock-transfer-detail-drawer.vue
new file mode 100644
index 0000000..7e6ff67
--- /dev/null
+++ b/rsf-design/src/views/stock/stock-transfer/modules/stock-transfer-detail-drawer.vue
@@ -0,0 +1,78 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撲綅杞Щ鏄庣粏璇︽儏"
+    size="86%"
+    destroy-on-close
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)] pr-1">
+      <div v-if="loading" class="py-6">
+        <ElSkeleton :rows="10" animated />
+      </div>
+      <div v-else class="flex flex-col gap-4">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="甯愰潰搴撳瓨">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瑙勬牸">{{ detail.spec || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍨嬪彿">{{ detail.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElCard v-if="dynamicFieldEntries.length > 0" shadow="never" class="border border-[var(--art-border-color)]">
+          <template #header>
+            <div class="text-sm font-medium text-[var(--art-text-gray-800)]">鎵╁睍瀛楁</div>
+          </template>
+          <div class="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
+            <div
+              v-for="item in dynamicFieldEntries"
+              :key="item.key"
+              class="rounded-lg border border-[var(--art-border-color)] px-3 py-2"
+            >
+              <div class="text-xs text-[var(--art-text-gray-500)]">{{ item.label }}</div>
+              <div class="mt-1 text-sm text-[var(--art-text-gray-800)]">{{ item.value || '--' }}</div>
+            </div>
+          </div>
+        </ElCard>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'StockTransferDetailDrawer' })
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const dynamicFieldEntries = computed(() => {
+    const extendFields = props.detail?.extendFields && typeof props.detail.extendFields === 'object'
+      ? props.detail.extendFields
+      : {}
+    return Object.entries(extendFields)
+      .map(([key, value]) => ({
+        key,
+        label: key,
+        value: typeof value === 'string' ? value.trim() : String(value ?? '')
+      }))
+      .filter((item) => item.value !== '')
+  })
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/stock/stock-transfer/stockTransferPage.helpers.js b/rsf-design/src/views/stock/stock-transfer/stockTransferPage.helpers.js
new file mode 100644
index 0000000..b7c3697
--- /dev/null
+++ b/rsf-design/src/views/stock/stock-transfer/stockTransferPage.helpers.js
@@ -0,0 +1,136 @@
+export const STOCK_TRANSFER_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+export function createStockTransferSearchState() {
+  return {
+    orgLoc: '',
+    tarLoc: '',
+    memo: ''
+  }
+}
+
+export function buildStockTransferSourcePageQueryParams(params = {}) {
+  const orgLoc = normalizeText(params.orgLoc || params.locCode)
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    orderBy: 'create_time desc'
+  }
+  if (orgLoc) {
+    result.locCode = orgLoc
+  }
+  return result
+}
+
+export function buildStockTransferTargetLocPageQueryParams(params = {}) {
+  const result = {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 50
+  }
+  const orgLoc = normalizeText(params.orgLoc || params.locCode)
+  const q = normalizeText(params.q)
+  if (orgLoc) {
+    result.locCode = orgLoc
+  }
+  if (q) {
+    result.q = q
+  }
+  return result
+}
+
+export function buildStockTransferTaskPayload(params = {}) {
+  return {
+    orgLoc: normalizeText(params.orgLoc),
+    tarLoc: normalizeText(params.tarLoc),
+    memo: normalizeText(params.memo)
+  }
+}
+
+export function getStockTransferDynamicFieldKey(fieldName) {
+  return `${STOCK_TRANSFER_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeStockTransferEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function normalizeStockTransferTargetLocOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records
+    .map((item) => {
+      if (item === null || item === undefined) {
+        return null
+      }
+      const value = normalizeText(typeof item === 'string' ? item : item.code || item.value || item.id)
+      if (!value) {
+        return null
+      }
+      return {
+        value,
+        label: normalizeText(typeof item === 'string' ? item : item.label || item.code || item.value || value)
+      }
+    })
+    .filter(Boolean)
+}
+
+export function attachStockTransferDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+  enabledFields.forEach((field) => {
+    dynamicValues[getStockTransferDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+  return {
+    ...record,
+    extendFields,
+    ...dynamicValues
+  }
+}
+
+export function normalizeStockTransferRow(record = {}, enabledFields = []) {
+  return attachStockTransferDynamicFields(
+    {
+      ...record,
+      locCode: normalizeText(record.locCode) || '-',
+      matnrCode: normalizeText(record.matnrCode) || '-',
+      maktx: normalizeText(record.maktx) || '-',
+      batch: normalizeText(record.batch) || '-',
+      spec: normalizeText(record.spec) || '-',
+      model: normalizeText(record.model) || '-',
+      unit: normalizeText(record.unit) || '-',
+      barcode: normalizeText(record.barcode) || '-',
+      statusText: normalizeText(record['status$'] || record.statusText || '姝e父') || '姝e父',
+      createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '-',
+      updateTimeText: normalizeText(record['updateTime$'] || record.updateTimeText || record.updateTime) || '-',
+      anfme: normalizeNumber(record.anfme),
+      workQty: normalizeNumber(record.workQty),
+      qty: normalizeNumber(record.qty),
+      memo: normalizeText(record.memo) || '-'
+    },
+    enabledFields
+  )
+}
+
+export function normalizeStockTransferDetail(record = {}, enabledFields = []) {
+  return normalizeStockTransferRow(record, enabledFields)
+}
diff --git a/rsf-design/src/views/stock/stock-transfer/stockTransferTable.columns.js b/rsf-design/src/views/stock/stock-transfer/stockTransferTable.columns.js
new file mode 100644
index 0000000..134b7dd
--- /dev/null
+++ b/rsf-design/src/views/stock/stock-transfer/stockTransferTable.columns.js
@@ -0,0 +1,83 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createStockTransferTableColumns({ enabledFields = [], handleView } = {}) {
+  const dynamicColumns = enabledFields.map((field) => ({
+    prop: `extendField__${field.fields}`,
+    label: field.fieldsAlise,
+    minWidth: 140,
+    showOverflowTooltip: true,
+    formatter: (row) => row[`extendField__${field.fields}`] || '-'
+  }))
+
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'anfme',
+      label: '甯愰潰搴撳瓨',
+      width: 120,
+      formatter: (row) => row.anfme ?? 0
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100,
+      formatter: (row) => row.unit || '-'
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => row.statusText || '-'
+    },
+    ...dynamicColumns,
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 100,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          text: '璇︽儏',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/stock/warehouse-areas-item/index.vue b/rsf-design/src/views/stock/warehouse-areas-item/index.vue
new file mode 100644
index 0000000..ae8bdd3
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-areas-item/index.vue
@@ -0,0 +1,482 @@
+<template>
+  <div class="warehouse-areas-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="loadPageData">
+        <template #left>
+          <ElSpace wrap>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :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"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WarehouseIsptResultDrawer
+      v-model:visible="isptDrawerVisible"
+      :loading="isptLoading"
+      :summary="activeRow"
+      :data="isptTableData"
+      :columns="isptColumns"
+      :pagination="isptPagination"
+      @size-change="handleIsptSizeChange"
+      @current-change="handleIsptCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import {
+    fetchEnabledFields,
+    fetchExportWarehouseAreasItemReport,
+    fetchGetWarehouseAreasItemMany,
+    fetchWarehouseAreasItemIsptPage,
+    fetchWarehouseAreasItemPage
+  } from '@/api/warehouse-areas-item'
+  import WarehouseIsptResultDrawer from './modules/warehouse-ispt-result-drawer.vue'
+  import { createWarehouseAreasItemTableColumns } from './warehouseAreasItemTable.columns'
+  import {
+    buildWarehouseAreasItemIsptQueryParams,
+    buildWarehouseAreasItemPageQueryParams,
+    buildWarehouseAreasItemPrintRows,
+    buildWarehouseAreasItemReportMeta,
+    buildWarehouseAreasItemSearchParams,
+    createWarehouseAreasItemSearchState,
+    getWarehouseAreasItemDynamicFieldKey,
+    normalizeWarehouseAreasItemEnabledFields,
+    normalizeWarehouseAreasItemIsptRow,
+    normalizeWarehouseAreasItemRow,
+    WAREHOUSE_AREAS_ITEM_REPORT_STYLE,
+    WAREHOUSE_AREAS_ITEM_REPORT_TITLE
+  } from './warehouseAreasItemPage.helpers'
+
+  defineOptions({ name: 'WarehouseAreasItem' })
+
+  const userStore = useUserStore()
+  const reportTitle = WAREHOUSE_AREAS_ITEM_REPORT_TITLE
+  const loading = ref(false)
+  const tableData = ref([])
+  const enabledFields = ref([])
+  const selectedRows = ref([])
+  const searchForm = ref(createWarehouseAreasItemSearchState())
+
+  const isptDrawerVisible = ref(false)
+  const isptLoading = ref(false)
+  const isptTableData = ref([])
+  const activeRow = ref({})
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const isptPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const reportQueryParams = computed(() => buildWarehouseAreasItemSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍒掕窡韪彿/鐗╂枡缂栫爜'
+      }
+    },
+    {
+      label: '璁″垝璺熻釜鍙�',
+      key: 'asnCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍒掕窡韪彿'
+      }
+    },
+    {
+      label: '搴撳尯鍚嶇О',
+      key: 'areaName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鏉$爜',
+      key: 'barcode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ潯鐮�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '骞冲彴鍗曞彿',
+      key: 'platOrderCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙板崟鍙�'
+      }
+    },
+    {
+      label: '骞冲彴宸ュ崟',
+      key: 'platWorkCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ钩鍙板伐鍗�'
+      }
+    },
+    {
+      label: '椤圭洰缂栫爜',
+      key: 'projectCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ」鐩紪鐮�'
+      }
+    },
+    ...enabledFields.value.map((field) => ({
+      label: field.fieldsAlise,
+      key: getWarehouseAreasItemDynamicFieldKey(field.fields),
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: `璇疯緭鍏�${field.fieldsAlise}`
+      }
+    }))
+  ])
+
+  const { columns, columnChecks, resetColumns } = useTableColumns(() =>
+    createWarehouseAreasItemTableColumns({
+      enabledFields: enabledFields.value,
+      handleViewIspt: openIsptDrawer
+    })
+  )
+
+  const isptColumns = computed(() => [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'ispectId',
+      label: '璐ㄦ鍗旾D',
+      minWidth: 120
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'label',
+      label: '鏍囩',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'dlyQty',
+      label: '閫佹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'anfme',
+      label: '纭鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'splrName',
+      label: '渚涘簲鍟�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'isptResultText',
+      label: '璐ㄦ缁撴灉',
+      minWidth: 120,
+      showOverflowTooltip: true
+    }
+  ])
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeWarehouseAreasItemEnabledFields(fields)
+    enabledFields.value.forEach((field) => {
+      const dynamicKey = getWarehouseAreasItemDynamicFieldKey(field.fields)
+      if (searchForm.value[dynamicKey] === undefined) {
+        searchForm.value[dynamicKey] = ''
+      }
+    })
+    resetColumns()
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWarehouseAreasItemPage(
+          buildWarehouseAreasItemPageQueryParams({
+            ...searchForm.value,
+            current: pagination.current,
+            pageSize: pagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        {
+          timeoutMessage: '鏀惰揣搴撳瓨鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeWarehouseAreasItemRow(record, enabledFields.value))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadIsptData() {
+    if (!activeRow.value?.id) {
+      return
+    }
+
+    isptLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWarehouseAreasItemIsptPage(
+          buildWarehouseAreasItemIsptQueryParams({
+            id: activeRow.value.id,
+            current: isptPagination.current,
+            pageSize: isptPagination.size
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: isptPagination.current,
+          size: isptPagination.size
+        },
+        {
+          timeoutMessage: '璐ㄦ缁撴灉鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      isptTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeWarehouseAreasItemIsptRow(record))
+        : []
+      updatePaginationState(isptPagination, response, isptPagination.current, isptPagination.size)
+    } finally {
+      isptLoading.value = false
+    }
+  }
+
+  function openIsptDrawer(row) {
+    activeRow.value = row
+    isptPagination.current = 1
+    isptDrawerVisible.value = true
+    loadIsptData()
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    searchForm.value = createWarehouseAreasItemSearchState()
+    enabledFields.value.forEach((field) => {
+      searchForm.value[getWarehouseAreasItemDynamicFieldKey(field.fields)] = ''
+    })
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleIsptSizeChange(size) {
+    isptPagination.size = size
+    isptPagination.current = 1
+    loadIsptData()
+  }
+
+  function handleIsptCurrentChange(current) {
+    isptPagination.current = current
+    loadIsptData()
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'warehouse-areas-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportWarehouseAreasItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords: async (payload) => {
+      if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+        return defaultResponseAdapter(await fetchGetWarehouseAreasItemMany(payload.ids)).records
+      }
+      return defaultResponseAdapter(
+        await fetchWarehouseAreasItemPage({
+          ...reportQueryParams.value,
+          current: 1,
+          pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : Number(payload?.pageSize) || 20
+        })
+      ).records
+    },
+    buildPreviewRows: (records) => buildWarehouseAreasItemPrintRows(records, enabledFields.value),
+    buildPreviewMeta: (rows) => {
+      const now = new Date()
+      return {
+        reportTitle,
+        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_ITEM_REPORT_STYLE
+        }
+      }
+    }
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildWarehouseAreasItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || WAREHOUSE_AREAS_ITEM_REPORT_STYLE.orientation
+    })
+  )
+
+  onMounted(async () => {
+    await loadEnabledFieldDefinitions()
+    await loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/stock/warehouse-areas-item/modules/warehouse-ispt-result-drawer.vue b/rsf-design/src/views/stock/warehouse-areas-item/modules/warehouse-ispt-result-drawer.vue
new file mode 100644
index 0000000..f908310
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-areas-item/modules/warehouse-ispt-result-drawer.vue
@@ -0,0 +1,44 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="璐ㄦ缁撴灉"
+    size="72%"
+    destroy-on-close
+    @update:model-value="emit('update:visible', $event)"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="璁″垝璺熻釜鍙�">{{ summary.asnCode || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="搴撳尯鍚嶇О">{{ summary.areaName || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ summary.matnrCode || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ summary.maktx || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ summary.splrBatch || '-' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="褰撳墠璐ㄦ缁撴灉">{{ summary.isptResultText || '-' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="emit('size-change', $event)"
+        @pagination:current-change="emit('current-change', $event)"
+      />
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'WarehouseIsptResultDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'size-change', 'current-change'])
+</script>
diff --git a/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemPage.helpers.js b/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemPage.helpers.js
new file mode 100644
index 0000000..e5b715b
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemPage.helpers.js
@@ -0,0 +1,201 @@
+export const WAREHOUSE_AREAS_ITEM_REPORT_TITLE = '鏀惰揣搴撳瓨鎶ヨ〃'
+export const WAREHOUSE_AREAS_ITEM_DYNAMIC_FIELD_PREFIX = 'extendField__'
+export const WAREHOUSE_AREAS_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createWarehouseAreasItemSearchState() {
+  return {
+    condition: '',
+    asnCode: '',
+    areaName: '',
+    matnrCode: '',
+    maktx: '',
+    barcode: '',
+    batch: '',
+    platOrderCode: '',
+    platWorkCode: '',
+    projectCode: '',
+    status: void 0
+  }
+}
+
+export function getWarehouseAreasItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWarehouseAreasItemDynamicFieldKey(fieldName) {
+  return `${WAREHOUSE_AREAS_ITEM_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeWarehouseAreasItemEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function buildWarehouseAreasItemSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'asnCode',
+    'areaName',
+    'matnrCode',
+    'maktx',
+    'barcode',
+    'batch',
+    'platOrderCode',
+    'platWorkCode',
+    'projectCode'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.status !== undefined && params.status !== null && params.status !== '') {
+    result.status = params.status
+  }
+
+  Object.entries(params).forEach(([key, value]) => {
+    if (!key.startsWith(WAREHOUSE_AREAS_ITEM_DYNAMIC_FIELD_PREFIX)) {
+      return
+    }
+    const normalizedValue = normalizeText(value)
+    if (normalizedValue) {
+      result[key.slice(WAREHOUSE_AREAS_ITEM_DYNAMIC_FIELD_PREFIX.length)] = normalizedValue
+    }
+  })
+
+  return result
+}
+
+export function buildWarehouseAreasItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWarehouseAreasItemSearchParams(params)
+  }
+}
+
+export function buildWarehouseAreasItemIsptQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...(params.id !== undefined ? { id: params.id } : {})
+  }
+}
+
+export function attachWarehouseAreasItemDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+
+  enabledFields.forEach((field) => {
+    dynamicValues[getWarehouseAreasItemDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+
+  return {
+    ...record,
+    ...dynamicValues
+  }
+}
+
+export function normalizeWarehouseAreasItemRow(record = {}, enabledFields = []) {
+  return attachWarehouseAreasItemDynamicFields(
+    {
+      ...record,
+      areaName: record.areaName || '-',
+      asnCode: record.asnCode || '-',
+      platWorkCode: record.platWorkCode || '-',
+      platOrderCode: record.platOrderCode || '-',
+      projectCode: record.projectCode || '-',
+      matnrCode: record.matnrCode || '-',
+      maktx: record.maktx || record.matnrName || '-',
+      splrBatch: record.splrBatch || record.splrBtch || '-',
+      batch: record.batch || '-',
+      barcode: record.trackCode || record.barcode || '-',
+      unit: record.unit || '-',
+      anfme: normalizeNumber(record.anfme),
+      workQty: normalizeNumber(record.workQty),
+      qty: normalizeNumber(record.qty),
+      isptResultText: record['isptResult$'] || '-',
+      statusText:
+        record.statusBool === true || Number(record.status) === 1
+          ? '鍚敤'
+          : record.statusBool === false || Number(record.status) === 0
+            ? '鍋滅敤'
+            : '-',
+      updateTimeText: record['updateTime$'] || record.updateTime || '-',
+      createTimeText: record['createTime$'] || record.createTime || '-'
+    },
+    enabledFields
+  )
+}
+
+export function normalizeWarehouseAreasItemIsptRow(record = {}) {
+  return {
+    ...record,
+    id: record.id,
+    ispectId: record.ispectId ?? '-',
+    matnrCode: record.matnrCode || '-',
+    maktx: record.maktx || '-',
+    label: record.label || '-',
+    splrBatch: record.splrBatch || '-',
+    dlyQty: normalizeNumber(record.dlyQty),
+    anfme: normalizeNumber(record.anfme),
+    splrName: record.splrName || '-',
+    isptResultText: record['isptResult$'] || record.isptResult$ || '-'
+  }
+}
+
+export function buildWarehouseAreasItemPrintRows(records = [], enabledFields = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+  return records.map((record) => normalizeWarehouseAreasItemRow(record, enabledFields))
+}
+
+export function buildWarehouseAreasItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  orientation = WAREHOUSE_AREAS_ITEM_REPORT_STYLE.orientation
+} = {}) {
+  return {
+    reportTitle: WAREHOUSE_AREAS_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      ...WAREHOUSE_AREAS_ITEM_REPORT_STYLE,
+      orientation
+    }
+  }
+}
diff --git a/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemTable.columns.js b/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemTable.columns.js
new file mode 100644
index 0000000..c605947
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-areas-item/warehouseAreasItemTable.columns.js
@@ -0,0 +1,113 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createWarehouseAreasItemTableColumns({
+  enabledFields = [],
+  handleViewIspt
+}) {
+  const dynamicColumns = enabledFields.map((field) => ({
+    prop: `extendField__${field.fields}`,
+    label: field.fieldsAlise,
+    minWidth: 140,
+    showOverflowTooltip: true,
+    formatter: (row) => row[`extendField__${field.fields}`] || '-'
+  }))
+
+  return [
+    {
+      type: 'selection',
+      width: 48,
+      align: 'center'
+    },
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'areaName',
+      label: '搴撳尯鍚嶇О',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'asnCode',
+      label: '璁″垝璺熻釜鍙�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'platWorkCode',
+      label: '琛屽彿',
+      width: 90,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '鎵规',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '搴旀敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '瀹炴敹鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓暟閲�',
+      width: 120,
+      align: 'right'
+    },
+    ...dynamicColumns,
+    {
+      prop: 'isptResultText',
+      label: '璐ㄦ缁撴灉',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          type: 'view',
+          text: '璐ㄦ缁撴灉',
+          onClick: () => handleViewIspt(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/stock/warehouse-stock/index.vue b/rsf-design/src/views/stock/warehouse-stock/index.vue
new file mode 100644
index 0000000..e6ab489
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-stock/index.vue
@@ -0,0 +1,503 @@
+<template>
+  <div class="warehouse-stock-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="tableData"
+        :columns="columns"
+        :pagination="pagination"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <WarehouseStockDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :summary="activeStockSummary"
+      :data="detailTableData"
+      :columns="detailColumns"
+      :pagination="detailPagination"
+      @refresh="refreshDetailData"
+      @size-change="handleDetailSizeChange"
+      @current-change="handleDetailCurrentChange"
+    />
+
+    <WarehouseStockHistoriesDrawer
+      v-model:visible="historiesDrawerVisible"
+      :loading="historiesLoading"
+      :summary="activeStockSummary"
+      :data="historiesTableData"
+      :columns="historiesColumns"
+      :pagination="historiesPagination"
+      @refresh="refreshHistoriesData"
+      @size-change="handleHistoriesSizeChange"
+      @current-change="handleHistoriesCurrentChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from 'vue'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchEnabledFields,
+    fetchWarehouseStockHistoriesPage,
+    fetchWarehouseStockInfoPage,
+    fetchWarehouseStockPage
+  } from '@/api/warehouse-stock'
+  import WarehouseStockDetailDrawer from './modules/warehouse-stock-detail-drawer.vue'
+  import WarehouseStockHistoriesDrawer from './modules/warehouse-stock-histories-drawer.vue'
+  import { createWarehouseStockTableColumns } from './warehouseStockTable.columns'
+  import {
+    buildWarehouseStockDetailQueryParams,
+    buildWarehouseStockHistoriesQueryParams,
+    buildWarehouseStockPageQueryParams,
+    createWarehouseStockSearchState,
+    getWarehouseStockAggTypeOptions,
+    getWarehouseStockDynamicFieldKey,
+    getWarehouseStockPaginationKey,
+    normalizeWarehouseEnabledFields,
+    normalizeWarehouseStockDetailRow,
+    normalizeWarehouseStockHistoryRow,
+    normalizeWarehouseStockRow
+  } from './warehouseStockPage.helpers'
+
+  defineOptions({ name: 'WarehouseStock' })
+
+  const searchForm = ref(createWarehouseStockSearchState())
+  const loading = ref(false)
+  const tableData = ref([])
+  const enabledFields = ref([])
+  const activeStockSummary = ref({})
+
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailTableData = ref([])
+
+  const historiesDrawerVisible = ref(false)
+  const historiesLoading = ref(false)
+  const historiesTableData = ref([])
+
+  const pagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const detailPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const historiesPagination = reactive({
+    current: 1,
+    size: 20,
+    total: 0
+  })
+
+  const paginationKey = getWarehouseStockPaginationKey()
+
+  const searchItems = computed(() => [
+    {
+      label: '姹囨�荤被鍨�',
+      key: 'aggType',
+      type: 'select',
+      props: {
+        clearable: false,
+        options: getWarehouseStockAggTypeOptions()
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    ...enabledFields.value.map((field) => ({
+      label: field.fieldsAlise,
+      key: getWarehouseStockDynamicFieldKey(field.fields),
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: `璇疯緭鍏�${field.fieldsAlise}`
+      }
+    }))
+  ])
+
+  function createDynamicFieldColumns() {
+    return enabledFields.value.map((field) => ({
+      prop: getWarehouseStockDynamicFieldKey(field.fields),
+      label: field.fieldsAlise,
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row[getWarehouseStockDynamicFieldKey(field.fields)] || '-'
+    }))
+  }
+
+  function createDetailColumns() {
+    return [
+      {
+        prop: 'warehouseLabel',
+        label: '浠撳簱',
+        minWidth: 120,
+        formatter: (row) => row.warehouseLabel || '-'
+      },
+      {
+        prop: 'matnrCode',
+        label: '鐗╂枡缂栫爜',
+        minWidth: 160,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'maktx',
+        label: '鐗╂枡鍚嶇О',
+        minWidth: 220,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'locCode',
+        label: '搴撲綅缂栫爜',
+        minWidth: 150,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'batch',
+        label: '鎵规',
+        minWidth: 140,
+        formatter: (row) => row.batch || '-'
+      },
+      {
+        prop: 'anfme',
+        label: '鍙敤搴撳瓨',
+        width: 120,
+        formatter: (row) => row.anfme ?? 0
+      },
+      {
+        prop: 'qty',
+        label: '搴撳瓨鏁伴噺',
+        width: 120,
+        formatter: (row) => row.qty ?? 0
+      },
+      {
+        prop: 'unit',
+        label: '鍗曚綅',
+        width: 100,
+        formatter: (row) => row.unit || '-'
+      },
+      ...createDynamicFieldColumns()
+    ]
+  }
+
+  function createHistoriesColumns() {
+    return [
+      {
+        prop: 'stockCode',
+        label: '鍗曟嵁缂栧彿',
+        minWidth: 180,
+        showOverflowTooltip: true,
+        formatter: (row) => row.stockCode || '-'
+      },
+      {
+        prop: 'matnrCode',
+        label: '鐗╂枡缂栫爜',
+        minWidth: 160,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'maktx',
+        label: '鐗╂枡鍚嶇О',
+        minWidth: 220,
+        showOverflowTooltip: true
+      },
+      {
+        prop: 'batch',
+        label: '鎵规',
+        minWidth: 140,
+        formatter: (row) => row.batch || '-'
+      },
+      {
+        prop: 'anfme',
+        label: '搴撳瓨鏁伴噺',
+        width: 120,
+        formatter: (row) => row.anfme ?? 0
+      },
+      {
+        prop: 'workQty',
+        label: '鎵ц涓暟閲�',
+        width: 120,
+        formatter: (row) => row.workQty ?? 0
+      },
+      {
+        prop: 'qty',
+        label: '宸叉敹鏁伴噺',
+        width: 120,
+        formatter: (row) => row.qty ?? 0
+      },
+      {
+        prop: 'stockUnit',
+        label: '鍗曚綅',
+        width: 100,
+        formatter: (row) => row.stockUnit || '-'
+      },
+      ...createDynamicFieldColumns(),
+      {
+        prop: 'createTimeText',
+        label: '鍒涘缓鏃堕棿',
+        minWidth: 180,
+        formatter: (row) => row.createTimeText || '-'
+      }
+    ]
+  }
+
+  const detailColumns = computed(() => createDetailColumns())
+  const historiesColumns = computed(() => createHistoriesColumns())
+
+  function openDetailDrawer(row) {
+    activeStockSummary.value = row
+    detailDrawerVisible.value = true
+    detailPagination.current = 1
+    loadDetailData()
+  }
+
+  function openHistoriesDrawer(row) {
+    activeStockSummary.value = row
+    historiesDrawerVisible.value = true
+    historiesPagination.current = 1
+    loadHistoriesData()
+  }
+
+  const { columns, columnChecks, resetColumns } = useTableColumns(() =>
+    createWarehouseStockTableColumns({
+      enabledFields: enabledFields.value,
+      handleViewDetail: openDetailDrawer,
+      handleViewHistories: openHistoriesDrawer
+    })
+  )
+
+  async function loadEnabledFieldDefinitions() {
+    const fields = await guardRequestWithMessage(fetchEnabledFields(), [], {
+      timeoutMessage: '鎵╁睍瀛楁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    enabledFields.value = normalizeWarehouseEnabledFields(fields)
+    enabledFields.value.forEach((field) => {
+      const dynamicKey = getWarehouseStockDynamicFieldKey(field.fields)
+      if (searchForm.value[dynamicKey] === undefined) {
+        searchForm.value[dynamicKey] = ''
+      }
+    })
+    resetColumns()
+  }
+
+  function updatePaginationState(target, response, fallbackCurrent, fallbackSize) {
+    target.total = Number(response?.total || 0)
+    target.current = Number(response?.current || fallbackCurrent || 1)
+    target.size = Number(response?.size || fallbackSize || target.size || 20)
+  }
+
+  async function loadPageData() {
+    loading.value = true
+    const query = buildWarehouseStockPageQueryParams({
+      ...searchForm.value,
+      [paginationKey.current]: pagination.current,
+      [paginationKey.size]: pagination.size
+    })
+
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWarehouseStockPage(query),
+        {
+          records: [],
+          total: 0,
+          current: pagination.current,
+          size: pagination.size
+        },
+        { timeoutMessage: '鍗虫椂搴撳瓨鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+      tableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeWarehouseStockRow(record, enabledFields.value))
+        : []
+      updatePaginationState(pagination, response, pagination.current, pagination.size)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  async function loadDetailData() {
+    if (!detailDrawerVisible.value || !activeStockSummary.value) {
+      return
+    }
+
+    detailLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWarehouseStockInfoPage(
+          buildWarehouseStockDetailQueryParams({
+            current: detailPagination.current,
+            pageSize: detailPagination.size,
+            aggType: searchForm.value.aggType,
+            stock: activeStockSummary.value
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: detailPagination.current,
+          size: detailPagination.size
+        },
+        { timeoutMessage: '搴撳瓨璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+
+      detailTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeWarehouseStockDetailRow(record, enabledFields.value))
+        : []
+      updatePaginationState(detailPagination, response, detailPagination.current, detailPagination.size)
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function loadHistoriesData() {
+    if (!historiesDrawerVisible.value || !activeStockSummary.value) {
+      return
+    }
+
+    historiesLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchWarehouseStockHistoriesPage(
+          buildWarehouseStockHistoriesQueryParams({
+            current: historiesPagination.current,
+            pageSize: historiesPagination.size,
+            aggType: searchForm.value.aggType,
+            stock: activeStockSummary.value
+          })
+        ),
+        {
+          records: [],
+          total: 0,
+          current: historiesPagination.current,
+          size: historiesPagination.size
+        },
+        { timeoutMessage: '搴撳瓨鍘嗗彶鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+      )
+
+      historiesTableData.value = Array.isArray(response?.records)
+        ? response.records.map((record) => normalizeWarehouseStockHistoryRow(record, enabledFields.value))
+        : []
+      updatePaginationState(
+        historiesPagination,
+        response,
+        historiesPagination.current,
+        historiesPagination.size
+      )
+    } finally {
+      historiesLoading.value = false
+    }
+  }
+
+  async function refreshData() {
+    await loadPageData()
+  }
+
+  async function refreshDetailData() {
+    await loadDetailData()
+  }
+
+  async function refreshHistoriesData() {
+    await loadHistoriesData()
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleReset() {
+    const resetState = createWarehouseStockSearchState()
+    Object.keys(searchForm.value).forEach((key) => {
+      if (key.startsWith('extendField__')) {
+        searchForm.value[key] = ''
+      }
+    })
+    Object.assign(searchForm.value, resetState)
+    pagination.current = 1
+    pagination.size = 20
+    loadPageData()
+  }
+
+  function handleSizeChange(size) {
+    pagination.size = size
+    pagination.current = 1
+    loadPageData()
+  }
+
+  function handleCurrentChange(current) {
+    pagination.current = current
+    loadPageData()
+  }
+
+  function handleDetailSizeChange(size) {
+    detailPagination.size = size
+    detailPagination.current = 1
+    loadDetailData()
+  }
+
+  function handleDetailCurrentChange(current) {
+    detailPagination.current = current
+    loadDetailData()
+  }
+
+  function handleHistoriesSizeChange(size) {
+    historiesPagination.size = size
+    historiesPagination.current = 1
+    loadHistoriesData()
+  }
+
+  function handleHistoriesCurrentChange(current) {
+    historiesPagination.current = current
+    loadHistoriesData()
+  }
+
+  onMounted(async () => {
+    await loadEnabledFieldDefinitions()
+    await loadPageData()
+  })
+</script>
diff --git a/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-detail-drawer.vue b/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-detail-drawer.vue
new file mode 100644
index 0000000..9d44483
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-detail-drawer.vue
@@ -0,0 +1,47 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨璇︽儏"
+    size="80%"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="4" border>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ summary.matnrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ summary.maktx || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="浠撳簱">{{ summary.warehouseLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ summary.batch || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <div class="flex justify-end">
+        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+      </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>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-histories-drawer.vue b/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-histories-drawer.vue
new file mode 100644
index 0000000..0c12fe9
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-stock/modules/warehouse-stock-histories-drawer.vue
@@ -0,0 +1,48 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="搴撳瓨鍘嗗彶"
+    size="80%"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="4" border>
+        <ElDescriptionsItem label="鍗曟嵁缂栧彿">{{ data[0]?.stockCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ summary.matnrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ summary.maktx || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="浠撳簱">{{ summary.warehouseLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ summary.batch || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <div class="flex justify-end">
+        <ElButton :loading="loading" @click="$emit('refresh')">鍒锋柊</ElButton>
+      </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>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    summary: { type: Object, default: () => ({}) },
+    data: { type: Array, default: () => [] },
+    columns: { type: Array, default: () => [] },
+    pagination: { type: Object, default: () => ({ current: 1, size: 20, total: 0 }) }
+  })
+
+  const emit = defineEmits(['update:visible', 'refresh', 'size-change', 'current-change'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/stock/warehouse-stock/warehouseStockPage.helpers.js b/rsf-design/src/views/stock/warehouse-stock/warehouseStockPage.helpers.js
new file mode 100644
index 0000000..09e27a6
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-stock/warehouseStockPage.helpers.js
@@ -0,0 +1,186 @@
+export const WAREHOUSE_STOCK_REPORT_TITLE = '鍗虫椂搴撳瓨鎶ヨ〃'
+export const WAREHOUSE_STOCK_DYNAMIC_FIELD_PREFIX = 'extendField__'
+
+const AGG_TYPE_OPTIONS = [
+  { label: '鎸夌墿鏂欐眹鎬�', value: 'matnr' },
+  { label: '鎸変緵搴斿晢姹囨��', value: 'supplier' },
+  { label: '鎸変粨搴撴眹鎬�', value: 'warehouse' },
+  { label: '鎸夋壒娆℃眹鎬�', value: 'batch' },
+  { label: '鎸夊姩鎬佹墿灞曞瓧娈垫眹鎬�', value: 'fieldsIndex' }
+]
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+export function createWarehouseStockSearchState() {
+  return {
+    aggType: 'matnr',
+    matnrCode: '',
+    maktx: '',
+    batch: ''
+  }
+}
+
+export function getWarehouseStockPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getWarehouseStockAggTypeOptions() {
+  return AGG_TYPE_OPTIONS
+}
+
+export function getWarehouseStockDynamicFieldKey(fieldName) {
+  return `${WAREHOUSE_STOCK_DYNAMIC_FIELD_PREFIX}${fieldName}`
+}
+
+export function normalizeWarehouseEnabledFields(fields = []) {
+  if (!Array.isArray(fields)) {
+    return []
+  }
+  return fields
+    .map((item) => ({
+      fields: normalizeText(item.fields),
+      fieldsAlise: normalizeText(item.fieldsAlise || item.fieldsAlias || item.fields)
+    }))
+    .filter((item) => item.fields)
+}
+
+export function buildWarehouseStockSearchParams(params = {}) {
+  const result = {
+    aggType: normalizeText(params.aggType) || 'matnr'
+  }
+
+  const matnrCode = normalizeText(params.matnrCode)
+  const maktx = normalizeText(params.maktx)
+  const batch = normalizeText(params.batch)
+
+  if (matnrCode) {
+    result.matnrCode = matnrCode
+  }
+  if (maktx) {
+    result.maktx = maktx
+  }
+  if (batch) {
+    result.batch = batch
+  }
+
+  Object.entries(params).forEach(([key, value]) => {
+    if (!key.startsWith(WAREHOUSE_STOCK_DYNAMIC_FIELD_PREFIX)) {
+      return
+    }
+    const normalizedValue = normalizeText(value)
+    if (normalizedValue) {
+      result[key.slice(WAREHOUSE_STOCK_DYNAMIC_FIELD_PREFIX.length)] = normalizedValue
+    }
+  })
+
+  return result
+}
+
+export function buildWarehouseStockPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildWarehouseStockSearchParams(params)
+  }
+}
+
+export function buildWarehouseStockDetailQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    aggType: normalizeText(params.aggType) || 'matnr',
+    stock: params.stock || {}
+  }
+}
+
+export function buildWarehouseStockHistoriesQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    aggType: normalizeText(params.aggType) || 'matnr',
+    stock: params.stock || {}
+  }
+}
+
+export function attachWarehouseStockDynamicFields(record = {}, enabledFields = []) {
+  const extendFields = record.extendFields && typeof record.extendFields === 'object' ? record.extendFields : {}
+  const dynamicValues = {}
+  enabledFields.forEach((field) => {
+    dynamicValues[getWarehouseStockDynamicFieldKey(field.fields)] = extendFields[field.fields] || ''
+  })
+  return {
+    ...record,
+    ...dynamicValues
+  }
+}
+
+export function normalizeWarehouseStockRow(record = {}, enabledFields = []) {
+  return attachWarehouseStockDynamicFields(
+    {
+      ...record,
+      warehouseLabel: record['warehouse$'] || record.warehouse || '',
+      matnrCode: record.matnrCode || '',
+      maktx: record.maktx || '',
+      batch: record.batch || '',
+      unit: record.unit || '',
+      spec: record.spec || '',
+      model: record.model || '',
+      fieldsIndex: record.fieldsIndex || '',
+      anfme: normalizeNumber(record.anfme),
+      qty: normalizeNumber(record.qty),
+      workQty: normalizeNumber(record.workQty),
+      updateTimeText: record['updateTime$'] || record.updateTime || ''
+    },
+    enabledFields
+  )
+}
+
+export function normalizeWarehouseStockDetailRow(record = {}, enabledFields = []) {
+  return attachWarehouseStockDynamicFields(
+    {
+      ...record,
+      warehouseLabel: record['warehouse$'] || record.warehouse || '',
+      locCode: record.locCode || '',
+      matnrCode: record.matnrCode || '',
+      maktx: record.maktx || '',
+      batch: record.batch || '',
+      unit: record.unit || '',
+      qty: normalizeNumber(record.qty),
+      anfme: normalizeNumber(record.anfme),
+      updateTimeText: record['updateTime$'] || record.updateTime || ''
+    },
+    enabledFields
+  )
+}
+
+export function normalizeWarehouseStockHistoryRow(record = {}, enabledFields = []) {
+  return attachWarehouseStockDynamicFields(
+    {
+      ...record,
+      stockCode: record.stockCode || record.orderCode || '',
+      orderCode: record.orderCode || '',
+      matnrCode: record.matnrCode || '',
+      maktx: record.maktx || '',
+      batch: record.batch || '',
+      stockUnit: record.stockUnit || record.unit || '',
+      qty: normalizeNumber(record.qty),
+      workQty: normalizeNumber(record.workQty),
+      createTimeText: record['createTime$'] || record.createTime || '',
+      updateTimeText: record['updateTime$'] || record.updateTime || ''
+    },
+    enabledFields
+  )
+}
diff --git a/rsf-design/src/views/stock/warehouse-stock/warehouseStockTable.columns.js b/rsf-design/src/views/stock/warehouse-stock/warehouseStockTable.columns.js
new file mode 100644
index 0000000..c69ed19
--- /dev/null
+++ b/rsf-design/src/views/stock/warehouse-stock/warehouseStockTable.columns.js
@@ -0,0 +1,93 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createWarehouseStockTableColumns({
+  enabledFields = [],
+  handleViewDetail,
+  handleViewHistories
+}) {
+  const dynamicColumns = enabledFields.map((field) => ({
+    prop: `extendField__${field.fields}`,
+    label: field.fieldsAlise,
+    minWidth: 140,
+    showOverflowTooltip: true,
+    formatter: (row) => row[`extendField__${field.fields}`] || '-'
+  }))
+
+  return [
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'warehouseLabel',
+      label: '浠撳簱',
+      minWidth: 150,
+      formatter: (row) => row.warehouseLabel || '-'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 140,
+      formatter: (row) => row.batch || '-'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 100,
+      formatter: (row) => row.unit || '-'
+    },
+    {
+      prop: 'anfme',
+      label: '鍙敤搴撳瓨',
+      width: 120,
+      formatter: (row) => row.anfme ?? 0
+    },
+    {
+      prop: 'qty',
+      label: '搴撳瓨鏁伴噺',
+      width: 120,
+      formatter: (row) => row.qty ?? 0
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц涓簱瀛�',
+      width: 120,
+      formatter: (row) => row.workQty ?? 0
+    },
+    ...dynamicColumns,
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鍘嗗彶璁板綍',
+      width: 130,
+      fixed: 'right',
+      formatter: (row) =>
+        h('div', { class: 'flex justify-end gap-2' }, [
+          h(ArtButtonTable, {
+            type: 'view',
+            text: '搴撳瓨璇︽儏',
+            onClick: () => handleViewDetail(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'view',
+            text: '鍘嗗彶璁板綍',
+            onClick: () => handleViewHistories(row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/ai-mcp-mount/aiMcpMountPage.helpers.js b/rsf-design/src/views/system/ai-mcp-mount/aiMcpMountPage.helpers.js
new file mode 100644
index 0000000..182c51c
--- /dev/null
+++ b/rsf-design/src/views/system/ai-mcp-mount/aiMcpMountPage.helpers.js
@@ -0,0 +1,122 @@
+export function createAiMcpMountSearchState() {
+  return {
+    condition: '',
+    transportType: '',
+    status: ''
+  }
+}
+
+export function getAiMcpMountPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getAiMcpMountTransportOptions() {
+  return [
+    { label: 'SSE_HTTP', value: 'SSE_HTTP' },
+    { label: 'STDIO', value: 'STDIO' },
+    { label: 'BUILTIN', value: 'BUILTIN' }
+  ]
+}
+
+export function getAiMcpMountHealthMeta(healthStatus) {
+  if (healthStatus === 'HEALTHY') {
+    return { text: '鍋ュ悍', type: 'success' }
+  }
+  if (healthStatus === 'UNHEALTHY') {
+    return { text: '寮傚父', type: 'danger' }
+  }
+  return { text: '鏈祴璇�', type: 'info' }
+}
+
+export function buildAiMcpMountSearchParams(params = {}) {
+  return {
+    condition: params.condition?.trim?.() || '',
+    transportType: params.transportType || '',
+    status: params.status === '' || params.status === undefined || params.status === null ? '' : Number(params.status)
+  }
+}
+
+export function buildAiMcpMountPageQueryParams(params = {}) {
+  const normalized = buildAiMcpMountSearchParams(params)
+  return {
+    current: Number(params.current || 1),
+    pageSize: Number(params.pageSize || params.size || 20),
+    condition: normalized.condition,
+    transportType: normalized.transportType,
+    status: normalized.status === '' ? null : normalized.status
+  }
+}
+
+export function buildAiMcpMountDialogModel(record = {}) {
+  return {
+    id: record.id ?? null,
+    name: record.name || '',
+    transportType: record.transportType || 'SSE_HTTP',
+    builtinCode: record.builtinCode || '',
+    serverUrl: record.serverUrl || '',
+    endpoint: record.endpoint || '/sse',
+    command: record.command || '',
+    argsJson: record.argsJson || '',
+    envJson: record.envJson || '',
+    headersJson: record.headersJson || '',
+    requestTimeoutMs: record.requestTimeoutMs ?? 60000,
+    healthStatus: record.healthStatus || 'NOT_TESTED',
+    'lastTestTime$': record['lastTestTime$'] || '',
+    lastTestMessage: record.lastTestMessage || '',
+    lastInitElapsedMs: record.lastInitElapsedMs ?? null,
+    sort: record.sort ?? 0,
+    status: record.status ?? 1,
+    memo: record.memo || '',
+    updateBy: record.updateBy || '',
+    'updateTime$': record['updateTime$'] || ''
+  }
+}
+
+export function buildAiMcpMountSavePayload(form = {}) {
+  return {
+    id: form.id ?? null,
+    name: form.name?.trim?.() || '',
+    transportType: form.transportType || 'SSE_HTTP',
+    builtinCode: form.builtinCode?.trim?.() || '',
+    serverUrl: form.serverUrl?.trim?.() || '',
+    endpoint: form.endpoint?.trim?.() || '/sse',
+    command: form.command?.trim?.() || '',
+    argsJson: form.argsJson?.trim?.() || '',
+    envJson: form.envJson?.trim?.() || '',
+    headersJson: form.headersJson?.trim?.() || '',
+    requestTimeoutMs: Number(form.requestTimeoutMs || 60000),
+    sort: Number(form.sort || 0),
+    status: Number(form.status ?? 1),
+    memo: form.memo?.trim?.() || ''
+  }
+}
+
+export function normalizeAiMcpMountRow(row = {}) {
+  const healthMeta = getAiMcpMountHealthMeta(row.healthStatus)
+  const statusBool = row.statusBool ?? row.status === 1
+  const targetLabel = row.transportType === 'BUILTIN'
+    ? row.builtinCode || '--'
+    : row.transportType === 'STDIO'
+      ? row.command || '--'
+      : row.serverUrl || '--'
+
+  return {
+    ...row,
+    endpoint: row.endpoint || '/sse',
+    command: row.command || '',
+    serverUrl: row.serverUrl || '',
+    memo: row.memo || '',
+    statusBool,
+    statusText: statusBool ? '鍚敤' : '鍋滅敤',
+    statusType: statusBool ? 'success' : 'info',
+    healthText: healthMeta.text,
+    healthType: healthMeta.type,
+    transportText: row['transportType$'] || row.transportType || '--',
+    targetLabel,
+    'lastTestTime$': row['lastTestTime$'] || '',
+    'updateTime$': row['updateTime$'] || ''
+  }
+}
diff --git a/rsf-design/src/views/system/ai-mcp-mount/index.vue b/rsf-design/src/views/system/ai-mcp-mount/index.vue
new file mode 100644
index 0000000..9170481
--- /dev/null
+++ b/rsf-design/src/views/system/ai-mcp-mount/index.vue
@@ -0,0 +1,347 @@
+<template>
+  <div class="ai-mcp-mount-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <div class="mb-5 flex flex-wrap items-center justify-between gap-4">
+        <div>
+          <h3 class="text-lg font-semibold text-[var(--art-gray-900)]">MCP 鎸傝浇</h3>
+          <p class="mt-1 text-sm text-[var(--art-gray-500)]">鎸変紶杈撶被鍨嬬鐞� MCP 鎸傝浇銆佽繛閫氭�у拰宸ュ叿棰勮銆�</p>
+        </div>
+
+        <ElSpace wrap>
+          <ElButton v-auth="'save'" @click="openCreateDialog" v-ripple>鏂板缓鎸傝浇</ElButton>
+          <ElButton :loading="loading" @click="refreshData" v-ripple>鍒锋柊</ElButton>
+        </ElSpace>
+      </div>
+
+      <div v-loading="loading" class="space-y-6">
+        <ElEmpty v-if="!groupedRecords.length" description="鏆傛棤 MCP 鎸傝浇鏁版嵁" :image-size="110" />
+
+        <section v-for="group in groupedRecords" :key="group.key" class="space-y-4">
+          <div>
+            <h4 class="text-base font-semibold text-[var(--art-gray-900)]">{{ group.title }}</h4>
+            <p class="mt-1 text-sm text-[var(--art-gray-500)]">{{ group.description }}</p>
+          </div>
+
+          <div class="grid gap-5 md:grid-cols-2 2xl:grid-cols-3">
+            <article
+              v-for="item in group.records"
+              :key="item.id"
+              class="rounded-3xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-5 shadow-[0_12px_40px_rgba(15,23,42,0.04)]"
+            >
+              <div class="flex items-start justify-between gap-4">
+                <div class="min-w-0">
+                  <div class="flex items-center gap-3">
+                    <div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-emerald-50 text-emerald-600">
+                      <ArtSvgIcon icon="ri:plug-2-line" class="text-xl" />
+                    </div>
+                    <div class="min-w-0">
+                      <h4 class="truncate text-base font-semibold text-[var(--art-gray-900)]">{{ item.name || '--' }}</h4>
+                      <p class="mt-1 truncate text-sm text-[var(--art-gray-500)]">{{ item.transportText }}</p>
+                    </div>
+                  </div>
+                </div>
+
+                <div class="flex flex-wrap justify-end gap-2">
+                  <ElTag :type="item.statusType" effect="light">{{ item.statusText }}</ElTag>
+                  <ElTag :type="item.healthType" effect="light">{{ item.healthText }}</ElTag>
+                </div>
+              </div>
+
+              <div class="mt-4 grid gap-3 text-sm sm:grid-cols-2">
+                <div class="rounded-2xl bg-[var(--art-main-bg-color)]/70 p-3 ring-1 ring-inset ring-[var(--art-border-color)]">
+                  <p class="text-xs text-[var(--art-gray-500)]">鐩爣鍦板潃</p>
+                  <p class="mt-2 break-all text-[var(--art-gray-900)]">{{ item.targetLabel || '--' }}</p>
+                </div>
+                <div class="rounded-2xl bg-[var(--art-main-bg-color)]/70 p-3 ring-1 ring-inset ring-[var(--art-border-color)]">
+                  <p class="text-xs text-[var(--art-gray-500)]">鏈�杩戞祴璇�</p>
+                  <p class="mt-2 text-[var(--art-gray-900)]">{{ item['lastTestTime$'] || '鏈祴璇�' }}</p>
+                </div>
+              </div>
+
+              <div class="mt-4 grid gap-3 text-sm sm:grid-cols-3">
+                <div class="rounded-2xl bg-slate-50 px-3 py-2">
+                  <p class="text-xs text-[var(--art-gray-500)]">瓒呮椂</p>
+                  <p class="mt-1 font-medium text-[var(--art-gray-900)]">{{ item.requestTimeoutMs ?? '--' }} ms</p>
+                </div>
+                <div class="rounded-2xl bg-slate-50 px-3 py-2">
+                  <p class="text-xs text-[var(--art-gray-500)]">鎺掑簭</p>
+                  <p class="mt-1 font-medium text-[var(--art-gray-900)]">{{ item.sort ?? '--' }}</p>
+                </div>
+                <div class="rounded-2xl bg-slate-50 px-3 py-2">
+                  <p class="text-xs text-[var(--art-gray-500)]">鍒濆鍖栬�楁椂</p>
+                  <p class="mt-1 font-medium text-[var(--art-gray-900)]">{{ item.lastInitElapsedMs ?? '--' }}</p>
+                </div>
+              </div>
+
+              <div class="mt-4 rounded-2xl bg-amber-50/80 px-4 py-3">
+                <p class="text-xs text-[var(--art-gray-500)]">澶囨敞</p>
+                <p class="mt-2 line-clamp-3 text-sm leading-6 text-[var(--art-gray-900)]">{{ item.memo || '--' }}</p>
+              </div>
+
+              <div class="mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-[var(--art-border-color)] pt-4">
+                <div class="text-xs text-[var(--art-gray-500)]">{{ item['updateTime$'] || '--' }}</div>
+
+                <ElSpace wrap>
+                  <ElButton text @click="openDetailDialog(item)">璇︽儏</ElButton>
+                  <ElButton v-auth="'update'" text @click="openEditDialog(item)">缂栬緫</ElButton>
+                  <ElButton
+                    v-auth="'update'"
+                    text
+                    :loading="connectivityTestingId === item.id"
+                    @click="handleConnectivityTest(item)"
+                  >
+                    杩為�氭�ф祴璇�
+                  </ElButton>
+                  <ElButton v-auth="'list'" text @click="openToolsDrawer(item)">宸ュ叿棰勮</ElButton>
+                  <ElButton v-auth="'remove'" text type="danger" @click="handleDelete(item)">鍒犻櫎</ElButton>
+                </ElSpace>
+              </div>
+            </article>
+          </div>
+        </section>
+      </div>
+
+      <div class="mt-6 flex justify-end">
+        <ElPagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :current-page="pagination.current"
+          :page-size="pagination.size"
+          :total="pagination.total"
+          :page-sizes="[20, 50, 100]"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+
+      <AiMcpMountDialog
+        v-model:visible="dialogVisible"
+        :mode="dialogMode"
+        :mcp-mount-data="currentMcpMountData"
+        @submit="handleDialogSubmit"
+      />
+
+      <AiMcpToolsDrawer
+        v-model:visible="toolsDrawerVisible"
+        :mount-id="currentToolsMount.id"
+        :mount-name="currentToolsMount.name"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchAiMcpMountPage,
+    fetchDeleteAiMcpMount,
+    fetchGetAiMcpMountDetail,
+    fetchSaveAiMcpMount,
+    fetchTestAiMcpConnectivity,
+    fetchUpdateAiMcpMount
+  } from '@/api/ai-config'
+  import AiMcpMountDialog from './modules/ai-mcp-mount-dialog.vue'
+  import AiMcpToolsDrawer from './modules/ai-mcp-tools-drawer.vue'
+  import {
+    buildAiMcpMountDialogModel,
+    buildAiMcpMountPageQueryParams,
+    buildAiMcpMountSearchParams,
+    createAiMcpMountSearchState,
+    getAiMcpMountPaginationKey,
+    normalizeAiMcpMountRow
+  } from './aiMcpMountPage.helpers'
+
+  defineOptions({ name: 'AiMcpMount' })
+
+  const searchForm = ref(createAiMcpMountSearchState())
+  const dialogVisible = ref(false)
+  const dialogMode = ref('create')
+  const currentMcpMountData = ref(buildAiMcpMountDialogModel())
+  const toolsDrawerVisible = ref(false)
+  const currentToolsMount = ref({ id: null, name: '' })
+  const connectivityTestingId = ref(null)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悕绉�'
+      }
+    },
+    {
+      label: '浼犺緭绫诲瀷',
+      key: 'transportType',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: 'SSE_HTTP', value: 'SSE_HTTP' },
+          { label: 'STDIO', value: 'STDIO' },
+          { label: 'BUILTIN', value: 'BUILTIN' }
+        ]
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鍚敤', value: 1 },
+          { label: '鍋滅敤', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  const {
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchAiMcpMountPage,
+      apiParams: buildAiMcpMountPageQueryParams(searchForm.value),
+      paginationKey: getAiMcpMountPaginationKey()
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeAiMcpMountRow(item))
+      }
+    }
+  })
+
+  const groupedRecords = computed(() => {
+    const groups = [
+      { key: 'BUILTIN', title: '鍐呯疆 MCP', description: '绯荤粺鍐呯疆鐨勬爣鍑� MCP 鎸傝浇銆�' },
+      { key: 'SSE_HTTP', title: 'SSE / HTTP', description: '閫氳繃鏈嶅姟鍦板潃鍜� SSE 绔偣鎺ュ叆鐨� MCP銆�' },
+      { key: 'STDIO', title: 'STDIO', description: '閫氳繃鏈湴鍛戒护鍚姩鐨� MCP銆�' }
+    ]
+    return groups
+      .map((group) => ({
+        ...group,
+        records: data.value.filter((item) => item.transportType === group.key)
+      }))
+      .filter((group) => group.records.length > 0)
+  })
+
+  async function openEditDialog(record) {
+    try {
+      currentMcpMountData.value = buildAiMcpMountDialogModel(await fetchGetAiMcpMountDetail(record.id))
+      dialogMode.value = 'edit'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  async function openDetailDialog(record) {
+    try {
+      currentMcpMountData.value = buildAiMcpMountDialogModel(await fetchGetAiMcpMountDetail(record.id))
+      dialogMode.value = 'show'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  function openCreateDialog() {
+    currentMcpMountData.value = buildAiMcpMountDialogModel()
+    dialogMode.value = 'create'
+    dialogVisible.value = true
+  }
+
+  function openToolsDrawer(record) {
+    currentToolsMount.value = {
+      id: record.id,
+      name: record.name || ''
+    }
+    toolsDrawerVisible.value = true
+  }
+
+  async function handleDialogSubmit(payload) {
+    try {
+      if (dialogMode.value === 'edit') {
+        await fetchUpdateAiMcpMount(payload)
+        ElMessage.success('淇敼鎴愬姛')
+        dialogVisible.value = false
+        await refreshUpdate()
+        return
+      }
+      await fetchSaveAiMcpMount(payload)
+      ElMessage.success('鏂板鎴愬姛')
+      dialogVisible.value = false
+      await refreshCreate()
+    } catch {
+      return
+    }
+  }
+
+  async function handleDelete(record) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄ゆ寕杞姐��${record.name || record.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteAiMcpMount(record.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  async function handleConnectivityTest(record) {
+    connectivityTestingId.value = record.id
+    try {
+      const result = await guardRequestWithMessage(fetchTestAiMcpConnectivity(record.id), null, {
+        timeoutMessage: '杩為�氭�ф祴璇曡秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      ElMessage.success(result?.message || '杩為�氭�ф祴璇曟垚鍔�')
+      await refreshUpdate()
+    } catch (error) {
+      ElMessage.error(error?.message || '杩為�氭�ф祴璇曞け璐�')
+    } finally {
+      connectivityTestingId.value = null
+    }
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildAiMcpMountSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createAiMcpMountSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-mount-dialog.vue b/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-mount-dialog.vue
new file mode 100644
index 0000000..eba8b9a
--- /dev/null
+++ b/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-mount-dialog.vue
@@ -0,0 +1,292 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="900px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+      :disabled="isReadonly"
+    />
+
+    <div v-if="!isReadonly" class="mt-4 rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4">
+      <div class="flex flex-wrap items-center justify-between gap-3">
+        <div>
+          <div class="text-sm font-semibold text-[var(--art-gray-900)]">鑽夌杩為�氭�ф祴璇�</div>
+          <div class="mt-1 text-xs text-[var(--art-gray-500)]">淇濆瓨鍓嶅厛鏍¢獙褰撳墠鎸傝浇閰嶇疆鏄惁鍙繛閫氥��</div>
+        </div>
+        <ElButton :loading="draftTesting" @click="handleDraftValidate">鑽夌杩為�氭�ф祴璇�</ElButton>
+      </div>
+      <ElAlert v-if="draftValidateResult" class="mt-4" :type="draftValidateResult.healthStatus === 'HEALTHY' ? 'success' : 'error'" :closable="false">
+        <div class="space-y-1 text-sm">
+          <div>{{ draftValidateResult.message || '--' }}</div>
+          <div v-if="draftValidateResult.initElapsedMs !== undefined && draftValidateResult.initElapsedMs !== null">
+            鍒濆鍖栬�楁椂 {{ draftValidateResult.initElapsedMs }} ms
+          </div>
+          <div v-if="draftValidateResult.testedAt">{{ draftValidateResult.testedAt }}</div>
+        </div>
+      </ElAlert>
+    </div>
+
+    <div class="mt-4 rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4">
+      <div class="text-sm font-semibold text-[var(--art-gray-900)]">杩愯鏃剁姸鎬�</div>
+      <div class="mt-4 grid gap-4 md:grid-cols-2">
+        <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+          <div class="text-xs text-[var(--art-gray-500)]">鍋ュ悍鐘舵��</div>
+          <div class="mt-2 text-sm text-[var(--art-gray-900)]">{{ form.healthStatus || 'NOT_TESTED' }}</div>
+        </div>
+        <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+          <div class="text-xs text-[var(--art-gray-500)]">鏈�杩戞祴璇曟椂闂�</div>
+          <div class="mt-2 text-sm text-[var(--art-gray-900)]">{{ form['lastTestTime$'] || '--' }}</div>
+        </div>
+        <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+          <div class="text-xs text-[var(--art-gray-500)]">鏈�杩戝垵濮嬪寲鑰楁椂</div>
+          <div class="mt-2 text-sm text-[var(--art-gray-900)]">{{ form.lastInitElapsedMs ?? '--' }}</div>
+        </div>
+        <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+          <div class="text-xs text-[var(--art-gray-500)]">鏈�杩戞洿鏂版椂闂�</div>
+          <div class="mt-2 text-sm text-[var(--art-gray-900)]">{{ form['updateTime$'] || '--' }}</div>
+        </div>
+      </div>
+      <div class="mt-4 rounded-xl bg-[var(--art-main-bg-color)] p-3">
+        <div class="text-xs text-[var(--art-gray-500)]">鏈�杩戞祴璇曚俊鎭�</div>
+        <div class="mt-2 whitespace-pre-wrap break-all text-sm text-[var(--art-gray-900)]">{{ form.lastTestMessage || '--' }}</div>
+      </div>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ isReadonly ? '鍏抽棴' : '鍙栨秷' }}</ElButton>
+        <ElButton v-if="!isReadonly" type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { ElMessage } from 'element-plus'
+  import { fetchValidateAiMcpDraftConnectivity } from '@/api/ai-config'
+  import {
+    buildAiMcpMountDialogModel,
+    buildAiMcpMountSavePayload,
+    getAiMcpMountTransportOptions
+  } from '../aiMcpMountPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    mode: { type: String, default: 'create' },
+    mcpMountData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(buildAiMcpMountDialogModel())
+  const draftTesting = ref(false)
+  const draftValidateResult = ref(null)
+
+  const isReadonly = computed(() => props.mode === 'show')
+  const dialogTitle = computed(() => {
+    if (props.mode === 'edit') return '缂栬緫鎸傝浇'
+    if (props.mode === 'show') return '鎸傝浇璇︽儏'
+    return '鏂板缓鎸傝浇'
+  })
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],
+    transportType: [{ required: true, message: '璇烽�夋嫨浼犺緭绫诲瀷', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => {
+    const items = [
+      {
+        label: '鍚嶇О',
+        key: 'name',
+        type: 'input',
+        props: { clearable: true, placeholder: '璇疯緭鍏ュ悕绉�' }
+      },
+      {
+        label: '浼犺緭绫诲瀷',
+        key: 'transportType',
+        type: 'select',
+        props: {
+          options: getAiMcpMountTransportOptions(),
+          placeholder: '璇烽�夋嫨浼犺緭绫诲瀷'
+        }
+      }
+    ]
+
+    if (form.transportType === 'BUILTIN') {
+      items.push({
+        label: '鍐呯疆 MCP 缂栫爜',
+        key: 'builtinCode',
+        type: 'input',
+        span: 24,
+        props: { clearable: true, placeholder: '璇疯緭鍏ュ唴缃� MCP 缂栫爜' }
+      })
+    }
+
+    if (form.transportType === 'SSE_HTTP') {
+      items.push(
+        {
+          label: '鏈嶅姟鍦板潃',
+          key: 'serverUrl',
+          type: 'input',
+          span: 24,
+          props: { clearable: true, placeholder: '璇疯緭鍏ユ湇鍔″湴鍧�' }
+        },
+        {
+          label: 'SSE 绔偣',
+          key: 'endpoint',
+          type: 'input',
+          props: { clearable: true, placeholder: '璇疯緭鍏� SSE 绔偣' }
+        },
+        {
+          label: '璇锋眰澶� JSON',
+          key: 'headersJson',
+          type: 'input',
+          span: 24,
+          props: { type: 'textarea', rows: 4, placeholder: '璇疯緭鍏ヨ姹傚ご JSON' }
+        }
+      )
+    }
+
+    if (form.transportType === 'STDIO') {
+      items.push(
+        {
+          label: '鍛戒护',
+          key: 'command',
+          type: 'input',
+          span: 24,
+          props: { clearable: true, placeholder: '璇疯緭鍏ュ懡浠�' }
+        },
+        {
+          label: '鍛戒护鍙傛暟 JSON',
+          key: 'argsJson',
+          type: 'input',
+          span: 24,
+          props: { type: 'textarea', rows: 4, placeholder: '璇疯緭鍏ュ懡浠ゅ弬鏁� JSON' }
+        },
+        {
+          label: '鐜鍙橀噺 JSON',
+          key: 'envJson',
+          type: 'input',
+          span: 24,
+          props: { type: 'textarea', rows: 4, placeholder: '璇疯緭鍏ョ幆澧冨彉閲� JSON' }
+        }
+      )
+    }
+
+    items.push(
+      {
+        label: '璇锋眰瓒呮椂(ms)',
+        key: 'requestTimeoutMs',
+        type: 'number',
+        props: { min: 1000, placeholder: '璇疯緭鍏ヨ姹傝秴鏃�' }
+      },
+      {
+        label: '鎺掑簭',
+        key: 'sort',
+        type: 'number',
+        props: { min: 0, placeholder: '璇疯緭鍏ユ帓搴�' }
+      },
+      {
+        label: '鐘舵��',
+        key: 'status',
+        type: 'select',
+        props: {
+          options: [
+            { label: '鍚敤', value: 1 },
+            { label: '鍋滅敤', value: 0 }
+          ],
+          placeholder: '璇烽�夋嫨鐘舵��'
+        }
+      },
+      {
+        label: '澶囨敞',
+        key: 'memo',
+        type: 'input',
+        span: 24,
+        props: { type: 'textarea', rows: 3, placeholder: '璇疯緭鍏ュ娉�' }
+      }
+    )
+    return items
+  })
+
+  function resetForm() {
+    Object.assign(form, buildAiMcpMountDialogModel())
+    draftValidateResult.value = null
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildAiMcpMountDialogModel(props.mcpMountData))
+    draftValidateResult.value = null
+  }
+
+  async function handleDraftValidate() {
+    draftTesting.value = true
+    try {
+      draftValidateResult.value = await fetchValidateAiMcpDraftConnectivity(buildAiMcpMountSavePayload(form))
+      ElMessage.success(draftValidateResult.value?.message || '鑽夌杩為�氭�ф祴璇曟垚鍔�')
+    } catch (error) {
+      draftValidateResult.value = {
+        healthStatus: 'UNHEALTHY',
+        message: error?.message || '鑽夌杩為�氭�ф祴璇曞け璐�'
+      }
+      ElMessage.error(draftValidateResult.value.message)
+    } finally {
+      draftTesting.value = false
+    }
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', buildAiMcpMountSavePayload(form))
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.mcpMountData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-tools-drawer.vue b/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-tools-drawer.vue
new file mode 100644
index 0000000..ce571b0
--- /dev/null
+++ b/rsf-design/src/views/system/ai-mcp-mount/modules/ai-mcp-tools-drawer.vue
@@ -0,0 +1,190 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="MCP 宸ュ叿棰勮"
+    size="760px"
+    @update:model-value="handleVisibleChange"
+  >
+    <div class="space-y-4">
+      <div class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4">
+        <div>
+          <div class="text-sm font-semibold text-[var(--art-gray-900)]">{{ mountName || '褰撳墠鎸傝浇' }}</div>
+          <div class="mt-1 text-xs text-[var(--art-gray-500)]">鍙瑙堝伐鍏峰垪琛紝骞跺宸ュ叿鍏ュ弬鍋氳仈璋冩祴璇曘��</div>
+        </div>
+        <ElSpace wrap>
+          <ElButton :loading="toolsLoading" @click="loadTools">鍒锋柊宸ュ叿</ElButton>
+          <ElButton :loading="connectivityLoading" @click="handleConnectivityTest">杩為�氭�ф祴璇�</ElButton>
+        </ElSpace>
+      </div>
+
+      <ElAlert v-if="connectivityResult" :type="connectivityResult.healthStatus === 'HEALTHY' ? 'success' : 'error'" :closable="false">
+        <div class="space-y-1 text-sm">
+          <div>{{ connectivityResult.message || '--' }}</div>
+          <div v-if="connectivityResult.initElapsedMs !== undefined && connectivityResult.initElapsedMs !== null">
+            鍒濆鍖栬�楁椂 {{ connectivityResult.initElapsedMs }} ms
+          </div>
+          <div v-if="connectivityResult.testedAt">{{ connectivityResult.testedAt }}</div>
+        </div>
+      </ElAlert>
+
+      <ElSkeleton :loading="toolsLoading" animated :rows="8">
+        <ElEmpty v-if="!tools.length" description="鏆傛棤宸ュ叿淇℃伅" :image-size="100" />
+
+        <div v-else class="space-y-4">
+          <div
+            v-for="tool in tools"
+            :key="tool.name"
+            class="rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4"
+          >
+            <div class="flex flex-wrap items-start justify-between gap-3">
+              <div>
+                <div class="text-base font-semibold text-[var(--art-gray-900)]">{{ tool.name }}</div>
+                <div class="mt-1 text-sm text-[var(--art-gray-500)]">{{ tool.description || '--' }}</div>
+              </div>
+              <ElButton :loading="testingToolName === tool.name" @click="handleToolTest(tool.name)">宸ュ叿娴嬭瘯</ElButton>
+            </div>
+
+            <div class="mt-4 grid gap-4 md:grid-cols-2">
+              <div class="space-y-2">
+                <div class="text-xs text-[var(--art-gray-500)]">杈撳叆鍙傛暟 JSON</div>
+                <ElInput
+                  v-model="toolInputs[tool.name]"
+                  type="textarea"
+                  :rows="8"
+                  placeholder='璇疯緭鍏� JSON锛屼緥濡� {"taskCode":"TK001"}'
+                />
+              </div>
+              <div class="space-y-2">
+                <div class="text-xs text-[var(--art-gray-500)]">宸ュ叿杈撳嚭</div>
+                <ElInput
+                  :model-value="toolOutputs[tool.name] || ''"
+                  type="textarea"
+                  :rows="8"
+                  readonly
+                  placeholder="宸ュ叿杈撳嚭浼氭樉绀哄湪杩欓噷"
+                />
+              </div>
+            </div>
+
+            <div v-if="tool.inputSchema" class="mt-4 rounded-xl bg-[var(--art-main-bg-color)] p-3">
+              <div class="text-xs text-[var(--art-gray-500)]">杈撳叆 Schema</div>
+              <pre class="mt-2 whitespace-pre-wrap break-all text-xs leading-6 text-[var(--art-gray-900)]">{{ formatSchema(tool.inputSchema) }}</pre>
+            </div>
+          </div>
+        </div>
+      </ElSkeleton>
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchPreviewAiMcpTools, fetchTestAiMcpConnectivity, fetchTestAiMcpTool } from '@/api/ai-config'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    mountId: { type: [Number, String], default: null },
+    mountName: { type: String, default: '' }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const tools = ref([])
+  const toolsLoading = ref(false)
+  const connectivityLoading = ref(false)
+  const connectivityResult = ref(null)
+  const toolInputs = reactive({})
+  const toolOutputs = reactive({})
+  const testingToolName = ref('')
+
+  function resetState() {
+    tools.value = []
+    connectivityResult.value = null
+    testingToolName.value = ''
+    Object.keys(toolInputs).forEach((key) => delete toolInputs[key])
+    Object.keys(toolOutputs).forEach((key) => delete toolOutputs[key])
+  }
+
+  function formatSchema(schema) {
+    try {
+      return JSON.stringify(JSON.parse(schema), null, 2)
+    } catch {
+      return schema
+    }
+  }
+
+  async function loadTools() {
+    if (!props.mountId) return
+    toolsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(fetchPreviewAiMcpTools(props.mountId), [], {
+        timeoutMessage: '宸ュ叿鍒楄〃鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      tools.value = Array.isArray(response) ? response : []
+    } catch (error) {
+      tools.value = []
+      ElMessage.error(error?.message || '鑾峰彇宸ュ叿鍒楄〃澶辫触')
+    } finally {
+      toolsLoading.value = false
+    }
+  }
+
+  async function handleConnectivityTest() {
+    if (!props.mountId) return
+    connectivityLoading.value = true
+    try {
+      connectivityResult.value = await guardRequestWithMessage(fetchTestAiMcpConnectivity(props.mountId), null, {
+        timeoutMessage: '杩為�氭�ф祴璇曡秴鏃讹紝宸插仠姝㈢瓑寰�'
+      })
+      ElMessage.success(connectivityResult.value?.message || '杩為�氭�ф祴璇曟垚鍔�')
+    } catch (error) {
+      ElMessage.error(error?.message || '杩為�氭�ф祴璇曞け璐�')
+    } finally {
+      connectivityLoading.value = false
+    }
+  }
+
+  async function handleToolTest(toolName) {
+    if (!props.mountId) return
+    const inputJson = toolInputs[toolName]?.trim?.() || ''
+    if (!inputJson) {
+      ElMessage.warning('璇疯緭鍏ュ伐鍏锋祴璇曞叆鍙� JSON')
+      return
+    }
+    testingToolName.value = toolName
+    try {
+      const result = await guardRequestWithMessage(
+        fetchTestAiMcpTool(props.mountId, {
+          toolName,
+          inputJson
+        }),
+        null,
+        {
+          timeoutMessage: '宸ュ叿娴嬭瘯瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      toolOutputs[toolName] = result?.output || JSON.stringify(result || {}, null, 2)
+      ElMessage.success('宸ュ叿娴嬭瘯鎴愬姛')
+    } catch (error) {
+      toolOutputs[toolName] = error?.message || '宸ュ叿娴嬭瘯澶辫触'
+      ElMessage.error(error?.message || '宸ュ叿娴嬭瘯澶辫触')
+    } finally {
+      testingToolName.value = ''
+    }
+  }
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        void loadTools()
+      } else {
+        resetState()
+      }
+    }
+  )
+</script>
diff --git a/rsf-design/src/views/system/ai-observe/aiObservePage.helpers.js b/rsf-design/src/views/system/ai-observe/aiObservePage.helpers.js
new file mode 100644
index 0000000..e3a6320
--- /dev/null
+++ b/rsf-design/src/views/system/ai-observe/aiObservePage.helpers.js
@@ -0,0 +1,165 @@
+export const AI_OBSERVE_REPORT_TITLE = 'AI 瑙傛祴鎶ヨ〃'
+
+export function createAiObserveSearchState() {
+  return {
+    condition: '',
+    requestId: '',
+    promptCode: '',
+    userId: '',
+    status: ''
+  }
+}
+
+export function getAiObservePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildAiObserveSearchParams(params = {}) {
+  return {
+    condition: params.condition?.trim?.() || '',
+    requestId: params.requestId?.trim?.() || '',
+    promptCode: params.promptCode?.trim?.() || '',
+    userId: params.userId?.trim?.() || '',
+    status: params.status || ''
+  }
+}
+
+export function buildAiObservePageQueryParams(params = {}) {
+  const normalized = buildAiObserveSearchParams(params)
+  return {
+    current: Number(params.current || 1),
+    pageSize: Number(params.pageSize || params.size || 20),
+    condition: normalized.condition,
+    requestId: normalized.requestId,
+    promptCode: normalized.promptCode,
+    userId: normalized.userId,
+    status: normalized.status || null
+  }
+}
+
+export function getAiObserveStatusMeta(status) {
+  if (status === 'COMPLETED') {
+    return { text: '宸插畬鎴�', type: 'success' }
+  }
+  if (status === 'FAILED') {
+    return { text: '澶辫触', type: 'danger' }
+  }
+  if (status === 'ABORTED') {
+    return { text: '宸蹭腑姝�', type: 'warning' }
+  }
+  if (status === 'RUNNING') {
+    return { text: '鎵ц涓�', type: 'primary' }
+  }
+  return { text: status || '--', type: 'info' }
+}
+
+export function formatAiObserveLatency(value) {
+  const normalized = Number(value)
+  return Number.isFinite(normalized) ? `${normalized} ms` : '--'
+}
+
+export function normalizeAiObserveStats(stats = {}) {
+  return {
+    callCount: Number(stats.callCount || 0),
+    successCount: Number(stats.successCount || 0),
+    failureCount: Number(stats.failureCount || 0),
+    avgElapsedMs: Number(stats.avgElapsedMs || 0),
+    avgFirstTokenLatencyMs: Number(stats.avgFirstTokenLatencyMs || 0),
+    totalTokens: Number(stats.totalTokens || 0),
+    avgTotalTokens: Number(stats.avgTotalTokens || 0),
+    toolSuccessRate: Number(stats.toolSuccessRate || 0),
+    toolCallCount: Number(stats.toolCallCount || 0),
+    toolFailureCount: Number(stats.toolFailureCount || 0)
+  }
+}
+
+export function normalizeAiObserveRow(row = {}) {
+  const statusMeta = getAiObserveStatusMeta(row.status)
+  const userLabel = row.userLabel || row.userId$ || row.userName || (row.userId ?? '')
+  return {
+    ...row,
+    promptName: row.promptName || '',
+    promptCode: row.promptCode || '',
+    model: row.model || '',
+    requestId: row.requestId || '',
+    userLabel: String(userLabel || ''),
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    elapsedText: formatAiObserveLatency(row.elapsedMs),
+    totalTokens: row.totalTokens ?? null,
+    mountedMcpNames: row.mountedMcpNames || '',
+    errorMessage: row.errorMessage || '',
+    'createTime$': row['createTime$'] || '',
+    'updateTime$': row['updateTime$'] || ''
+  }
+}
+
+export function buildAiObserveDetail(detail = {}, fallback = {}) {
+  const merged = {
+    ...fallback,
+    ...detail
+  }
+  const statusMeta = getAiObserveStatusMeta(merged.status)
+  return {
+    requestId: merged.requestId || '',
+    sessionId: merged.sessionId ?? '',
+    promptName: merged.promptName || '',
+    promptCode: merged.promptCode || '',
+    model: merged.model || '',
+    userId: merged.userId ?? '',
+    status: merged.status || '',
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    errorCategory: merged.errorCategory || '',
+    errorStage: merged.errorStage || '',
+    errorMessage: merged.errorMessage || '',
+    mountedMcpNames: merged.mountedMcpNames || '',
+    configuredMcpCount: merged.configuredMcpCount ?? null,
+    mountedMcpCount: merged.mountedMcpCount ?? null,
+    toolCallCount: merged.toolCallCount ?? null,
+    toolSuccessCount: merged.toolSuccessCount ?? null,
+    toolFailureCount: merged.toolFailureCount ?? null,
+    elapsedMs: merged.elapsedMs ?? null,
+    elapsedText: formatAiObserveLatency(merged.elapsedMs),
+    firstTokenLatencyMs: merged.firstTokenLatencyMs ?? null,
+    firstTokenLatencyText: formatAiObserveLatency(merged.firstTokenLatencyMs),
+    promptTokens: merged.promptTokens ?? null,
+    completionTokens: merged.completionTokens ?? null,
+    totalTokens: merged.totalTokens ?? null,
+    createTimeText: merged['createTime$'] || '--',
+    updateTimeText: merged['updateTime$'] || '--',
+    mcpLogs: Array.isArray(merged.mcpLogs) ? merged.mcpLogs : []
+  }
+}
+
+export function getAiObserveReportColumns() {
+  return [
+    { prop: 'requestId', label: '璇锋眰ID' },
+    { prop: 'promptLabel', label: 'Prompt' },
+    { prop: 'model', label: '妯″瀷' },
+    { prop: 'userLabel', label: '鐢ㄦ埛' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'elapsedText', label: '鎬昏�楁椂(ms)' },
+    { prop: 'totalTokensText', label: '鎬� Tokens' },
+    { prop: 'createTimeText', label: '鍒涘缓鏃堕棿' }
+  ]
+}
+
+export function buildAiObservePrintRows(rows = []) {
+  return rows.map((item) => {
+    const normalized = normalizeAiObserveRow(item)
+    return {
+      requestId: normalized.requestId || '--',
+      promptLabel: [normalized.promptName || '--', normalized.promptCode || '--'].join(' / '),
+      model: normalized.model || '--',
+      userLabel: normalized.userLabel || '--',
+      statusText: normalized.statusText,
+      elapsedText: normalized.elapsedText,
+      totalTokensText: normalized.totalTokens ?? '--',
+      createTimeText: normalized['createTime$'] || '--'
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/ai-observe/aiObserveTable.columns.js b/rsf-design/src/views/system/ai-observe/aiObserveTable.columns.js
new file mode 100644
index 0000000..b8b3674
--- /dev/null
+++ b/rsf-design/src/views/system/ai-observe/aiObserveTable.columns.js
@@ -0,0 +1,78 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createAiObserveTableColumns({ handleView }) {
+  return [
+    { type: 'selection', width: 52, fixed: 'left' },
+    {
+      prop: 'requestId',
+      label: '璇锋眰ID',
+      minWidth: 210,
+      showOverflowTooltip: true,
+      formatter: (row) => row.requestId || '-'
+    },
+    {
+      prop: 'promptName',
+      label: 'Prompt',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => {
+        const name = row.promptName || '-'
+        const code = row.promptCode || '-'
+        return `${name} / ${code}`
+      }
+    },
+    {
+      prop: 'model',
+      label: '妯″瀷',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.model || '-'
+    },
+    {
+      prop: 'userLabel',
+      label: '鐢ㄦ埛',
+      width: 110,
+      formatter: (row) => row.userLabel || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText)
+    },
+    {
+      prop: 'elapsedText',
+      label: '鎬昏�楁椂',
+      width: 110,
+      formatter: (row) => row.elapsedText || '--'
+    },
+    {
+      prop: 'totalTokens',
+      label: '鎬� Tokens',
+      width: 110,
+      formatter: (row) => row.totalTokens ?? '--'
+    },
+    {
+      prop: 'createTime$',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      formatter: (row) => row['createTime$'] || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 70,
+      align: 'right',
+      fixed: 'right',
+      formatter: (row) =>
+        h('div', { class: 'flex justify-end' }, [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/ai-observe/index.vue b/rsf-design/src/views/system/ai-observe/index.vue
new file mode 100644
index 0000000..5833dbe
--- /dev/null
+++ b/rsf-design/src/views/system/ai-observe/index.vue
@@ -0,0 +1,279 @@
+<template>
+  <div class="ai-observe-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <div class="mb-5 grid gap-5 md:grid-cols-2 xl:grid-cols-4">
+      <ElCard
+        v-for="item in summaryCards"
+        :key="item.label"
+        class="rounded-3xl border border-[var(--art-border-color)] shadow-[0_12px_36px_rgba(15,23,42,0.04)]"
+        v-loading="statsLoading"
+      >
+        <div class="flex items-start justify-between gap-4">
+          <div class="min-w-0">
+            <div class="text-sm text-[var(--art-gray-500)]">{{ item.label }}</div>
+            <div class="mt-3 text-3xl font-semibold text-[var(--art-gray-900)]">{{ item.value }}</div>
+            <div class="mt-2 text-xs text-[var(--art-gray-500)]">{{ item.description }}</div>
+          </div>
+          <div class="flex size-12 shrink-0 items-center justify-center rounded-2xl" :class="item.iconWrapClass">
+            <ArtSvgIcon :icon="item.icon" class="text-xl" :class="item.iconClass" />
+          </div>
+        </div>
+      </ElCard>
+    </div>
+
+    <ElCard class="art-table-card">
+      <div class="mb-4">
+        <h3 class="text-lg font-semibold text-[var(--art-gray-900)]">AI 瑙傛祴鎽樿</h3>
+        <p class="mt-1 text-sm text-[var(--art-gray-500)]">瑙傚療 AI 璋冪敤鐘舵�併�佽�楁椂銆乀okens 涓� MCP 宸ュ叿鎵ц鎯呭喌銆�</p>
+      </div>
+
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshAll">
+        <template #left>
+          <ElSpace wrap>
+            <ElTag effect="plain" type="info">鏈�杩戝叡 {{ pagination.total || 0 }} 鏉¤皟鐢ㄨ褰�</ElTag>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <AiObserveDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchAiCallLogPage,
+    fetchGetAiCallLogDetail,
+    fetchGetAiCallLogMcpLogs,
+    fetchGetAiObserveStats
+  } from '@/api/ai-config'
+  import {
+    AI_OBSERVE_REPORT_TITLE,
+    buildAiObserveDetail,
+    buildAiObservePageQueryParams,
+    buildAiObserveSearchParams,
+    createAiObserveSearchState,
+    getAiObservePaginationKey,
+    normalizeAiObserveRow,
+    normalizeAiObserveStats
+  } from './aiObservePage.helpers'
+  import { createAiObserveTableColumns } from './aiObserveTable.columns'
+  import AiObserveDetailDrawer from './modules/ai-observe-detail-drawer.vue'
+
+  defineOptions({ name: 'AiObserve' })
+
+  const searchForm = ref(createAiObserveSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const statsLoading = ref(false)
+  const stats = ref(normalizeAiObserveStats())
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ姹侷D鎴� Prompt'
+      }
+    },
+    {
+      label: '璇锋眰ID',
+      key: 'requestId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ姹侷D'
+      }
+    },
+    {
+      label: 'Prompt 缂栫爜',
+      key: 'promptCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� Prompt 缂栫爜'
+      }
+    },
+    {
+      label: '鐢ㄦ埛',
+      key: 'userId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ敤鎴稩D'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鎵ц涓�', value: 'RUNNING' },
+          { label: '宸插畬鎴�', value: 'COMPLETED' },
+          { label: '澶辫触', value: 'FAILED' },
+          { label: '宸蹭腑姝�', value: 'ABORTED' }
+        ]
+      }
+    }
+  ])
+
+  const summaryCards = computed(() => [
+    {
+      label: '璋冪敤娆℃暟',
+      value: stats.value.callCount,
+      description: `鎴愬姛 ${stats.value.successCount} / 澶辫触 ${stats.value.failureCount}`,
+      icon: 'ri:pulse-line',
+      iconWrapClass: 'bg-sky-50',
+      iconClass: 'text-sky-600'
+    },
+    {
+      label: '骞冲潎鑰楁椂',
+      value: `${stats.value.avgElapsedMs} ms`,
+      description: `棣栧寘 ${stats.value.avgFirstTokenLatencyMs} ms`,
+      icon: 'ri:timer-flash-line',
+      iconWrapClass: 'bg-amber-50',
+      iconClass: 'text-amber-600'
+    },
+    {
+      label: '鎬� Tokens',
+      value: stats.value.totalTokens,
+      description: `骞冲潎 ${stats.value.avgTotalTokens.toFixed(1)} Tokens`,
+      icon: 'ri:coin-line',
+      iconWrapClass: 'bg-emerald-50',
+      iconClass: 'text-emerald-600'
+    },
+    {
+      label: '宸ュ叿鎴愬姛鐜�',
+      value: `${stats.value.toolSuccessRate.toFixed(2)}%`,
+      description: `璋冪敤 ${stats.value.toolCallCount} / 澶辫触 ${stats.value.toolFailureCount}`,
+      icon: 'ri:service-line',
+      iconWrapClass: 'bg-violet-50',
+      iconClass: 'text-violet-600'
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const [detail, mcpLogs] = await Promise.all([
+        guardRequestWithMessage(fetchGetAiCallLogDetail(row.id), null, {
+          timeoutMessage: 'AI 瑙傛祴璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }),
+        guardRequestWithMessage(fetchGetAiCallLogMcpLogs(row.id), [], {
+          timeoutMessage: 'MCP 璋冪敤鏃ュ織鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        })
+      ])
+      detailData.value = buildAiObserveDetail(
+        {
+          ...detail,
+          mcpLogs: Array.isArray(mcpLogs) ? mcpLogs : []
+        },
+        row
+      )
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇 AI 瑙傛祴璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchAiCallLogPage,
+      apiParams: buildAiObservePageQueryParams(searchForm.value),
+      paginationKey: getAiObservePaginationKey(),
+      columnsFactory: () =>
+        createAiObserveTableColumns({
+          handleView: openDetail
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeAiObserveRow(item))
+      }
+    }
+  })
+
+  async function loadStats() {
+    statsLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(fetchGetAiObserveStats(), normalizeAiObserveStats(), {
+        timeoutMessage: 'AI 瑙傛祴鎽樿鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      stats.value = normalizeAiObserveStats(response)
+    } catch (error) {
+      stats.value = normalizeAiObserveStats()
+      ElMessage.error(error?.message || '鑾峰彇 AI 瑙傛祴鎽樿澶辫触')
+    } finally {
+      statsLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildAiObserveSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createAiObserveSearchState())
+    resetSearchParams()
+  }
+
+  async function refreshAll() {
+    await Promise.all([refreshData(), loadStats()])
+  }
+
+  void loadStats()
+</script>
diff --git a/rsf-design/src/views/system/ai-observe/modules/ai-observe-detail-drawer.vue b/rsf-design/src/views/system/ai-observe/modules/ai-observe-detail-drawer.vue
new file mode 100644
index 0000000..c1ed6fd
--- /dev/null
+++ b/rsf-design/src/views/system/ai-observe/modules/ai-observe-detail-drawer.vue
@@ -0,0 +1,110 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="AI 瑙傛祴璇︽儏"
+    size="720px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="14">
+      <div class="space-y-5">
+        <ElDescriptions :column="2" border>
+          <ElDescriptionsItem label="璇锋眰ID">{{ displayData.requestId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浼氳瘽ID">{{ displayData.sessionId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="Prompt">{{ displayPrompt }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="妯″瀷">{{ displayData.model || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐢ㄦ埛">{{ displayData.userId || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎸傝浇 MCP">{{ displayData.mountedMcpNames || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閰嶇疆 MCP 鏁�">{{ displayData.configuredMcpCount ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="宸ュ叿璋冪敤">{{ displayData.toolCallCount ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎴愬姛/澶辫触">
+            {{ displayData.toolSuccessCount ?? '--' }} / {{ displayData.toolFailureCount ?? '--' }}
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="鎬昏�楁椂">{{ displayData.elapsedText }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="棣栧寘鑰楁椂">{{ displayData.firstTokenLatencyText }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="Prompt Tokens">{{ displayData.promptTokens ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="Completion Tokens">{{ displayData.completionTokens ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="Total Tokens">{{ displayData.totalTokens ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿欒鍒嗙被">{{ displayData.errorCategory || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿欒闃舵">{{ displayData.errorStage || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+
+        <ElAlert v-if="displayData.errorMessage" type="error" :closable="false" show-icon>
+          <template #title>閿欒淇℃伅</template>
+          <div class="whitespace-pre-wrap break-all text-sm">{{ displayData.errorMessage }}</div>
+        </ElAlert>
+
+        <div class="space-y-3">
+          <div class="flex items-center justify-between">
+            <h4 class="text-base font-semibold text-[var(--art-gray-900)]">MCP 璋冪敤鏃ュ織</h4>
+            <span class="text-sm text-[var(--art-gray-500)]">{{ displayData.mcpLogs.length }} 鏉�</span>
+          </div>
+
+          <ElEmpty v-if="!displayData.mcpLogs.length" description="鏆傛棤 MCP 璋冪敤鏃ュ織" :image-size="100" />
+
+          <div v-else class="space-y-3">
+            <div
+              v-for="item in displayData.mcpLogs"
+              :key="item.id"
+              class="rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4"
+            >
+              <div class="flex flex-wrap items-start justify-between gap-3">
+                <div class="space-y-1">
+                  <div class="text-sm font-semibold text-[var(--art-gray-900)]">{{ item.toolName || '--' }}</div>
+                  <div class="text-xs text-[var(--art-gray-500)]">
+                    {{ item.mountName || '--' }} 路 {{ item['createTime$'] || '--' }}
+                  </div>
+                </div>
+                <ElTag :type="item.status === 'COMPLETED' ? 'success' : 'danger'" effect="light">
+                  {{ item.status || '--' }}
+                </ElTag>
+              </div>
+
+              <div class="mt-4 grid gap-3 md:grid-cols-2">
+                <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+                  <div class="text-xs text-[var(--art-gray-500)]">杈撳叆鎽樿</div>
+                  <div class="mt-2 whitespace-pre-wrap break-all text-sm text-[var(--art-gray-900)]">
+                    {{ item.inputSummary || '--' }}
+                  </div>
+                </div>
+                <div class="rounded-xl bg-[var(--art-main-bg-color)] p-3">
+                  <div class="text-xs text-[var(--art-gray-500)]">杈撳嚭鎽樿</div>
+                  <div class="mt-2 whitespace-pre-wrap break-all text-sm text-[var(--art-gray-900)]">
+                    {{ item.outputSummary || item.errorMessage || '--' }}
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { buildAiObserveDetail } from '../aiObservePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const displayData = computed(() => buildAiObserveDetail(props.detailData))
+  const displayPrompt = computed(() => {
+    const name = displayData.value.promptName || '--'
+    const code = displayData.value.promptCode || '--'
+    return `${name} / ${code}`
+  })
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/ai-param/aiParamPage.helpers.js b/rsf-design/src/views/system/ai-param/aiParamPage.helpers.js
new file mode 100644
index 0000000..f04e148
--- /dev/null
+++ b/rsf-design/src/views/system/ai-param/aiParamPage.helpers.js
@@ -0,0 +1,225 @@
+const AI_PARAM_REPORT_TITLE = 'AI 鍙傛暟鎶ヨ〃'
+
+const PROVIDER_OPTIONS = [{ label: 'OPENAI_COMPATIBLE', value: 'OPENAI_COMPATIBLE' }]
+
+const STATUS_OPTIONS = [
+  { label: '榛樿', value: 1 },
+  { label: '鍊欓��', value: 0 }
+]
+
+const VALIDATE_STATUS_META = {
+  VALID: { text: '宸叉牎楠�', type: 'success' },
+  INVALID: { text: '鏍¢獙澶辫触', type: 'danger' },
+  NOT_TESTED: { text: '鏈牎楠�', type: 'info' }
+}
+
+function createAiParamSearchState() {
+  return {
+    condition: '',
+    providerType: '',
+    model: '',
+    status: ''
+  }
+}
+
+function createAiParamFormState() {
+  return {
+    id: null,
+    name: '',
+    providerType: 'OPENAI_COMPATIBLE',
+    baseUrl: '',
+    apiKey: '',
+    model: '',
+    temperature: 0.7,
+    topP: 1,
+    maxTokens: null,
+    timeoutMs: 60000,
+    streamingEnabled: true,
+    status: 1,
+    memo: '',
+    validateStatus: 'NOT_TESTED',
+    lastValidateMessage: '',
+    lastValidateElapsedMs: null,
+    'lastValidateTime$': '',
+    updateBy: '',
+    'updateTime$': ''
+  }
+}
+
+function getAiParamPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+function getAiParamProviderOptions() {
+  return PROVIDER_OPTIONS
+}
+
+function getAiParamStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+function getAiParamStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '榛樿', type: 'success' }
+    : { text: '鍊欓��', type: 'info' }
+}
+
+function getAiParamValidateStatusMeta(status) {
+  return VALIDATE_STATUS_META[String(status || '').trim()] || { text: status || '鏈煡', type: 'info' }
+}
+
+function buildAiParamSearchParams(params = {}) {
+  return {
+    condition: normalizeText(params.condition),
+    providerType: normalizeText(params.providerType),
+    model: normalizeText(params.model),
+    status: normalizeOptionalNumber(params.status)
+  }
+}
+
+function buildAiParamPageQueryParams(params = {}) {
+  return {
+    current: Number(params.current) > 0 ? Number(params.current) : 1,
+    pageSize: Number(params.pageSize) > 0 ? Number(params.pageSize) : 20,
+    condition: normalizeText(params.condition),
+    providerType: normalizeText(params.providerType),
+    model: normalizeText(params.model),
+    status: normalizeOptionalNumber(params.status)
+  }
+}
+
+function buildAiParamDialogModel(record = {}) {
+  const base = createAiParamFormState()
+  return {
+    ...base,
+    ...record,
+    id: normalizeOptionalNumber(record.id) ?? null,
+    providerType: normalizeText(record.providerType) || base.providerType,
+    status: normalizeOptionalNumber(record.status) ?? base.status,
+    temperature: normalizeOptionalFloat(record.temperature) ?? base.temperature,
+    topP: normalizeOptionalFloat(record.topP) ?? base.topP,
+    maxTokens: normalizeOptionalNumber(record.maxTokens),
+    timeoutMs: normalizeOptionalNumber(record.timeoutMs) ?? base.timeoutMs,
+    streamingEnabled:
+      record.streamingEnabled === undefined || record.streamingEnabled === null
+        ? base.streamingEnabled
+        : Boolean(record.streamingEnabled),
+    validateStatus: normalizeText(record.validateStatus) || base.validateStatus,
+    lastValidateMessage: normalizeText(record.lastValidateMessage),
+    lastValidateElapsedMs: normalizeOptionalNumber(record.lastValidateElapsedMs),
+    'lastValidateTime$': normalizeText(record['lastValidateTime$']),
+    updateBy: record.updateBy ?? '',
+    'updateTime$': normalizeText(record['updateTime$'])
+  }
+}
+
+function buildAiParamSavePayload(formData = {}) {
+  return {
+    ...(normalizeOptionalNumber(formData.id) !== null ? { id: normalizeOptionalNumber(formData.id) } : {}),
+    name: normalizeText(formData.name),
+    providerType: normalizeText(formData.providerType) || 'OPENAI_COMPATIBLE',
+    baseUrl: normalizeText(formData.baseUrl),
+    apiKey: normalizeText(formData.apiKey),
+    model: normalizeText(formData.model),
+    temperature: normalizeOptionalFloat(formData.temperature),
+    topP: normalizeOptionalFloat(formData.topP),
+    maxTokens: normalizeOptionalNumber(formData.maxTokens),
+    timeoutMs: normalizeOptionalNumber(formData.timeoutMs),
+    streamingEnabled: Boolean(formData.streamingEnabled),
+    status: normalizeOptionalNumber(formData.status) ?? 1,
+    memo: normalizeText(formData.memo)
+  }
+}
+
+function normalizeAiParamRow(record = {}) {
+  const statusMeta = getAiParamStatusMeta(record.status)
+  const validateMeta = getAiParamValidateStatusMeta(record.validateStatus)
+
+  return {
+    ...record,
+    providerType: normalizeText(record.providerType) || 'OPENAI_COMPATIBLE',
+    model: normalizeText(record.model),
+    baseUrl: normalizeText(record.baseUrl),
+    memo: normalizeText(record.memo),
+    statusBool: Number(record.status) === 1,
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    validateStatusText: validateMeta.text,
+    validateStatusType: validateMeta.type,
+    streamingEnabled: Boolean(record.streamingEnabled),
+    streamingText: record.streamingEnabled ? '娴佸紡鍝嶅簲' : '鏍囧噯鍝嶅簲',
+    'createTime$': normalizeText(record['createTime$']),
+    'updateTime$': normalizeText(record['updateTime$']),
+    'lastValidateTime$': normalizeText(record['lastValidateTime$'])
+  }
+}
+
+function buildAiParamPrintRows(records = []) {
+  return records.map((item) => {
+    const row = normalizeAiParamRow(item)
+    return {
+      name: row.name || '-',
+      providerType: row.providerType || '-',
+      model: row.model || '-',
+      statusText: row.statusText,
+      validateStatusText: row.validateStatusText,
+      timeoutMs: row.timeoutMs ?? '--',
+      updateTime: row['updateTime$'] || '--',
+      memo: row.memo || '--'
+    }
+  })
+}
+
+function getAiParamReportColumns() {
+  return [
+    { label: '鍚嶇О', prop: 'name' },
+    { label: '鎻愪緵鏂�', prop: 'providerType' },
+    { label: '妯″瀷', prop: 'model' },
+    { label: '榛樿鐘舵��', prop: 'statusText' },
+    { label: '鏍¢獙鐘舵��', prop: 'validateStatusText' },
+    { label: '瓒呮椂鏃堕棿(ms)', prop: 'timeoutMs' },
+    { label: '鏇存柊鏃堕棿', prop: 'updateTime' },
+    { label: '澶囨敞', prop: 'memo' }
+  ]
+}
+
+function normalizeText(value) {
+  return value === undefined || value === null ? '' : String(value).trim()
+}
+
+function normalizeOptionalNumber(value) {
+  if (value === undefined || value === null || value === '') {
+    return null
+  }
+  const number = Number(value)
+  return Number.isFinite(number) ? number : null
+}
+
+function normalizeOptionalFloat(value) {
+  if (value === undefined || value === null || value === '') {
+    return null
+  }
+  const number = Number(value)
+  return Number.isFinite(number) ? number : null
+}
+
+export {
+  AI_PARAM_REPORT_TITLE,
+  buildAiParamDialogModel,
+  buildAiParamPageQueryParams,
+  buildAiParamPrintRows,
+  buildAiParamSavePayload,
+  buildAiParamSearchParams,
+  createAiParamFormState,
+  createAiParamSearchState,
+  getAiParamPaginationKey,
+  getAiParamProviderOptions,
+  getAiParamReportColumns,
+  getAiParamStatusMeta,
+  getAiParamStatusOptions,
+  getAiParamValidateStatusMeta,
+  normalizeAiParamRow
+}
diff --git a/rsf-design/src/views/system/ai-param/index.vue b/rsf-design/src/views/system/ai-param/index.vue
new file mode 100644
index 0000000..972e5ba
--- /dev/null
+++ b/rsf-design/src/views/system/ai-param/index.vue
@@ -0,0 +1,392 @@
+<template>
+  <div class="art-full-height ai-param-page">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <AiParamRuntimeSummary :key="summaryRefreshSeed" />
+
+    <ElCard class="art-table-card ai-param-list-card">
+      <div class="mb-5 flex flex-wrap items-center justify-between gap-4">
+        <div>
+          <h3 class="text-lg font-semibold text-[var(--art-gray-900)]">AI 鍙傛暟</h3>
+          <p class="mt-1 text-sm text-[var(--art-gray-500)]">鎸夊崱鐗囩鐞嗗綋鍓嶇鎴风殑妯″瀷鎺ュ叆鍙傛暟涓庨粯璁ら厤缃��</p>
+        </div>
+
+        <ElSpace wrap>
+          <ElButton v-auth="'add'" @click="openCreateDialog" v-ripple>鏂板缓鍙傛暟</ElButton>
+          <ElButton :loading="exportLoading" @click="handleExport" v-ripple>瀵煎嚭</ElButton>
+          <ElButton :loading="loading" @click="refreshData" v-ripple>鍒锋柊</ElButton>
+        </ElSpace>
+      </div>
+
+      <div v-loading="loading">
+        <div v-if="data.length" class="grid gap-5 md:grid-cols-2 2xl:grid-cols-3">
+          <article
+            v-for="item in data"
+            :key="item.id"
+            class="overflow-hidden rounded-3xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4 shadow-[0_10px_30px_rgba(15,23,42,0.04)]"
+          >
+            <div class="flex items-start justify-between gap-4">
+              <div class="min-w-0 flex-1">
+                <div class="flex items-center gap-3">
+                  <div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-sky-50 text-sky-600">
+                    <ArtSvgIcon icon="ri:robot-2-line" class="text-xl" />
+                  </div>
+                  <div class="min-w-0">
+                    <h4 class="truncate text-base font-semibold text-[var(--art-gray-900)]">{{ item.name || '--' }}</h4>
+                    <p class="mt-1 truncate text-sm text-[var(--art-gray-500)]">{{ item.model || '--' }}</p>
+                  </div>
+                </div>
+              </div>
+
+              <ElTag :type="item.statusType" effect="light">{{ item.statusText }}</ElTag>
+            </div>
+
+            <div class="mt-3 flex flex-wrap gap-2">
+              <ElTag size="small" effect="plain">{{ item.providerType }}</ElTag>
+              <ElTag size="small" :type="item.validateStatusType" effect="plain">
+                {{ item.validateStatusText }}
+              </ElTag>
+              <ElTag size="small" effect="plain">{{ item.streamingText }}</ElTag>
+            </div>
+
+            <div class="mt-4 grid gap-2 xl:grid-cols-2">
+              <div
+                class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2.5"
+              >
+                <p class="text-xs text-[var(--art-gray-500)]">鍩虹鍦板潃</p>
+                <p class="mt-1.5 break-all text-sm leading-6 text-[var(--art-gray-900)]">{{ item.baseUrl || '--' }}</p>
+              </div>
+              <div
+                class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2.5"
+              >
+                <p class="text-xs text-[var(--art-gray-500)]">鏈�杩戞牎楠�</p>
+                <p class="mt-1.5 text-sm text-[var(--art-gray-900)]">{{ item['lastValidateTime$'] || '鏈牎楠�' }}</p>
+              </div>
+            </div>
+
+            <div class="mt-4 grid grid-cols-2 gap-2 text-sm 2xl:grid-cols-4">
+              <div class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2">
+                <p class="text-xs text-[var(--art-gray-500)]">Temperature</p>
+                <p class="mt-1 text-sm font-medium text-[var(--art-gray-900)]">{{ item.temperature ?? '--' }}</p>
+              </div>
+              <div class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2">
+                <p class="text-xs text-[var(--art-gray-500)]">Top P</p>
+                <p class="mt-1 text-sm font-medium text-[var(--art-gray-900)]">{{ item.topP ?? '--' }}</p>
+              </div>
+              <div class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2">
+                <p class="text-xs text-[var(--art-gray-500)]">Max Tokens</p>
+                <p class="mt-1 text-sm font-medium text-[var(--art-gray-900)]">{{ item.maxTokens ?? '--' }}</p>
+              </div>
+              <div class="min-w-0 rounded-2xl bg-slate-50 px-3 py-2">
+                <p class="text-xs text-[var(--art-gray-500)]">瓒呮椂鏃堕棿</p>
+                <p class="mt-1 text-sm font-medium text-[var(--art-gray-900)]">{{ item.timeoutMs ?? '--' }} ms</p>
+              </div>
+            </div>
+
+            <div class="mt-4 rounded-2xl bg-amber-50/70 px-3 py-2.5">
+              <p class="text-xs text-[var(--art-gray-500)]">澶囨敞</p>
+              <p class="mt-1.5 line-clamp-2 text-sm leading-6 text-[var(--art-gray-900)]">{{ item.memo || '--' }}</p>
+            </div>
+
+            <div class="mt-4 flex flex-wrap items-center justify-between gap-3 border-t border-[var(--art-border-color)] pt-3">
+              <div class="flex items-center gap-2 text-xs text-[var(--art-gray-500)]">
+                <span>鏇存柊鏃堕棿</span>
+                <span>{{ item['updateTime$'] || '--' }}</span>
+              </div>
+
+              <ElSpace wrap>
+                <ElButton text @click="openDetailDialog(item)">璇︽儏</ElButton>
+                <ElButton v-auth="'edit'" text @click="openEditDialog(item)">缂栬緫</ElButton>
+                <ElButton
+                  v-auth="'edit'"
+                  text
+                  :disabled="item.statusBool || defaultUpdatingId === item.id"
+                  :loading="defaultUpdatingId === item.id"
+                  @click="handleSetDefault(item)"
+                >
+                  璁句负榛樿
+                </ElButton>
+                <ElButton v-auth="'delete'" text type="danger" @click="handleDelete(item)">鍒犻櫎</ElButton>
+              </ElSpace>
+            </div>
+          </article>
+        </div>
+
+        <ElEmpty v-else description="鏆傛棤 AI 鍙傛暟鏁版嵁" :image-size="110" />
+      </div>
+
+      <div class="mt-6 flex justify-end">
+        <ElPagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :current-page="pagination.current"
+          :page-size="pagination.size"
+          :total="pagination.total"
+          :page-sizes="[20, 50, 100]"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+
+      <AiParamDialog
+        v-model:visible="dialogVisible"
+        :mode="dialogMode"
+        :ai-param-data="currentAiParamData"
+        @submit="handleDialogSubmit"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchAiParamPage,
+    fetchDeleteAiParam,
+    fetchExportAiParamReport,
+    fetchGetAiParamDetail,
+    fetchSaveAiParam,
+    fetchSetAiParamDefault,
+    fetchUpdateAiParam
+  } from '@/api/ai-config'
+  import AiParamDialog from './modules/ai-param-dialog.vue'
+  import AiParamRuntimeSummary from './modules/ai-param-runtime-summary.vue'
+  import {
+    buildAiParamDialogModel,
+    buildAiParamPageQueryParams,
+    buildAiParamSavePayload,
+    buildAiParamSearchParams,
+    createAiParamSearchState,
+    getAiParamPaginationKey,
+    normalizeAiParamRow
+  } from './aiParamPage.helpers'
+
+  defineOptions({ name: 'AiParam' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createAiParamSearchState())
+  const dialogVisible = ref(false)
+  const dialogMode = ref('create')
+  const currentAiParamData = ref(buildAiParamDialogModel())
+  const defaultUpdatingId = ref(null)
+  const exportLoading = ref(false)
+  const summaryRefreshSeed = ref(0)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ弬鏁板悕绉�'
+      }
+    },
+    {
+      label: '鎻愪緵鏂�',
+      key: 'providerType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ彁渚涙柟绫诲瀷'
+      }
+    },
+    {
+      label: '妯″瀷',
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鍨嬪悕绉�'
+      }
+    },
+    {
+      label: '榛樿鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '榛樿', value: 1 },
+          { label: '鍊欓��', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  const {
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchAiParamPage,
+      apiParams: buildAiParamPageQueryParams(searchForm.value),
+      paginationKey: getAiParamPaginationKey()
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeAiParamRow(item))
+      }
+    }
+  })
+
+  async function openEditDialog(record) {
+    try {
+      currentAiParamData.value = buildAiParamDialogModel(await fetchGetAiParamDetail(record.id))
+      dialogMode.value = 'edit'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  async function openDetailDialog(record) {
+    try {
+      currentAiParamData.value = buildAiParamDialogModel(await fetchGetAiParamDetail(record.id))
+      dialogMode.value = 'show'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  function openCreateDialog() {
+    currentAiParamData.value = buildAiParamDialogModel()
+    dialogMode.value = 'create'
+    dialogVisible.value = true
+  }
+
+  async function handleDialogSubmit(payload) {
+    try {
+      if (dialogMode.value === 'edit') {
+        await fetchUpdateAiParam(buildAiParamSavePayload(payload))
+        ElMessage.success('淇敼鎴愬姛')
+        dialogVisible.value = false
+        summaryRefreshSeed.value += 1
+        await refreshUpdate()
+        return
+      }
+      await fetchSaveAiParam(buildAiParamSavePayload(payload))
+      ElMessage.success('鏂板鎴愬姛')
+      dialogVisible.value = false
+      summaryRefreshSeed.value += 1
+      await refreshCreate()
+    } catch {
+      return
+    }
+  }
+
+  async function handleSetDefault(record) {
+    if (!record?.id || record.statusBool) return
+    defaultUpdatingId.value = record.id
+    try {
+      await fetchSetAiParamDefault(record.id)
+      ElMessage.success('榛樿鍙傛暟宸插垏鎹�')
+      summaryRefreshSeed.value += 1
+      await refreshUpdate()
+    } catch {
+      return
+    } finally {
+      defaultUpdatingId.value = null
+    }
+  }
+
+  async function handleDelete(record) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄� AI 鍙傛暟銆�${record?.name || record?.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteAiParam(record.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  async function handleExport() {
+    exportLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchExportAiParamReport(buildAiParamSearchParams(searchForm.value), {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+        null,
+        {
+          timeoutMessage: '瀵煎嚭璇锋眰瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      if (!response) return
+      if (!response.ok) {
+        throw new Error(`瀵煎嚭澶辫触 (${response.status})`)
+      }
+      const blob = await response.blob()
+      const url = window.URL.createObjectURL(blob)
+      const link = document.createElement('a')
+      link.href = url
+      link.download = 'ai-param.xlsx'
+      document.body.appendChild(link)
+      link.click()
+      link.remove()
+      window.URL.revokeObjectURL(url)
+      ElMessage.success('瀵煎嚭鎴愬姛')
+    } catch (error) {
+      ElMessage.error(error?.message || '瀵煎嚭澶辫触')
+    } finally {
+      exportLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildAiParamSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createAiParamSearchState())
+    resetSearchParams()
+  }
+</script>
+
+<style scoped>
+  .ai-param-page {
+    overflow-y: auto;
+  }
+
+  .ai-param-list-card {
+    flex: none;
+  }
+
+  .ai-param-list-card :deep(.el-card__body) {
+    height: auto;
+    overflow: visible;
+  }
+</style>
diff --git a/rsf-design/src/views/system/ai-param/modules/ai-param-dialog.vue b/rsf-design/src/views/system/ai-param/modules/ai-param-dialog.vue
new file mode 100644
index 0000000..e48a405
--- /dev/null
+++ b/rsf-design/src/views/system/ai-param/modules/ai-param-dialog.vue
@@ -0,0 +1,325 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="920px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <div v-if="showRuntimeSection" class="mt-2 rounded-2xl border border-[var(--art-border-color)] px-5 py-4">
+      <div class="mb-3 flex items-center justify-between gap-4">
+        <div>
+          <h4 class="text-base font-semibold text-[var(--art-gray-900)]">杩愯鏃剁姸鎬�</h4>
+          <p class="mt-1 text-sm text-[var(--art-gray-500)]">淇濆瓨鍓嶅彲鍏堟墽琛岃崏绋挎牎楠岋紝杩愯鏃剁姸鎬佺敱鍚庣鐪熷疄杩斿洖銆�</p>
+        </div>
+        <ElButton v-if="!isReadonly" :loading="validateLoading" @click="handleValidateDraft">
+          鑽夌鏍¢獙
+        </ElButton>
+      </div>
+
+      <ElAlert
+        v-if="validateResultMessage"
+        class="!mb-4"
+        :type="validateAlertType"
+        :closable="false"
+        :title="validateResultMessage"
+      />
+
+      <ElDescriptions :column="2" border>
+        <ElDescriptionsItem label="鏍¢獙鐘舵��">{{ form.validateStatus || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞牎楠岃�楁椂">
+          {{ form.lastValidateElapsedMs !== null && form.lastValidateElapsedMs !== undefined ? `${form.lastValidateElapsedMs} ms` : '--' }}
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞牎楠屾椂闂�">{{ form['lastValidateTime$'] || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞洿鏂颁汉">{{ form.updateBy || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞洿鏂版椂闂�" :span="2">{{ form['updateTime$'] || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞牎楠屼俊鎭�" :span="2">
+          <div class="whitespace-pre-wrap break-all text-sm leading-6 text-[var(--art-gray-700)]">
+            {{ form.lastValidateMessage || '--' }}
+          </div>
+        </ElDescriptionsItem>
+      </ElDescriptions>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ isReadonly ? '鍏抽棴' : '鍙栨秷' }}</ElButton>
+        <ElButton v-if="!isReadonly" type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { fetchValidateAiParamDraft } from '@/api/ai-config'
+  import {
+    buildAiParamDialogModel,
+    buildAiParamSavePayload,
+    createAiParamFormState,
+    getAiParamProviderOptions,
+    getAiParamStatusOptions
+  } from '../aiParamPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    mode: { type: String, default: 'create' },
+    aiParamData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['submit', 'update:visible'])
+  const formRef = ref()
+  const form = reactive(createAiParamFormState())
+  const validateLoading = ref(false)
+  const validateResult = ref(null)
+
+  const isReadonly = computed(() => props.mode === 'show')
+  const showRuntimeSection = computed(() => Boolean(form.id) || props.mode !== 'create')
+  const dialogTitle = computed(() => {
+    if (props.mode === 'edit') return '缂栬緫 AI 鍙傛暟'
+    if (props.mode === 'show') return 'AI 鍙傛暟璇︽儏'
+    return '鏂板缓 AI 鍙傛暟'
+  })
+
+  const validateAlertType = computed(() =>
+    validateResult.value?.status === 'VALID' ? 'success' : 'warning'
+  )
+
+  const validateResultMessage = computed(() => {
+    if (!validateResult.value?.message) {
+      return ''
+    }
+    const suffix = [
+      validateResult.value.elapsedMs ? `${validateResult.value.elapsedMs} ms` : '',
+      validateResult.value.validatedAt || ''
+    ]
+      .filter(Boolean)
+      .join(' 路 ')
+    return suffix ? `${validateResult.value.message} 路 ${suffix}` : validateResult.value.message
+  })
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ュ弬鏁板悕绉�', trigger: 'blur' }],
+    providerType: [{ required: true, message: '璇烽�夋嫨鎻愪緵鏂圭被鍨�', trigger: 'change' }],
+    baseUrl: [{ required: true, message: '璇疯緭鍏ュ熀纭�鍦板潃', trigger: 'blur' }],
+    apiKey: [{ required: true, message: '璇疯緭鍏� API Key', trigger: 'blur' }],
+    model: [{ required: true, message: '璇疯緭鍏ユā鍨嬪悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '鍙傛暟鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ弬鏁板悕绉�',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '鎻愪緵鏂圭被鍨�',
+      key: 'providerType',
+      type: 'select',
+      props: {
+        options: getAiParamProviderOptions(),
+        disabled: isReadonly.value,
+        placeholder: '璇烽�夋嫨鎻愪緵鏂圭被鍨�'
+      }
+    },
+    {
+      label: '鍩虹鍦板潃',
+      key: 'baseUrl',
+      type: 'input',
+      span: 24,
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ吋瀹� OpenAI 鐨勫熀纭�鍦板潃',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: 'API Key',
+      key: 'apiKey',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� API Key',
+        disabled: isReadonly.value,
+        type: isReadonly.value ? 'text' : 'password',
+        showPassword: !isReadonly.value
+      }
+    },
+    {
+      label: '妯″瀷鍚嶇О',
+      key: 'model',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユā鍨嬪悕绉�',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: 'Temperature',
+      key: 'temperature',
+      type: 'number',
+      props: {
+        min: 0,
+        max: 2,
+        step: 0.1,
+        precision: 2,
+        placeholder: '璇疯緭鍏� temperature',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: 'Top P',
+      key: 'topP',
+      type: 'number',
+      props: {
+        min: 0,
+        max: 1,
+        step: 0.1,
+        precision: 2,
+        placeholder: '璇疯緭鍏� topP',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '鏈�澶� Token',
+      key: 'maxTokens',
+      type: 'number',
+      props: {
+        min: 1,
+        step: 1,
+        placeholder: '璇疯緭鍏ユ渶澶� token',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '瓒呮椂鏃堕棿(ms)',
+      key: 'timeoutMs',
+      type: 'number',
+      props: {
+        min: 1000,
+        step: 1000,
+        placeholder: '璇疯緭鍏ヨ秴鏃舵椂闂�',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '娴佸紡鍝嶅簲',
+      key: 'streamingEnabled',
+      type: 'switch',
+      props: {
+        disabled: isReadonly.value,
+        activeText: '寮�鍚�',
+        inactiveText: '鍏抽棴'
+      }
+    },
+    {
+      label: '榛樿鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        disabled: isReadonly.value,
+        options: getAiParamStatusOptions(),
+        placeholder: '璇烽�夋嫨榛樿鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        disabled: isReadonly.value,
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createAiParamFormState())
+    validateResult.value = null
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildAiParamDialogModel(props.aiParamData))
+    validateResult.value = null
+  }
+
+  async function handleValidateDraft() {
+    validateLoading.value = true
+    try {
+      const result = await fetchValidateAiParamDraft(buildAiParamSavePayload(form))
+      validateResult.value = result
+      Object.assign(form, {
+        validateStatus: result?.status || form.validateStatus,
+        lastValidateMessage: result?.message || '',
+        lastValidateElapsedMs: result?.elapsedMs ?? null,
+        'lastValidateTime$': result?.validatedAt || ''
+      })
+    } catch {
+      return
+    } finally {
+      validateLoading.value = false
+    }
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', buildAiParamSavePayload(form))
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.aiParamData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/ai-param/modules/ai-param-runtime-summary.vue b/rsf-design/src/views/system/ai-param/modules/ai-param-runtime-summary.vue
new file mode 100644
index 0000000..cf200ff
--- /dev/null
+++ b/rsf-design/src/views/system/ai-param/modules/ai-param-runtime-summary.vue
@@ -0,0 +1,142 @@
+<template>
+  <ElCard class="art-table-card ai-param-runtime-summary-card !mb-5" shadow="never">
+    <div class="mb-3 flex items-start justify-between gap-4">
+      <div>
+        <h3 class="text-base font-semibold text-[var(--art-gray-900)]">杩愯鏃舵憳瑕�</h3>
+        <p class="mt-0.5 text-xs text-[var(--art-gray-500)]">褰撳墠鐢熸晥鐨勬ā鍨嬨�丳rompt 涓� MCP 鎸傝浇姒傚喌</p>
+      </div>
+      <ElButton text :loading="loading" @click="loadSummary">鍒锋柊鎽樿</ElButton>
+    </div>
+
+    <ElAlert
+      v-if="errorMessage"
+      type="warning"
+      :closable="false"
+      class="!mb-5"
+      :title="errorMessage"
+    />
+
+    <div class="grid gap-2.5 lg:grid-cols-3" v-loading="loading">
+      <div class="rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-3 py-3">
+        <div class="flex items-center gap-2.5">
+          <div class="flex size-8 items-center justify-center rounded-xl bg-sky-50 text-sky-600">
+            <ArtSvgIcon icon="ri:robot-2-line" class="text-base" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <p class="text-[11px] text-[var(--art-gray-500)]">褰撳墠妯″瀷</p>
+            <h4 class="truncate text-sm font-semibold text-[var(--art-gray-900)]">
+              {{ summary.activeModel || '--' }}
+            </h4>
+          </div>
+        </div>
+        <div class="mt-2 flex items-center justify-between gap-3">
+          <p class="truncate text-xs text-[var(--art-gray-700)]">{{ summary.activeParamName || '--' }}</p>
+          <ElTag size="small" :type="validateMeta.type" effect="light">
+            {{ validateMeta.text }}
+          </ElTag>
+        </div>
+        <p class="mt-1 text-[11px] text-[var(--art-gray-500)]">{{ summary.activeParamValidatedAt || '鏈牎楠�' }}</p>
+      </div>
+
+      <div class="rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-3 py-3">
+        <div class="flex items-center gap-2.5">
+          <div class="flex size-8 items-center justify-center rounded-xl bg-violet-50 text-violet-600">
+            <ArtSvgIcon icon="ri:lightbulb-flash-line" class="text-base" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <p class="text-[11px] text-[var(--art-gray-500)]">褰撳墠 Prompt</p>
+            <h4 class="truncate text-sm font-semibold text-[var(--art-gray-900)]">
+              {{ summary.promptName || '--' }}
+            </h4>
+          </div>
+        </div>
+        <p class="mt-2 truncate text-xs text-[var(--art-gray-700)]">
+          {{ [summary.promptCode, summary.promptScene].filter(Boolean).join(' / ') || '--' }}
+        </p>
+        <p class="mt-1 text-[11px] text-[var(--art-gray-500)]">
+          鏈�杩戞洿鏂版椂闂� {{ summary.activePromptUpdatedAt || '--' }}
+        </p>
+      </div>
+
+      <div class="rounded-2xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-3 py-3">
+        <div class="flex items-center gap-2.5">
+          <div class="flex size-8 items-center justify-center rounded-xl bg-emerald-50 text-emerald-600">
+            <ArtSvgIcon icon="ri:plug-2-line" class="text-base" />
+          </div>
+          <div class="min-w-0 flex-1">
+            <p class="text-[11px] text-[var(--art-gray-500)]">宸插惎鐢� MCP</p>
+            <h4 class="text-sm font-semibold text-[var(--art-gray-900)]">
+              {{ summary.enabledMcpCount ?? 0 }} 涓�
+            </h4>
+          </div>
+        </div>
+        <div class="mt-2 flex flex-wrap gap-1.5">
+          <ElTag
+            v-for="name in enabledMcpNames"
+            :key="name"
+            size="small"
+            effect="plain"
+          >
+            {{ name }}
+          </ElTag>
+          <span v-if="!enabledMcpNames.length" class="text-xs text-[var(--art-gray-500)]">鏆傛棤鎸傝浇</span>
+        </div>
+      </div>
+    </div>
+  </ElCard>
+</template>
+
+<script setup>
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchGetAiConfigSummary } from '@/api/ai-config'
+  import { getAiParamValidateStatusMeta } from '../aiParamPage.helpers'
+
+  const props = defineProps({
+    promptCode: { type: String, default: '' }
+  })
+
+  const loading = ref(false)
+  const summary = ref({})
+  const errorMessage = ref('')
+
+  const validateMeta = computed(() =>
+    getAiParamValidateStatusMeta(summary.value?.activeParamValidateStatus)
+  )
+
+  const enabledMcpNames = computed(() =>
+    Array.isArray(summary.value?.enabledMcpNames) ? summary.value.enabledMcpNames : []
+  )
+
+  async function loadSummary() {
+    loading.value = true
+    errorMessage.value = ''
+    const data = await guardRequestWithMessage(
+      fetchGetAiConfigSummary(props.promptCode),
+      null,
+      {
+        timeoutMessage: '杩愯鏃舵憳瑕佸姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+      }
+    )
+    loading.value = false
+    if (!data) {
+      errorMessage.value = '杩愯鏃舵憳瑕佹殏鏃朵笉鍙敤'
+      summary.value = {}
+      return
+    }
+    summary.value = data
+  }
+
+  onMounted(() => {
+    loadSummary()
+  })
+</script>
+
+<style scoped>
+  .ai-param-runtime-summary-card {
+    flex: none;
+  }
+
+  .ai-param-runtime-summary-card :deep(.el-card__body) {
+    height: auto;
+  }
+</style>
diff --git a/rsf-design/src/views/system/ai-prompt/aiPromptPage.helpers.js b/rsf-design/src/views/system/ai-prompt/aiPromptPage.helpers.js
new file mode 100644
index 0000000..5f22a4c
--- /dev/null
+++ b/rsf-design/src/views/system/ai-prompt/aiPromptPage.helpers.js
@@ -0,0 +1,182 @@
+const AI_PROMPT_REPORT_TITLE = 'Prompt 绠$悊鎶ヨ〃'
+
+const STATUS_OPTIONS = [
+  { label: '鍚敤', value: 1 },
+  { label: '鍋滅敤', value: 0 }
+]
+
+function createAiPromptSearchState() {
+  return {
+    condition: '',
+    code: '',
+    scene: '',
+    status: ''
+  }
+}
+
+function createAiPromptFormState() {
+  return {
+    id: null,
+    name: '',
+    code: 'home.default',
+    scene: 'home',
+    systemPrompt: '',
+    userPromptTemplate: '',
+    status: 1,
+    memo: '',
+    updateBy: '',
+    'updateTime$': ''
+  }
+}
+
+function getAiPromptPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+function getAiPromptStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+function getAiPromptStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '鍚敤', type: 'success' }
+    : { text: '鍋滅敤', type: 'info' }
+}
+
+function buildAiPromptSearchParams(params = {}) {
+  return {
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    scene: normalizeText(params.scene),
+    status: normalizeOptionalNumber(params.status)
+  }
+}
+
+function buildAiPromptPageQueryParams(params = {}) {
+  return {
+    current: Number(params.current) > 0 ? Number(params.current) : 1,
+    pageSize: Number(params.pageSize) > 0 ? Number(params.pageSize) : 20,
+    condition: normalizeText(params.condition),
+    code: normalizeText(params.code),
+    scene: normalizeText(params.scene),
+    status: normalizeOptionalNumber(params.status)
+  }
+}
+
+function buildAiPromptDialogModel(record = {}) {
+  const base = createAiPromptFormState()
+  return {
+    ...base,
+    ...record,
+    id: normalizeOptionalNumber(record.id) ?? null,
+    status: normalizeOptionalNumber(record.status) ?? base.status,
+    code: normalizeText(record.code) || base.code,
+    scene: normalizeText(record.scene) || base.scene,
+    systemPrompt: normalizeText(record.systemPrompt),
+    userPromptTemplate: normalizeText(record.userPromptTemplate),
+    memo: normalizeText(record.memo),
+    updateBy: record.updateBy ?? '',
+    'updateTime$': normalizeText(record['updateTime$'])
+  }
+}
+
+function buildAiPromptSavePayload(formData = {}) {
+  return {
+    ...(normalizeOptionalNumber(formData.id) !== null ? { id: normalizeOptionalNumber(formData.id) } : {}),
+    name: normalizeText(formData.name),
+    code: normalizeText(formData.code),
+    scene: normalizeText(formData.scene),
+    systemPrompt: normalizeText(formData.systemPrompt),
+    userPromptTemplate: normalizeText(formData.userPromptTemplate),
+    status: normalizeOptionalNumber(formData.status) ?? 1,
+    memo: normalizeText(formData.memo)
+  }
+}
+
+function buildAiPromptPreviewPayload(formData = {}, previewInput = '', metadata = {}) {
+  return {
+    ...buildAiPromptSavePayload(formData),
+    input: normalizeText(previewInput),
+    metadata: isPlainObject(metadata) ? metadata : {}
+  }
+}
+
+function normalizeAiPromptRow(record = {}) {
+  const statusMeta = getAiPromptStatusMeta(record.status)
+  return {
+    ...record,
+    code: normalizeText(record.code),
+    scene: normalizeText(record.scene),
+    systemPrompt: normalizeText(record.systemPrompt),
+    userPromptTemplate: normalizeText(record.userPromptTemplate),
+    memo: normalizeText(record.memo),
+    statusBool: Number(record.status) === 1,
+    statusText: statusMeta.text,
+    statusType: statusMeta.type,
+    'createTime$': normalizeText(record['createTime$']),
+    'updateTime$': normalizeText(record['updateTime$'])
+  }
+}
+
+function buildAiPromptPrintRows(records = []) {
+  return records.map((item) => {
+    const row = normalizeAiPromptRow(item)
+    return {
+      name: row.name || '-',
+      code: row.code || '-',
+      scene: row.scene || '-',
+      statusText: row.statusText,
+      systemPrompt: row.systemPrompt || '--',
+      userPromptTemplate: row.userPromptTemplate || '--',
+      updateTime: row['updateTime$'] || '--'
+    }
+  })
+}
+
+function getAiPromptReportColumns() {
+  return [
+    { label: '鍚嶇О', prop: 'name' },
+    { label: '缂栫爜', prop: 'code' },
+    { label: '鍦烘櫙', prop: 'scene' },
+    { label: '鐘舵��', prop: 'statusText' },
+    { label: '绯荤粺鎻愮ず璇�', prop: 'systemPrompt' },
+    { label: '鐢ㄦ埛鎻愮ず璇嶆ā鏉�', prop: 'userPromptTemplate' },
+    { label: '鏇存柊鏃堕棿', prop: 'updateTime' }
+  ]
+}
+
+function normalizeText(value) {
+  return value === undefined || value === null ? '' : String(value).trim()
+}
+
+function normalizeOptionalNumber(value) {
+  if (value === undefined || value === null || value === '') {
+    return null
+  }
+  const number = Number(value)
+  return Number.isFinite(number) ? number : null
+}
+
+function isPlainObject(value) {
+  return Object.prototype.toString.call(value) === '[object Object]'
+}
+
+export {
+  AI_PROMPT_REPORT_TITLE,
+  buildAiPromptDialogModel,
+  buildAiPromptPageQueryParams,
+  buildAiPromptPreviewPayload,
+  buildAiPromptPrintRows,
+  buildAiPromptSavePayload,
+  buildAiPromptSearchParams,
+  createAiPromptFormState,
+  createAiPromptSearchState,
+  getAiPromptPaginationKey,
+  getAiPromptReportColumns,
+  getAiPromptStatusMeta,
+  getAiPromptStatusOptions,
+  normalizeAiPromptRow
+}
diff --git a/rsf-design/src/views/system/ai-prompt/index.vue b/rsf-design/src/views/system/ai-prompt/index.vue
new file mode 100644
index 0000000..aa0ab7e
--- /dev/null
+++ b/rsf-design/src/views/system/ai-prompt/index.vue
@@ -0,0 +1,316 @@
+<template>
+  <div class="art-full-height ai-prompt-page">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <div class="mb-5 flex flex-wrap items-center justify-between gap-4">
+        <div>
+          <h3 class="text-lg font-semibold text-[var(--art-gray-900)]">Prompt 绠$悊</h3>
+          <p class="mt-1 text-sm text-[var(--art-gray-500)]">鎸夊崱鐗囩淮鎶� Prompt 妯℃澘銆佸満鏅紪鐮佸拰杩愯鏃堕瑙堛��</p>
+        </div>
+
+        <ElSpace wrap>
+          <ElButton v-auth="'add'" @click="openCreateDialog" v-ripple>鏂板缓 Prompt</ElButton>
+          <ElButton :loading="exportLoading" @click="handleExport" v-ripple>瀵煎嚭</ElButton>
+          <ElButton :loading="loading" @click="refreshData" v-ripple>鍒锋柊</ElButton>
+        </ElSpace>
+      </div>
+
+      <div v-loading="loading">
+        <div v-if="data.length" class="grid gap-5 md:grid-cols-2 2xl:grid-cols-3">
+          <article
+            v-for="item in data"
+            :key="item.id"
+            class="rounded-3xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-5 shadow-[0_12px_40px_rgba(15,23,42,0.04)]"
+          >
+            <div class="flex items-start justify-between gap-4">
+              <div class="min-w-0">
+                <div class="flex items-center gap-3">
+                  <div class="flex size-11 shrink-0 items-center justify-center rounded-2xl bg-violet-50 text-violet-600">
+                    <ArtSvgIcon icon="ri:lightbulb-flash-line" class="text-xl" />
+                  </div>
+                  <div class="min-w-0">
+                    <h4 class="truncate text-base font-semibold text-[var(--art-gray-900)]">{{ item.name || '--' }}</h4>
+                    <p class="mt-1 truncate text-sm text-[var(--art-gray-500)]">{{ item.code || '--' }}</p>
+                  </div>
+                </div>
+              </div>
+
+              <ElTag :type="item.statusType" effect="light">{{ item.statusText }}</ElTag>
+            </div>
+
+            <div class="mt-4 flex flex-wrap gap-2">
+              <ElTag size="small" effect="plain">鍦烘櫙 {{ item.scene || '--' }}</ElTag>
+            </div>
+
+            <div class="mt-5 rounded-2xl bg-[var(--art-main-bg-color)]/70 p-4 ring-1 ring-inset ring-[var(--art-border-color)]">
+              <p class="text-xs text-[var(--art-gray-500)]">绯荤粺鎻愮ず璇�</p>
+              <p class="mt-2 line-clamp-4 whitespace-pre-wrap text-sm leading-6 text-[var(--art-gray-900)]">
+                {{ item.systemPrompt || '--' }}
+              </p>
+            </div>
+
+            <div class="mt-4 rounded-2xl bg-amber-50/80 px-4 py-3">
+              <p class="text-xs text-[var(--art-gray-500)]">鐢ㄦ埛鎻愮ず璇嶆ā鏉�</p>
+              <p class="mt-2 line-clamp-4 whitespace-pre-wrap text-sm leading-6 text-[var(--art-gray-900)]">
+                {{ item.userPromptTemplate || '--' }}
+              </p>
+            </div>
+
+            <div class="mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-[var(--art-border-color)] pt-4">
+              <div class="flex items-center gap-2 text-xs text-[var(--art-gray-500)]">
+                <span>鏇存柊鏃堕棿</span>
+                <span>{{ item['updateTime$'] || '--' }}</span>
+              </div>
+
+              <ElSpace wrap>
+                <ElButton text @click="openDetailDialog(item)">璇︽儏</ElButton>
+                <ElButton v-auth="'edit'" text @click="openEditDialog(item)">缂栬緫</ElButton>
+                <ElButton v-auth="'delete'" text type="danger" @click="handleDelete(item)">鍒犻櫎</ElButton>
+              </ElSpace>
+            </div>
+          </article>
+        </div>
+
+        <ElEmpty v-else description="鏆傛棤 Prompt 鏁版嵁" :image-size="110" />
+      </div>
+
+      <div class="mt-6 flex justify-end">
+        <ElPagination
+          background
+          layout="total, sizes, prev, pager, next, jumper"
+          :current-page="pagination.current"
+          :page-size="pagination.size"
+          :total="pagination.total"
+          :page-sizes="[20, 50, 100]"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+
+      <AiPromptDialog
+        v-model:visible="dialogVisible"
+        :mode="dialogMode"
+        :ai-prompt-data="currentAiPromptData"
+        @submit="handleDialogSubmit"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchAiPromptPage,
+    fetchDeleteAiPrompt,
+    fetchExportAiPromptReport,
+    fetchGetAiPromptDetail,
+    fetchSaveAiPrompt,
+    fetchUpdateAiPrompt
+  } from '@/api/ai-config'
+  import AiPromptDialog from './modules/ai-prompt-dialog.vue'
+  import {
+    buildAiPromptDialogModel,
+    buildAiPromptPageQueryParams,
+    buildAiPromptSavePayload,
+    buildAiPromptSearchParams,
+    createAiPromptSearchState,
+    getAiPromptPaginationKey,
+    normalizeAiPromptRow
+  } from './aiPromptPage.helpers'
+
+  defineOptions({ name: 'AiPrompt' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createAiPromptSearchState())
+  const dialogVisible = ref(false)
+  const dialogMode = ref('create')
+  const currentAiPromptData = ref(buildAiPromptDialogModel())
+  const exportLoading = ref(false)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� Prompt 鍚嶇О'
+      }
+    },
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� Prompt 缂栫爜'
+      }
+    },
+    {
+      label: '鍦烘櫙',
+      key: 'scene',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ満鏅爣璇�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鍚敤', value: 1 },
+          { label: '鍋滅敤', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  const {
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchAiPromptPage,
+      apiParams: buildAiPromptPageQueryParams(searchForm.value),
+      paginationKey: getAiPromptPaginationKey()
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeAiPromptRow(item))
+      }
+    }
+  })
+
+  async function openEditDialog(record) {
+    try {
+      currentAiPromptData.value = buildAiPromptDialogModel(await fetchGetAiPromptDetail(record.id))
+      dialogMode.value = 'edit'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  async function openDetailDialog(record) {
+    try {
+      currentAiPromptData.value = buildAiPromptDialogModel(await fetchGetAiPromptDetail(record.id))
+      dialogMode.value = 'show'
+      dialogVisible.value = true
+    } catch {
+      return
+    }
+  }
+
+  function openCreateDialog() {
+    currentAiPromptData.value = buildAiPromptDialogModel()
+    dialogMode.value = 'create'
+    dialogVisible.value = true
+  }
+
+  async function handleDialogSubmit(payload) {
+    try {
+      if (dialogMode.value === 'edit') {
+        await fetchUpdateAiPrompt(buildAiPromptSavePayload(payload))
+        ElMessage.success('淇敼鎴愬姛')
+        dialogVisible.value = false
+        await refreshUpdate()
+        return
+      }
+      await fetchSaveAiPrompt(buildAiPromptSavePayload(payload))
+      ElMessage.success('鏂板鎴愬姛')
+      dialogVisible.value = false
+      await refreshCreate()
+    } catch {
+      return
+    }
+  }
+
+  async function handleDelete(record) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄� Prompt銆�${record?.name || record?.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteAiPrompt(record.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  async function handleExport() {
+    exportLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(
+        fetchExportAiPromptReport(buildAiPromptSearchParams(searchForm.value), {
+          headers: {
+            Authorization: userStore.accessToken || ''
+          }
+        }),
+        null,
+        {
+          timeoutMessage: '瀵煎嚭璇锋眰瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      if (!response) return
+      if (!response.ok) {
+        throw new Error(`瀵煎嚭澶辫触 (${response.status})`)
+      }
+      const blob = await response.blob()
+      const url = window.URL.createObjectURL(blob)
+      const link = document.createElement('a')
+      link.href = url
+      link.download = 'ai-prompt.xlsx'
+      document.body.appendChild(link)
+      link.click()
+      link.remove()
+      window.URL.revokeObjectURL(url)
+      ElMessage.success('瀵煎嚭鎴愬姛')
+    } catch (error) {
+      ElMessage.error(error?.message || '瀵煎嚭澶辫触')
+    } finally {
+      exportLoading.value = false
+    }
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildAiPromptSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createAiPromptSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/ai-prompt/modules/ai-prompt-dialog.vue b/rsf-design/src/views/system/ai-prompt/modules/ai-prompt-dialog.vue
new file mode 100644
index 0000000..67211b6
--- /dev/null
+++ b/rsf-design/src/views/system/ai-prompt/modules/ai-prompt-dialog.vue
@@ -0,0 +1,303 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="980px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <div class="mt-2 rounded-2xl border border-[var(--art-border-color)] px-5 py-4">
+      <div class="mb-3 flex items-center justify-between gap-4">
+        <div>
+          <h4 class="text-base font-semibold text-[var(--art-gray-900)]">娓叉煋棰勮</h4>
+          <p class="mt-1 text-sm text-[var(--art-gray-500)]">杈撳叆绀轰緥鍐呭鍜� metadata锛岀洿鎺ラ瑙堟渶缁堟覆鏌撶粨鏋溿��</p>
+        </div>
+        <ElButton v-if="!isReadonly" :loading="previewLoading" @click="handlePreview">娓叉煋棰勮</ElButton>
+      </div>
+
+      <div class="grid gap-4 lg:grid-cols-2">
+        <div class="space-y-4">
+          <ElInput
+            v-model="previewInput"
+            type="textarea"
+            :rows="4"
+            :disabled="isReadonly"
+            placeholder="璇疯緭鍏ョず渚嬭緭鍏ュ唴瀹�"
+          />
+          <ElInput
+            v-model="metadataText"
+            type="textarea"
+            :rows="4"
+            :disabled="isReadonly"
+            placeholder='璇疯緭鍏� JSON metadata锛屼緥濡� {"path":"/system/aiPrompt"}'
+          />
+        </div>
+
+        <div class="space-y-4">
+          <ElAlert
+            v-if="previewErrorMessage"
+            type="warning"
+            :closable="false"
+            :title="previewErrorMessage"
+          />
+          <ElAlert
+            v-else-if="previewResult"
+            type="success"
+            :closable="false"
+            :title="`宸茶В鏋愬彉閲忥細${resolvedVariablesText}`"
+          />
+          <ElInput
+            :model-value="previewResult?.renderedSystemPrompt || ''"
+            type="textarea"
+            :rows="5"
+            readonly
+            placeholder="绯荤粺鎻愮ず璇嶆覆鏌撶粨鏋�"
+          />
+          <ElInput
+            :model-value="previewResult?.renderedUserPrompt || ''"
+            type="textarea"
+            :rows="5"
+            readonly
+            placeholder="鐢ㄦ埛鎻愮ず璇嶆覆鏌撶粨鏋�"
+          />
+        </div>
+      </div>
+    </div>
+
+    <div v-if="showRuntimeSection" class="mt-5 rounded-2xl border border-[var(--art-border-color)] px-5 py-4">
+      <h4 class="text-base font-semibold text-[var(--art-gray-900)]">杩愯鏃剁姸鎬�</h4>
+      <ElDescriptions class="mt-4" :column="2" border>
+        <ElDescriptionsItem label="鏈�杩戞洿鏂颁汉">{{ form.updateBy || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戞洿鏂版椂闂�">{{ form['updateTime$'] || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </div>
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">{{ isReadonly ? '鍏抽棴' : '鍙栨秷' }}</ElButton>
+        <ElButton v-if="!isReadonly" type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { fetchRenderAiPromptPreview } from '@/api/ai-config'
+  import {
+    buildAiPromptDialogModel,
+    buildAiPromptPreviewPayload,
+    buildAiPromptSavePayload,
+    createAiPromptFormState,
+    getAiPromptStatusOptions
+  } from '../aiPromptPage.helpers'
+
+  const DEFAULT_PREVIEW_INPUT = '璇锋牴鎹綋鍓嶈緭鍏ョ粰鍑烘憳瑕�'
+  const DEFAULT_METADATA_TEXT = '{"path":"/system/aiPrompt"}'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    mode: { type: String, default: 'create' },
+    aiPromptData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['submit', 'update:visible'])
+  const formRef = ref()
+  const form = reactive(createAiPromptFormState())
+  const previewInput = ref(DEFAULT_PREVIEW_INPUT)
+  const metadataText = ref(DEFAULT_METADATA_TEXT)
+  const previewLoading = ref(false)
+  const previewResult = ref(null)
+  const previewErrorMessage = ref('')
+
+  const isReadonly = computed(() => props.mode === 'show')
+  const showRuntimeSection = computed(() => Boolean(form.id) || props.mode !== 'create')
+  const dialogTitle = computed(() => {
+    if (props.mode === 'edit') return '缂栬緫 Prompt'
+    if (props.mode === 'show') return 'Prompt 璇︽儏'
+    return '鏂板缓 Prompt'
+  })
+
+  const resolvedVariablesText = computed(() => {
+    if (!Array.isArray(previewResult.value?.resolvedVariables) || !previewResult.value.resolvedVariables.length) {
+      return '鏃�'
+    }
+    return previewResult.value.resolvedVariables.join('銆�')
+  })
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏� Prompt 鍚嶇О', trigger: 'blur' }],
+    code: [{ required: true, message: '璇疯緭鍏� Prompt 缂栫爜', trigger: 'blur' }],
+    scene: [{ required: true, message: '璇疯緭鍏ュ満鏅爣璇�', trigger: 'blur' }],
+    systemPrompt: [{ required: true, message: '璇疯緭鍏ョ郴缁熸彁绀鸿瘝', trigger: 'blur' }],
+    userPromptTemplate: [{ required: true, message: '璇疯緭鍏ョ敤鎴锋彁绀鸿瘝妯℃澘', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: 'Prompt 鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� Prompt 鍚嶇О',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: 'Prompt 缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� Prompt 缂栫爜',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '鍦烘櫙鏍囪瘑',
+      key: 'scene',
+      type: 'input',
+      span: 24,
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ満鏅爣璇�',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '绯荤粺鎻愮ず璇�',
+      key: 'systemPrompt',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 6,
+        placeholder: '璇疯緭鍏ョ郴缁熸彁绀鸿瘝',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '鐢ㄦ埛鎻愮ず璇嶆ā鏉�',
+      key: 'userPromptTemplate',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 5,
+        placeholder: '璇疯緭鍏ョ敤鎴锋彁绀鸿瘝妯℃澘',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getAiPromptStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��',
+        disabled: isReadonly.value
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        disabled: isReadonly.value
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createAiPromptFormState())
+    previewInput.value = DEFAULT_PREVIEW_INPUT
+    metadataText.value = DEFAULT_METADATA_TEXT
+    previewResult.value = null
+    previewErrorMessage.value = ''
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildAiPromptDialogModel(props.aiPromptData))
+    previewResult.value = null
+    previewErrorMessage.value = ''
+  }
+
+  function parseMetadataText() {
+    const text = String(metadataText.value || '').trim()
+    if (!text) return {}
+    return JSON.parse(text)
+  }
+
+  async function handlePreview() {
+    previewLoading.value = true
+    previewErrorMessage.value = ''
+    try {
+      previewResult.value = await fetchRenderAiPromptPreview(
+        buildAiPromptPreviewPayload(form, previewInput.value, parseMetadataText())
+      )
+    } catch (error) {
+      previewResult.value = null
+      previewErrorMessage.value = error?.message || '娓叉煋棰勮澶辫触'
+    } finally {
+      previewLoading.value = false
+    }
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', buildAiPromptSavePayload(form))
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.aiPromptData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/common/usePrintExportPage.js b/rsf-design/src/views/system/common/usePrintExportPage.js
index c202543..49fdd41 100644
--- a/rsf-design/src/views/system/common/usePrintExportPage.js
+++ b/rsf-design/src/views/system/common/usePrintExportPage.js
@@ -1,12 +1,14 @@
 import { ref } from 'vue'
 import { ElMessage } from 'element-plus'
+import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
 
 export function usePrintExportPage({
   downloadFileName,
   requestExport,
   resolvePrintRecords,
   buildPreviewRows,
-  buildPreviewMeta
+  buildPreviewMeta,
+  timeoutMs
 }) {
   const previewVisible = ref(false)
   const previewRows = ref([])
@@ -23,7 +25,13 @@
 
   const handleExport = async (payload) => {
     try {
-      const response = await requestExport(payload)
+      const response = await guardRequestWithMessage(requestExport(payload), null, {
+        timeoutMs,
+        timeoutMessage: '瀵煎嚭璇锋眰瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      if (!response) {
+        return
+      }
       if (!response.ok) {
         throw new Error(`瀵煎嚭澶辫触 (${response.status})`)
       }
@@ -52,10 +60,16 @@
     previewMeta.value = {}
 
     try {
-      const records = await resolvePrintRecords(payload)
+      const records = await guardRequestWithMessage(resolvePrintRecords(payload), null, {
+        timeoutMs,
+        timeoutMessage: '鎵撳嵃鏁版嵁鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
       if (activePrintToken.value !== token) {
         return
       }
+      if (!records) {
+        return
+      }
 
       const rows = buildPreviewRows(records)
       previewRows.value = rows
diff --git a/rsf-design/src/views/system/config/configPage.helpers.js b/rsf-design/src/views/system/config/configPage.helpers.js
new file mode 100644
index 0000000..4564433
--- /dev/null
+++ b/rsf-design/src/views/system/config/configPage.helpers.js
@@ -0,0 +1,119 @@
+const CONFIG_TYPE_OPTIONS = [
+  { label: 'boolean', value: 1 },
+  { label: 'number', value: 2 },
+  { label: 'string', value: 3 },
+  { label: 'json', value: 4 },
+  { label: 'date', value: 5 }
+]
+
+export function createConfigSearchState() {
+  return {
+    condition: '',
+    flag: '',
+    type: '',
+    status: ''
+  }
+}
+
+export function createConfigFormState() {
+  return {
+    id: null,
+    uuid: '',
+    name: '',
+    flag: '',
+    type: 3,
+    val: '',
+    content: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getConfigPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getConfigTypeOptions() {
+  return CONFIG_TYPE_OPTIONS
+}
+
+export function getConfigTypeMeta(type) {
+  const normalizedType = Number(type)
+  return CONFIG_TYPE_OPTIONS.find((item) => item.value === normalizedType) || { label: '-', value: normalizedType || '' }
+}
+
+export function getConfigStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function buildConfigSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    flag: String(params.flag || '').trim(),
+    ...(params.type !== '' && params.type !== null && params.type !== undefined ? { type: Number(params.type) } : {}),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildConfigPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildConfigSearchParams(params)
+  }
+}
+
+export function buildConfigDialogModel(record = {}) {
+  return {
+    ...createConfigFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    uuid: record.uuid || '',
+    name: record.name || '',
+    flag: record.flag || '',
+    type: record.type !== undefined && record.type !== null ? Number(record.type) : 3,
+    val: record.val || '',
+    content: record.content || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildConfigSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    uuid: String(formData.uuid || '').trim(),
+    name: String(formData.name || '').trim(),
+    flag: String(formData.flag || '').trim(),
+    type: Number(formData.type || 3),
+    val: String(formData.val || '').trim(),
+    content: String(formData.content || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeConfigListRow(record = {}) {
+  const statusMeta = getConfigStatusMeta(record.status)
+  return {
+    ...record,
+    uuid: record.uuid || '',
+    name: record.name || '',
+    flag: record.flag || '',
+    val: record.val || '',
+    content: record.content || '',
+    memo: record.memo || '',
+    typeText: record['type$'] || getConfigTypeMeta(record.type).label,
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/config/configTable.columns.js b/rsf-design/src/views/system/config/configTable.columns.js
new file mode 100644
index 0000000..f61a9e4
--- /dev/null
+++ b/rsf-design/src/views/system/config/configTable.columns.js
@@ -0,0 +1,80 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createConfigTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'uuid',
+      label: '缂栧彿',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'flag',
+      label: '鏍囪瘑',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'typeText',
+      label: '绫诲瀷',
+      width: 110,
+      formatter: (row) => row.typeText || '-'
+    },
+    {
+      prop: 'val',
+      label: '鍊�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.val || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/config/index.vue b/rsf-design/src/views/system/config/index.vue
new file mode 100644
index 0000000..a879fe6
--- /dev/null
+++ b/rsf-design/src/views/system/config/index.vue
@@ -0,0 +1,226 @@
+<template>
+  <div class="config-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板閰嶇疆</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <ConfigDialog
+        v-model:visible="dialogVisible"
+        :config-data="currentConfigData"
+        @submit="handleDialogSubmit"
+      />
+
+      <ConfigDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchConfigPage,
+    fetchDeleteConfig,
+    fetchGetConfigDetail,
+    fetchSaveConfig,
+    fetchUpdateConfig
+  } from '@/api/system-manage'
+  import ConfigDialog from './modules/config-dialog.vue'
+  import ConfigDetailDrawer from './modules/config-detail-drawer.vue'
+  import { createConfigTableColumns } from './configTable.columns'
+  import {
+    buildConfigDialogModel,
+    buildConfigPageQueryParams,
+    buildConfigSavePayload,
+    buildConfigSearchParams,
+    createConfigSearchState,
+    getConfigPaginationKey,
+    getConfigTypeOptions,
+    normalizeConfigListRow
+  } from './configPage.helpers'
+
+  defineOptions({ name: 'Config' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createConfigSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ厤缃悕绉�'
+      }
+    },
+    {
+      label: '鏍囪瘑',
+      key: 'flag',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ厤缃爣璇�'
+      }
+    },
+    {
+      label: '绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getConfigTypeOptions()
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeConfigListRow(await fetchGetConfigDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇閰嶇疆璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentConfigData.value = buildConfigDialogModel(await fetchGetConfigDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇閰嶇疆璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchConfigPage,
+      apiParams: buildConfigPageQueryParams(searchForm.value),
+      paginationKey: getConfigPaginationKey(),
+      columnsFactory: () =>
+        createConfigTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeConfigListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentConfigData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildConfigDialogModel(),
+    buildEditModel: (record) => buildConfigDialogModel(record),
+    buildSavePayload: (formData) => buildConfigSavePayload(formData),
+    saveRequest: fetchSaveConfig,
+    updateRequest: fetchUpdateConfig,
+    deleteRequest: fetchDeleteConfig,
+    entityName: '閰嶇疆',
+    resolveRecordLabel: (record) => record?.name || record?.flag || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildConfigSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createConfigSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/config/modules/config-detail-drawer.vue b/rsf-design/src/views/system/config/modules/config-detail-drawer.vue
new file mode 100644
index 0000000..0e7105d
--- /dev/null
+++ b/rsf-design/src/views/system/config/modules/config-detail-drawer.vue
@@ -0,0 +1,42 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="閰嶇疆璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="缂栧彿">{{ displayData.uuid || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏍囪瘑">{{ displayData.flag || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="绫诲瀷">{{ displayData.typeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍊�">{{ displayData.val || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏂囨湰">{{ displayData.content || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeConfigListRow } from '../configPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeConfigListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/config/modules/config-dialog.vue b/rsf-design/src/views/system/config/modules/config-dialog.vue
new file mode 100644
index 0000000..bb56df3
--- /dev/null
+++ b/rsf-design/src/views/system/config/modules/config-dialog.vue
@@ -0,0 +1,181 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildConfigDialogModel, createConfigFormState, getConfigTypeOptions } from '../configPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    configData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createConfigFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫閰嶇疆' : '鏂板閰嶇疆'))
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ラ厤缃悕绉�', trigger: 'blur' }],
+    flag: [{ required: true, message: '璇疯緭鍏ラ厤缃爣璇�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '缂栧彿',
+      key: 'uuid',
+      type: 'input',
+      props: {
+        disabled: true,
+        placeholder: '鏂板鍚庤嚜鍔ㄧ敓鎴�'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ厤缃悕绉�'
+      }
+    },
+    {
+      label: '鏍囪瘑',
+      key: 'flag',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ厤缃爣璇�'
+      }
+    },
+    {
+      label: '绫诲瀷',
+      key: 'type',
+      type: 'select',
+      props: {
+        options: getConfigTypeOptions(),
+        placeholder: '璇烽�夋嫨绫诲瀷'
+      }
+    },
+    {
+      label: '鍊�',
+      key: 'val',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ厤缃��'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '鏂囨湰',
+      key: 'content',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ラ厤缃枃鏈�'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createConfigFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildConfigDialogModel(props.configData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.configData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/dept/deptPage.helpers.js b/rsf-design/src/views/system/dept/deptPage.helpers.js
new file mode 100644
index 0000000..47fad23
--- /dev/null
+++ b/rsf-design/src/views/system/dept/deptPage.helpers.js
@@ -0,0 +1,137 @@
+function createDeptSearchState() {
+  return {
+    condition: ''
+  }
+}
+
+function createDeptFormState() {
+  return {
+    id: null,
+    parentId: 0,
+    name: '',
+    fullName: '',
+    leader: '',
+    sort: 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+function normalizeDeptTreeRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records.map((item) => {
+    const id = normalizeDeptId(item?.id)
+    const children = normalizeDeptTreeRows(item?.children || [])
+    return {
+      ...item,
+      id,
+      parentId: normalizeDeptId(item?.parentId),
+      name: item?.name || '',
+      fullName: item?.fullName || '-',
+      leader: item?.leader || '-',
+      sort: normalizeDeptNumber(item?.sort, 0),
+      status: normalizeDeptNumber(item?.status, 1),
+      statusBool: normalizeDeptNumber(item?.status, 1) === 1,
+      statusText: normalizeDeptNumber(item?.status, 1) === 1 ? '姝e父' : '绂佺敤',
+      statusType: normalizeDeptNumber(item?.status, 1) === 1 ? 'success' : 'danger',
+      updateTimeText: item?.updateTime$ || item?.updateTime || '-',
+      createTimeText: item?.createTime$ || item?.createTime || '-',
+      memo: item?.memo || '-',
+      children
+    }
+  })
+}
+
+function buildDeptTreeOptions(records = [], currentId = null) {
+  const normalizedCurrentId = normalizeDeptId(currentId)
+
+  return normalizeDeptTreeRows(records)
+    .filter((item) => item.id !== normalizedCurrentId)
+    .map((item) => mapDeptTreeOption(item, normalizedCurrentId))
+    .filter(Boolean)
+}
+
+function mapDeptTreeOption(item, currentId) {
+  if (!item || item.id === currentId) {
+    return null
+  }
+
+  const children = Array.isArray(item.children)
+    ? item.children.map((child) => mapDeptTreeOption(child, currentId)).filter(Boolean)
+    : []
+
+  return {
+    value: item.id,
+    label: item.name || '-',
+    children
+  }
+}
+
+function buildDeptSearchParams(params = {}) {
+  return {
+    ...(params.condition !== undefined ? { condition: String(params.condition || '').trim() } : {})
+  }
+}
+
+function buildDeptDialogModel(record = {}) {
+  return {
+    id: normalizeDeptNullableId(record?.id),
+    parentId: normalizeDeptId(record?.parentId),
+    name: record?.name || '',
+    fullName: record?.fullName || '',
+    leader: record?.leader || '',
+    sort: normalizeDeptNumber(record?.sort, 0),
+    status: normalizeDeptNumber(record?.status, 1),
+    memo: record?.memo || ''
+  }
+}
+
+function buildDeptSavePayload(form = {}) {
+  return {
+    ...(form.id ? { id: normalizeDeptId(form.id) } : {}),
+    parentId: normalizeDeptId(form.parentId),
+    name: String(form.name || '').trim(),
+    fullName: String(form.fullName || '').trim(),
+    leader: String(form.leader || '').trim(),
+    sort: normalizeDeptNumber(form.sort, 0),
+    status: normalizeDeptNumber(form.status, 1),
+    memo: String(form.memo || '').trim()
+  }
+}
+
+function normalizeDeptId(value) {
+  if (value === null || value === undefined || value === '') {
+    return 0
+  }
+  const normalized = Number(value)
+  return Number.isFinite(normalized) ? normalized : 0
+}
+
+function normalizeDeptNullableId(value) {
+  if (value === null || value === undefined || value === '') {
+    return null
+  }
+  const normalized = Number(value)
+  return Number.isFinite(normalized) ? normalized : null
+}
+
+function normalizeDeptNumber(value, fallback = 0) {
+  if (value === null || value === undefined || value === '') {
+    return fallback
+  }
+  const normalized = Number(value)
+  return Number.isFinite(normalized) ? normalized : fallback
+}
+
+export {
+  buildDeptDialogModel,
+  buildDeptSavePayload,
+  buildDeptSearchParams,
+  buildDeptTreeOptions,
+  createDeptFormState,
+  createDeptSearchState,
+  normalizeDeptTreeRows
+}
diff --git a/rsf-design/src/views/system/dept/deptTable.columns.js b/rsf-design/src/views/system/dept/deptTable.columns.js
new file mode 100644
index 0000000..001815e
--- /dev/null
+++ b/rsf-design/src/views/system/dept/deptTable.columns.js
@@ -0,0 +1,69 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createDeptTableColumns({ handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'name',
+      label: '閮ㄩ棬鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fullName',
+      label: '閮ㄩ棬鍏ㄧО',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.fullName || '-'
+    },
+    {
+      prop: 'leader',
+      label: '璐熻矗浜�',
+      minWidth: 140,
+      formatter: (row) => row.leader || '-'
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭',
+      width: 90
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 140,
+      align: 'right',
+      formatter: (row) =>
+        h('div', { class: 'flex justify-end' }, [
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'delete',
+            onClick: () => handleDelete(row)
+          })
+        ])
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/dept/index.vue b/rsf-design/src/views/system/dept/index.vue
new file mode 100644
index 0000000..2dbb5b6
--- /dev/null
+++ b/rsf-design/src/views/system/dept/index.vue
@@ -0,0 +1,211 @@
+<template>
+  <div class="dept-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :show-expand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader
+        :loading="loading"
+        :show-zebra="false"
+        v-model:columns="columnChecks"
+        @refresh="handleRefresh"
+      >
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="handleAdd" v-ripple>鏂板閮ㄩ棬</ElButton>
+            <ElButton @click="toggleExpand" v-ripple>
+              {{ isExpanded ? '鏀惰捣' : '灞曞紑' }}
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        ref="tableRef"
+        rowKey="id"
+        :loading="loading"
+        :columns="columns"
+        :data="tableData"
+        :stripe="false"
+        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
+        :default-expand-all="false"
+      />
+
+      <DeptDialog
+        v-model:visible="dialogVisible"
+        :dept-data="currentDeptData"
+        :dept-tree-options="deptTreeOptions"
+        @submit="handleDialogSubmit"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import {
+    fetchDeleteDept,
+    fetchGetDeptDetail,
+    fetchGetDeptTree,
+    fetchSaveDept,
+    fetchUpdateDept
+  } from '@/api/system-manage'
+  import DeptDialog from './modules/dept-dialog.vue'
+  import { createDeptTableColumns } from './deptTable.columns'
+  import {
+    buildDeptDialogModel,
+    buildDeptSavePayload,
+    buildDeptSearchParams,
+    buildDeptTreeOptions,
+    createDeptSearchState,
+    normalizeDeptTreeRows
+  } from './deptPage.helpers'
+
+  defineOptions({ name: 'Dept' })
+
+  const loading = ref(false)
+  const isExpanded = ref(false)
+  const tableRef = ref()
+  const dialogVisible = ref(false)
+  const currentDeptData = ref(buildDeptDialogModel())
+  const deptTreeOptions = ref([])
+  const tableData = ref([])
+
+  const initialSearchState = createDeptSearchState()
+  const searchForm = reactive({ ...initialSearchState })
+
+  const searchItems = computed(() => [
+    {
+      label: '閮ㄩ棬鍚嶇О',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ラ儴闂ㄥ悕绉�'
+      }
+    }
+  ])
+
+  const { columnChecks, columns } = useTableColumns(() =>
+    createDeptTableColumns({
+      handleEdit: handleEdit,
+      handleDelete: handleDelete
+    })
+  )
+
+  onMounted(() => {
+    loadDeptTree()
+  })
+
+  async function loadDeptTree() {
+    loading.value = true
+    try {
+      const tree = await guardRequestWithMessage(
+        fetchGetDeptTree(buildDeptSearchParams(searchForm)),
+        [],
+        {
+          timeoutMessage: '閮ㄩ棬鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+        }
+      )
+      const normalizedRows = normalizeDeptTreeRows(tree || [])
+      tableData.value = normalizedRows
+      deptTreeOptions.value = buildDeptTreeOptions(normalizedRows)
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSearch() {
+    loadDeptTree()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm, { ...initialSearchState })
+    loadDeptTree()
+  }
+
+  function handleRefresh() {
+    loadDeptTree()
+  }
+
+  function handleAdd() {
+    currentDeptData.value = buildDeptDialogModel()
+    deptTreeOptions.value = buildDeptTreeOptions(tableData.value)
+    dialogVisible.value = true
+  }
+
+  async function handleEdit(row) {
+    try {
+      const detail = await fetchGetDeptDetail(row.id)
+      currentDeptData.value = buildDeptDialogModel(detail || row)
+      deptTreeOptions.value = buildDeptTreeOptions(tableData.value, row.id)
+      dialogVisible.value = true
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇閮ㄩ棬璇︽儏澶辫触')
+    }
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄ら儴闂ㄣ��${row.name || row.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteDept(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await loadDeptTree()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  async function handleDialogSubmit(formData) {
+    const payload = buildDeptSavePayload(formData)
+    if (payload.id && payload.id === payload.parentId) {
+      ElMessage.error('涓婄骇閮ㄩ棬涓嶈兘閫夋嫨褰撳墠閮ㄩ棬')
+      return
+    }
+
+    try {
+      if (payload.id) {
+        await fetchUpdateDept(payload)
+        ElMessage.success('淇敼鎴愬姛')
+      } else {
+        await fetchSaveDept(payload)
+        ElMessage.success('鏂板鎴愬姛')
+      }
+      dialogVisible.value = false
+      currentDeptData.value = buildDeptDialogModel()
+      await loadDeptTree()
+    } catch (error) {
+      ElMessage.error(error?.message || '鎻愪氦澶辫触')
+    }
+  }
+
+  function toggleExpand() {
+    isExpanded.value = !isExpanded.value
+    nextTick(() => {
+      if (tableRef.value?.elTableRef && tableData.value) {
+        const processRows = (rows) => {
+          rows.forEach((row) => {
+            if (row.children?.length) {
+              tableRef.value.elTableRef.toggleRowExpansion(row, isExpanded.value)
+              processRows(row.children)
+            }
+          })
+        }
+        processRows(tableData.value)
+      }
+    })
+  }
+</script>
diff --git a/rsf-design/src/views/system/dept/modules/dept-dialog.vue b/rsf-design/src/views/system/dept/modules/dept-dialog.vue
new file mode 100644
index 0000000..650083b
--- /dev/null
+++ b/rsf-design/src/views/system/dept/modules/dept-dialog.vue
@@ -0,0 +1,183 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="760px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildDeptDialogModel, createDeptFormState } from '../deptPage.helpers'
+
+  const props = defineProps({
+    visible: { required: false, default: false },
+    deptData: { required: false, default: () => ({}) },
+    deptTreeOptions: { required: false, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createDeptFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫閮ㄩ棬' : '鏂板閮ㄩ棬'))
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ラ儴闂ㄥ悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '涓婄骇閮ㄩ棬',
+      key: 'parentId',
+      type: 'treeselect',
+      span: 24,
+      props: {
+        data: props.deptTreeOptions,
+        props: {
+          label: 'label',
+          value: 'value',
+          children: 'children'
+        },
+        placeholder: '璇烽�夋嫨涓婄骇閮ㄩ棬',
+        clearable: false,
+        checkStrictly: true,
+        defaultExpandAll: true
+      }
+    },
+    {
+      label: '閮ㄩ棬鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ儴闂ㄥ悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '閮ㄩ棬鍏ㄧО',
+      key: 'fullName',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ儴闂ㄥ叏绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '璐熻矗浜�',
+      key: 'leader',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ヨ礋璐d汉',
+        clearable: true
+      }
+    },
+    {
+      label: '鎺掑簭',
+      key: 'sort',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '绂佺敤', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�',
+        clearable: true
+      }
+    }
+  ])
+
+  const resetForm = () => {
+    Object.assign(form, createDeptFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  const loadFormData = () => {
+    Object.assign(form, buildDeptDialogModel(props.deptData))
+  }
+
+  const handleSubmit = async () => {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  const handleCancel = () => {
+    emit('update:visible', false)
+  }
+
+  const handleClosed = () => {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => {
+          formRef.value?.clearValidate?.()
+        })
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.deptData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/dict-type/dictTypePage.helpers.js b/rsf-design/src/views/system/dict-type/dictTypePage.helpers.js
new file mode 100644
index 0000000..a141bdc
--- /dev/null
+++ b/rsf-design/src/views/system/dict-type/dictTypePage.helpers.js
@@ -0,0 +1,92 @@
+export function createDictTypeSearchState() {
+  return {
+    condition: '',
+    code: '',
+    name: '',
+    status: ''
+  }
+}
+
+export function createDictTypeFormState() {
+  return {
+    id: null,
+    code: '',
+    name: '',
+    description: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getDictTypePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getDictTypeStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function buildDictTypeSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    code: String(params.code || '').trim(),
+    name: String(params.name || '').trim(),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildDictTypePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildDictTypeSearchParams(params)
+  }
+}
+
+export function buildDictTypeDialogModel(record = {}) {
+  return {
+    ...createDictTypeFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    code: record.code || '',
+    name: record.name || '',
+    description: record.description || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildDictTypeSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    code: String(formData.code || '').trim(),
+    name: String(formData.name || '').trim(),
+    description: String(formData.description || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeDictTypeListRow(record = {}) {
+  const statusMeta = getDictTypeStatusMeta(record.status)
+  return {
+    ...record,
+    code: record.code || '',
+    name: record.name || '',
+    description: record.description || '',
+    memo: record.memo || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || record.updateByLabel || '',
+    createByLabel: record['createBy$'] || record.createByLabel || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/dict-type/dictTypeTable.columns.js b/rsf-design/src/views/system/dict-type/dictTypeTable.columns.js
new file mode 100644
index 0000000..5426adc
--- /dev/null
+++ b/rsf-design/src/views/system/dict-type/dictTypeTable.columns.js
@@ -0,0 +1,74 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createDictTypeTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'code',
+      label: '缂栫爜',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'description',
+      label: '鎻忚堪',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.description || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateByLabel',
+      label: '鏇存柊浜�',
+      width: 120,
+      formatter: (row) => row.updateByLabel || '-'
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/dict-type/index.vue b/rsf-design/src/views/system/dict-type/index.vue
new file mode 100644
index 0000000..b346aad
--- /dev/null
+++ b/rsf-design/src/views/system/dict-type/index.vue
@@ -0,0 +1,225 @@
+<template>
+  <div class="dict-type-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鏁版嵁瀛楀吀</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <DictTypeDialog
+        v-model:visible="dialogVisible"
+        :dict-type-data="currentDictTypeData"
+        @submit="handleDialogSubmit"
+      />
+
+      <DictTypeDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteDictType,
+    fetchDictTypePage,
+    fetchGetDictTypeDetail,
+    fetchSaveDictType,
+    fetchUpdateDictType
+  } from '@/api/system-manage'
+  import DictTypeDialog from './modules/dict-type-dialog.vue'
+  import DictTypeDetailDrawer from './modules/dict-type-detail-drawer.vue'
+  import { createDictTypeTableColumns } from './dictTypeTable.columns'
+  import {
+    buildDictTypeDialogModel,
+    buildDictTypePageQueryParams,
+    buildDictTypeSavePayload,
+    buildDictTypeSearchParams,
+    createDictTypeSearchState,
+    getDictTypePaginationKey,
+    normalizeDictTypeListRow
+  } from './dictTypePage.helpers'
+
+  defineOptions({ name: 'DictType' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createDictTypeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鐮佹垨鍚嶇О'
+      }
+    },
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧鍏哥紪鐮�'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧鍏稿悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeDictTypeListRow(await fetchGetDictTypeDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鏁版嵁瀛楀吀璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentDictTypeData.value = buildDictTypeDialogModel(await fetchGetDictTypeDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鏁版嵁瀛楀吀璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchDictTypePage,
+      apiParams: buildDictTypePageQueryParams(searchForm.value),
+      paginationKey: getDictTypePaginationKey(),
+      columnsFactory: () =>
+        createDictTypeTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeDictTypeListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentDictTypeData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildDictTypeDialogModel(),
+    buildEditModel: (record) => buildDictTypeDialogModel(record),
+    buildSavePayload: (formData) => buildDictTypeSavePayload(formData),
+    saveRequest: fetchSaveDictType,
+    updateRequest: fetchUpdateDictType,
+    deleteRequest: fetchDeleteDictType,
+    entityName: '鏁版嵁瀛楀吀',
+    resolveRecordLabel: (record) => record?.name || record?.code || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildDictTypeSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createDictTypeSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/dict-type/modules/dict-type-detail-drawer.vue b/rsf-design/src/views/system/dict-type/modules/dict-type-detail-drawer.vue
new file mode 100644
index 0000000..9863bb3
--- /dev/null
+++ b/rsf-design/src/views/system/dict-type/modules/dict-type-detail-drawer.vue
@@ -0,0 +1,41 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鏁版嵁瀛楀吀璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="缂栫爜">{{ displayData.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎻忚堪">{{ displayData.description || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeDictTypeListRow } from '../dictTypePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeDictTypeListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/dict-type/modules/dict-type-dialog.vue b/rsf-design/src/views/system/dict-type/modules/dict-type-dialog.vue
new file mode 100644
index 0000000..22abb74
--- /dev/null
+++ b/rsf-design/src/views/system/dict-type/modules/dict-type-dialog.vue
@@ -0,0 +1,154 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildDictTypeDialogModel, createDictTypeFormState } from '../dictTypePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    dictTypeData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createDictTypeFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鏁版嵁瀛楀吀' : '鏂板鏁版嵁瀛楀吀'))
+
+  const rules = computed(() => ({
+    code: [{ required: true, message: '璇疯緭鍏ュ瓧鍏哥紪鐮�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ瓧鍏稿悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '缂栫爜',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧鍏哥紪鐮�'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧鍏稿悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '鎻忚堪',
+      key: 'description',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ瓧鍏告弿杩�'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createDictTypeFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildDictTypeDialogModel(props.dictTypeData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.dictTypeData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/fields-item/fieldsItemPage.helpers.js b/rsf-design/src/views/system/fields-item/fieldsItemPage.helpers.js
new file mode 100644
index 0000000..75746c2
--- /dev/null
+++ b/rsf-design/src/views/system/fields-item/fieldsItemPage.helpers.js
@@ -0,0 +1,136 @@
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1 },
+  { label: '鍐荤粨', value: 0 }
+]
+
+export function createFieldsItemSearchState() {
+  return {
+    condition: '',
+    uuid: '',
+    fieldsId: '',
+    value: '',
+    matnrId: '',
+    shiperId: '',
+    status: ''
+  }
+}
+
+export function createFieldsItemFormState() {
+  return {
+    id: null,
+    uuid: '',
+    fieldsId: null,
+    value: '',
+    matnrId: null,
+    shiperId: null,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getFieldsItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getFieldsItemStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function getFieldsItemStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function buildFieldsItemSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    uuid: String(params.uuid || '').trim(),
+    ...(params.fieldsId !== '' && params.fieldsId !== null && params.fieldsId !== undefined
+      ? { fieldsId: Number(params.fieldsId) }
+      : {}),
+    value: String(params.value || '').trim(),
+    ...(params.matnrId !== '' && params.matnrId !== null && params.matnrId !== undefined
+      ? { matnrId: Number(params.matnrId) }
+      : {}),
+    ...(params.shiperId !== '' && params.shiperId !== null && params.shiperId !== undefined
+      ? { shiperId: Number(params.shiperId) }
+      : {}),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildFieldsItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFieldsItemSearchParams(params)
+  }
+}
+
+export function buildFieldsItemDialogModel(record = {}) {
+  return {
+    ...createFieldsItemFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    uuid: record.uuid || '',
+    fieldsId:
+      record.fieldsId !== undefined && record.fieldsId !== null && record.fieldsId !== ''
+        ? Number(record.fieldsId)
+        : null,
+    value: record.value || '',
+    matnrId:
+      record.matnrId !== undefined && record.matnrId !== null && record.matnrId !== ''
+        ? Number(record.matnrId)
+        : null,
+    shiperId:
+      record.shiperId !== undefined && record.shiperId !== null && record.shiperId !== ''
+        ? Number(record.shiperId)
+        : null,
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildFieldsItemSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    uuid: String(formData.uuid || '').trim(),
+    ...(formData.fieldsId !== '' && formData.fieldsId !== null && formData.fieldsId !== undefined
+      ? { fieldsId: Number(formData.fieldsId) }
+      : {}),
+    value: String(formData.value || '').trim(),
+    ...(formData.matnrId !== '' && formData.matnrId !== null && formData.matnrId !== undefined
+      ? { matnrId: Number(formData.matnrId) }
+      : {}),
+    ...(formData.shiperId !== '' && formData.shiperId !== null && formData.shiperId !== undefined
+      ? { shiperId: Number(formData.shiperId) }
+      : {}),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeFieldsItemListRow(record = {}) {
+  const statusMeta = getFieldsItemStatusMeta(record.status)
+  return {
+    ...record,
+    uuid: record.uuid || '',
+    fieldsId: record.fieldsId ?? '',
+    value: record.value || '',
+    matnrId: record.matnrId ?? '',
+    shiperId: record.shiperId ?? '',
+    memo: record.memo || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || record.updateBy || '',
+    createByLabel: record['createBy$'] || record.createBy || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/fields-item/fieldsItemTable.columns.js b/rsf-design/src/views/system/fields-item/fieldsItemTable.columns.js
new file mode 100644
index 0000000..6a17e17
--- /dev/null
+++ b/rsf-design/src/views/system/fields-item/fieldsItemTable.columns.js
@@ -0,0 +1,80 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFieldsItemTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'uuid',
+      label: '鍞竴鏍囪瘑',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsId',
+      label: '鎵╁睍瀛楁鏍囪瘑',
+      width: 130,
+      formatter: (row) => row.fieldsId || '-'
+    },
+    {
+      prop: 'value',
+      label: '瀛楁鍊�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.value || '-'
+    },
+    {
+      prop: 'matnrId',
+      label: '鐗╂枡鏍囪瘑',
+      width: 120,
+      formatter: (row) => row.matnrId || '-'
+    },
+    {
+      prop: 'shiperId',
+      label: '璐т富鏍囪瘑',
+      width: 120,
+      formatter: (row) => row.shiperId || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/fields-item/index.vue b/rsf-design/src/views/system/fields-item/index.vue
new file mode 100644
index 0000000..5773b73
--- /dev/null
+++ b/rsf-design/src/views/system/fields-item/index.vue
@@ -0,0 +1,232 @@
+<template>
+  <div class="fields-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鎵╁睍瀛楁鏄庣粏</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <FieldsItemDialog
+        v-model:visible="dialogVisible"
+        :fields-item-data="currentFieldsItemData"
+        @submit="handleDialogSubmit"
+      />
+
+      <FieldsItemDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteFieldsItem,
+    fetchFieldsItemPage,
+    fetchGetFieldsItemDetail,
+    fetchSaveFieldsItem,
+    fetchUpdateFieldsItem
+  } from '@/api/system-manage'
+  import FieldsItemDialog from './modules/fields-item-dialog.vue'
+  import FieldsItemDetailDrawer from './modules/fields-item-detail-drawer.vue'
+  import { createFieldsItemTableColumns } from './fieldsItemTable.columns'
+  import {
+    buildFieldsItemDialogModel,
+    buildFieldsItemPageQueryParams,
+    buildFieldsItemSavePayload,
+    buildFieldsItemSearchParams,
+    createFieldsItemSearchState,
+    getFieldsItemPaginationKey,
+    getFieldsItemStatusOptions,
+    normalizeFieldsItemListRow
+  } from './fieldsItemPage.helpers'
+
+  defineOptions({ name: 'FieldsItem' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createFieldsItemSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ敮涓�鏍囪瘑鎴栧瓧娈靛��'
+      }
+    },
+    {
+      label: '鍞竴鏍囪瘑',
+      key: 'uuid',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ敮涓�鏍囪瘑'
+      }
+    },
+    {
+      label: '鎵╁睍瀛楁鏍囪瘑',
+      key: 'fieldsId',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ墿灞曞瓧娈垫爣璇�'
+      }
+    },
+    {
+      label: '瀛楁鍊�',
+      key: 'value',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛��'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getFieldsItemStatusOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeFieldsItemListRow(await fetchGetFieldsItemDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鎵╁睍瀛楁鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentFieldsItemData.value = buildFieldsItemDialogModel(await fetchGetFieldsItemDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鎵╁睍瀛楁鏄庣粏璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchFieldsItemPage,
+      apiParams: buildFieldsItemPageQueryParams(searchForm.value),
+      paginationKey: getFieldsItemPaginationKey(),
+      columnsFactory: () =>
+        createFieldsItemTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeFieldsItemListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentFieldsItemData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildFieldsItemDialogModel(),
+    buildEditModel: (record) => buildFieldsItemDialogModel(record),
+    buildSavePayload: (formData) => buildFieldsItemSavePayload(formData),
+    saveRequest: fetchSaveFieldsItem,
+    updateRequest: fetchUpdateFieldsItem,
+    deleteRequest: fetchDeleteFieldsItem,
+    entityName: '鎵╁睍瀛楁鏄庣粏',
+    resolveRecordLabel: (record) => record?.uuid || record?.value || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildFieldsItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createFieldsItemSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/fields-item/modules/fields-item-detail-drawer.vue b/rsf-design/src/views/system/fields-item/modules/fields-item-detail-drawer.vue
new file mode 100644
index 0000000..673076e
--- /dev/null
+++ b/rsf-design/src/views/system/fields-item/modules/fields-item-detail-drawer.vue
@@ -0,0 +1,52 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鎵╁睍瀛楁鏄庣粏璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="鍞竴鏍囪瘑">{{ displayData.uuid || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵╁睍瀛楁鏍囪瘑">{{ displayData.fieldsId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀛楁鍊�">{{ displayData.value || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鏍囪瘑">{{ displayData.matnrId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璐т富鏍囪瘑">{{ displayData.shiperId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeFieldsItemListRow } from '../fieldsItemPage.helpers'
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    },
+    detailData: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeFieldsItemListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/fields-item/modules/fields-item-dialog.vue b/rsf-design/src/views/system/fields-item/modules/fields-item-dialog.vue
new file mode 100644
index 0000000..486f286
--- /dev/null
+++ b/rsf-design/src/views/system/fields-item/modules/fields-item-dialog.vue
@@ -0,0 +1,174 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildFieldsItemDialogModel,
+    createFieldsItemFormState,
+    getFieldsItemStatusOptions
+  } from '../fieldsItemPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    fieldsItemData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createFieldsItemFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鎵╁睍瀛楁鏄庣粏' : '鏂板鎵╁睍瀛楁鏄庣粏'))
+
+  const rules = computed(() => ({
+    uuid: [{ required: true, message: '璇疯緭鍏ュ敮涓�鏍囪瘑', trigger: 'blur' }],
+    fieldsId: [{ required: true, message: '璇疯緭鍏ユ墿灞曞瓧娈垫爣璇�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '鍞竴鏍囪瘑',
+      key: 'uuid',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ敮涓�鏍囪瘑'
+      }
+    },
+    {
+      label: '鎵╁睍瀛楁鏍囪瘑',
+      key: 'fieldsId',
+      type: 'input-number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ墿灞曞瓧娈垫爣璇�'
+      }
+    },
+    {
+      label: '瀛楁鍊�',
+      key: 'value',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛��'
+      }
+    },
+    {
+      label: '鐗╂枡鏍囪瘑',
+      key: 'matnrId',
+      type: 'input-number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ョ墿鏂欐爣璇�'
+      }
+    },
+    {
+      label: '璐т富鏍囪瘑',
+      key: 'shiperId',
+      type: 'input-number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ揣涓绘爣璇�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getFieldsItemStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createFieldsItemFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildFieldsItemDialogModel(props.fieldsItemData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.fieldsItemData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/fields/fieldsPage.helpers.js b/rsf-design/src/views/system/fields/fieldsPage.helpers.js
new file mode 100644
index 0000000..37b3043
--- /dev/null
+++ b/rsf-design/src/views/system/fields/fieldsPage.helpers.js
@@ -0,0 +1,134 @@
+const UNIQUE_OPTIONS = [
+  { label: '闈炲繀濉�', value: 0, text: '涓绘暟鎹�' },
+  { label: '蹇呭~', value: 1, text: '涓氬姟鏁版嵁' }
+]
+
+const ENABLE_OPTIONS = [
+  { label: '涓嶅惎鐢�', value: 0, text: '涓嶅惎鐢�' },
+  { label: '鍚敤', value: 1, text: '鍚敤' }
+]
+
+export function createFieldsSearchState() {
+  return {
+    condition: '',
+    fields: '',
+    fieldsAlise: '',
+    unique: '',
+    flagEnable: '',
+    status: ''
+  }
+}
+
+export function createFieldsFormState() {
+  return {
+    id: null,
+    fields: '',
+    fieldsAlise: '',
+    unique: 0,
+    flagEnable: 1,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getFieldsPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getFieldsUniqueOptions() {
+  return UNIQUE_OPTIONS
+}
+
+export function getFieldsEnableOptions() {
+  return ENABLE_OPTIONS
+}
+
+export function getFieldsStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function getFieldsUniqueMeta(unique) {
+  const normalized = Number(unique)
+  return UNIQUE_OPTIONS.find((item) => item.value === normalized) || { label: '-', value: normalized, text: '-' }
+}
+
+export function getFieldsEnableMeta(flagEnable) {
+  const normalized = Number(flagEnable)
+  return ENABLE_OPTIONS.find((item) => item.value === normalized) || { label: '-', value: normalized, text: '-' }
+}
+
+export function buildFieldsSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    fields: String(params.fields || '').trim(),
+    fieldsAlise: String(params.fieldsAlise || '').trim(),
+    ...(params.unique !== '' && params.unique !== null && params.unique !== undefined
+      ? { unique: Number(params.unique) }
+      : {}),
+    ...(params.flagEnable !== '' && params.flagEnable !== null && params.flagEnable !== undefined
+      ? { flagEnable: Number(params.flagEnable) }
+      : {}),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildFieldsPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFieldsSearchParams(params)
+  }
+}
+
+export function buildFieldsDialogModel(record = {}) {
+  return {
+    ...createFieldsFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    fields: record.fields || '',
+    fieldsAlise: record.fieldsAlise || '',
+    unique: record.unique !== undefined && record.unique !== null ? Number(record.unique) : 0,
+    flagEnable: record.flagEnable !== undefined && record.flagEnable !== null ? Number(record.flagEnable) : 1,
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildFieldsSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    fields: String(formData.fields || '').trim(),
+    fieldsAlise: String(formData.fieldsAlise || '').trim(),
+    unique: Number(formData.unique ?? 0),
+    flagEnable: Number(formData.flagEnable ?? 1),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeFieldsListRow(record = {}) {
+  const statusMeta = getFieldsStatusMeta(record.status)
+  const uniqueMeta = getFieldsUniqueMeta(record.unique)
+  const enableMeta = getFieldsEnableMeta(record.flagEnable)
+  return {
+    ...record,
+    fields: record.fields || '',
+    fieldsAlise: record.fieldsAlise || '',
+    memo: record.memo || '',
+    uniqueText: record['unique$'] || uniqueMeta.text,
+    flagEnableText: record['flagEnable$'] || enableMeta.text,
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || record.updateByLabel || '',
+    createByLabel: record['createBy$'] || record.createByLabel || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/fields/fieldsTable.columns.js b/rsf-design/src/views/system/fields/fieldsTable.columns.js
new file mode 100644
index 0000000..9e45e5d
--- /dev/null
+++ b/rsf-design/src/views/system/fields/fieldsTable.columns.js
@@ -0,0 +1,73 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFieldsTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'fields',
+      label: '瀛楁鍚�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'fieldsAlise',
+      label: '瀛楁鍒悕',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'uniqueText',
+      label: '瀛楁灞炴��',
+      width: 120,
+      formatter: (row) => row.uniqueText || '-'
+    },
+    {
+      prop: 'flagEnableText',
+      label: '鍚敤鐘舵��',
+      width: 120,
+      formatter: (row) => row.flagEnableText || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/fields/index.vue b/rsf-design/src/views/system/fields/index.vue
new file mode 100644
index 0000000..8a23c68
--- /dev/null
+++ b/rsf-design/src/views/system/fields/index.vue
@@ -0,0 +1,233 @@
+<template>
+  <div class="fields-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鎵╁睍瀛楁</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <FieldsDialog
+        v-model:visible="dialogVisible"
+        :fields-data="currentFieldsData"
+        @submit="handleDialogSubmit"
+      />
+
+      <FieldsDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteFields,
+    fetchFieldsPage,
+    fetchGetFieldsDetail,
+    fetchSaveFields,
+    fetchUpdateFields
+  } from '@/api/system-manage'
+  import FieldsDialog from './modules/fields-dialog.vue'
+  import FieldsDetailDrawer from './modules/fields-detail-drawer.vue'
+  import { createFieldsTableColumns } from './fieldsTable.columns'
+  import {
+    buildFieldsDialogModel,
+    buildFieldsPageQueryParams,
+    buildFieldsSavePayload,
+    buildFieldsSearchParams,
+    createFieldsSearchState,
+    getFieldsEnableOptions,
+    getFieldsPaginationKey,
+    getFieldsUniqueOptions,
+    normalizeFieldsListRow
+  } from './fieldsPage.helpers'
+
+  defineOptions({ name: 'Fields' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createFieldsSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛悕鎴栧埆鍚�'
+      }
+    },
+    {
+      label: '瀛楁鍚�',
+      key: 'fields',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛悕'
+      }
+    },
+    {
+      label: '瀛楁鍒悕',
+      key: 'fieldsAlise',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛埆鍚�'
+      }
+    },
+    {
+      label: '瀛楁灞炴��',
+      key: 'unique',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getFieldsUniqueOptions()
+      }
+    },
+    {
+      label: '鍚敤鐘舵��',
+      key: 'flagEnable',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getFieldsEnableOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeFieldsListRow(await fetchGetFieldsDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鎵╁睍瀛楁璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentFieldsData.value = buildFieldsDialogModel(await fetchGetFieldsDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鎵╁睍瀛楁璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchFieldsPage,
+      apiParams: buildFieldsPageQueryParams(searchForm.value),
+      paginationKey: getFieldsPaginationKey(),
+      columnsFactory: () =>
+        createFieldsTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeFieldsListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentFieldsData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildFieldsDialogModel(),
+    buildEditModel: (record) => buildFieldsDialogModel(record),
+    buildSavePayload: (formData) => buildFieldsSavePayload(formData),
+    saveRequest: fetchSaveFields,
+    updateRequest: fetchUpdateFields,
+    deleteRequest: fetchDeleteFields,
+    entityName: '鎵╁睍瀛楁',
+    resolveRecordLabel: (record) => record?.fieldsAlise || record?.fields || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildFieldsSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createFieldsSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/fields/modules/fields-detail-drawer.vue b/rsf-design/src/views/system/fields/modules/fields-detail-drawer.vue
new file mode 100644
index 0000000..3922ac4
--- /dev/null
+++ b/rsf-design/src/views/system/fields/modules/fields-detail-drawer.vue
@@ -0,0 +1,42 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鎵╁睍瀛楁璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="瀛楁鍚�">{{ displayData.fields || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀛楁鍒悕">{{ displayData.fieldsAlise || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀛楁灞炴��">{{ displayData.uniqueText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍚敤鐘舵��">{{ displayData.flagEnableText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeFieldsListRow } from '../fieldsPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeFieldsListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/fields/modules/fields-dialog.vue b/rsf-design/src/views/system/fields/modules/fields-dialog.vue
new file mode 100644
index 0000000..ec28062
--- /dev/null
+++ b/rsf-design/src/views/system/fields/modules/fields-dialog.vue
@@ -0,0 +1,166 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildFieldsDialogModel,
+    createFieldsFormState,
+    getFieldsEnableOptions,
+    getFieldsUniqueOptions
+  } from '../fieldsPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    fieldsData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createFieldsFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鎵╁睍瀛楁' : '鏂板鎵╁睍瀛楁'))
+
+  const rules = computed(() => ({
+    fields: [{ required: true, message: '璇疯緭鍏ュ瓧娈靛悕', trigger: 'blur' }],
+    fieldsAlise: [{ required: true, message: '璇疯緭鍏ュ瓧娈靛埆鍚�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '瀛楁鍚�',
+      key: 'fields',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛悕'
+      }
+    },
+    {
+      label: '瀛楁鍒悕',
+      key: 'fieldsAlise',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛埆鍚�'
+      }
+    },
+    {
+      label: '瀛楁灞炴��',
+      key: 'unique',
+      type: 'select',
+      props: {
+        options: getFieldsUniqueOptions(),
+        placeholder: '璇烽�夋嫨瀛楁灞炴��'
+      }
+    },
+    {
+      label: '鍚敤鐘舵��',
+      key: 'flagEnable',
+      type: 'select',
+      props: {
+        options: getFieldsEnableOptions(),
+        placeholder: '璇烽�夋嫨鍚敤鐘舵��'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createFieldsFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildFieldsDialogModel(props.fieldsData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.fieldsData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/flow-instance/flowInstancePage.helpers.js b/rsf-design/src/views/system/flow-instance/flowInstancePage.helpers.js
new file mode 100644
index 0000000..e46da7e
--- /dev/null
+++ b/rsf-design/src/views/system/flow-instance/flowInstancePage.helpers.js
@@ -0,0 +1,175 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const FLOW_INSTANCE_REPORT_TITLE = '娴佺▼瀹炰緥鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeDateTime(value) {
+  return normalizeText(value) || '--'
+}
+
+export function createFlowInstanceSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    flowInstanceNo: '',
+    taskId: '',
+    taskNo: '',
+    nodeInstanceId: '',
+    nodeCode: '',
+    flowTemplateId: '',
+    flowTemplateCode: '',
+    templateVersion: '',
+    currentStepCode: '',
+    currentStepOrder: '',
+    executeResult: '',
+    errorCode: '',
+    errorMessage: '',
+    startTime: '',
+    endTime: '',
+    timeoutAt: '',
+    durationSeconds: '',
+    retryTimes: '',
+    lastRetryTime: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getFlowInstancePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildFlowInstanceSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'flowInstanceNo',
+    'taskNo',
+    'nodeCode',
+    'flowTemplateCode',
+    'currentStepCode',
+    'executeResult',
+    'errorCode',
+    'errorMessage',
+    'startTime',
+    'endTime',
+    'timeoutAt',
+    'lastRetryTime',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;[
+    'taskId',
+    'nodeInstanceId',
+    'flowTemplateId',
+    'templateVersion',
+    'currentStepOrder',
+    'durationSeconds',
+    'retryTimes',
+    'status'
+  ].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildFlowInstancePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFlowInstanceSearchParams(params)
+  }
+}
+
+export function normalizeFlowInstanceRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    flowInstanceNo: normalizeText(record.flowInstanceNo) || '--',
+    taskNo: normalizeText(record.taskNo) || '--',
+    nodeCode: normalizeText(record.nodeCode) || '--',
+    flowTemplateCode: normalizeText(record.flowTemplateCode) || '--',
+    currentStepCode: normalizeText(record.currentStepCode) || '--',
+    executeResult: normalizeText(record.executeResult) || '--',
+    errorMessage: normalizeText(record.errorMessage) || '--',
+    startTimeText: normalizeDateTime(record['startTime$'] || record.startTimeText || record.startTime),
+    endTimeText: normalizeDateTime(record['endTime$'] || record.endTimeText || record.endTime),
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getFlowInstanceReportColumns() {
+  return [
+    { prop: 'flowInstanceNo', label: '娴佺▼瀹炰緥鍙�' },
+    { prop: 'taskNo', label: '浠诲姟鍙�' },
+    { prop: 'nodeCode', label: '鑺傜偣缂栫爜' },
+    { prop: 'flowTemplateCode', label: '娴佺▼妯℃澘缂栫爜' },
+    { prop: 'currentStepCode', label: '褰撳墠姝ラ' },
+    { prop: 'executeResult', label: '鎵ц缁撴灉' },
+    { prop: 'errorMessage', label: '閿欒淇℃伅' },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�' },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildFlowInstancePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeFlowInstanceRow(record)
+    return {
+      flowInstanceNo: row.flowInstanceNo,
+      taskNo: row.taskNo,
+      nodeCode: row.nodeCode,
+      flowTemplateCode: row.flowTemplateCode,
+      currentStepCode: row.currentStepCode,
+      executeResult: row.executeResult,
+      errorMessage: row.errorMessage,
+      startTimeText: row.startTimeText,
+      endTimeText: row.endTimeText,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/flow-instance/flowInstanceTable.columns.js b/rsf-design/src/views/system/flow-instance/flowInstanceTable.columns.js
new file mode 100644
index 0000000..58785d0
--- /dev/null
+++ b/rsf-design/src/views/system/flow-instance/flowInstanceTable.columns.js
@@ -0,0 +1,35 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFlowInstanceTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'flowInstanceNo', label: '娴佺▼瀹炰緥鍙�', minWidth: 180, showOverflowTooltip: true },
+    { prop: 'taskNo', label: '浠诲姟鍙�', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'nodeCode', label: '鑺傜偣缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'flowTemplateCode', label: '娴佺▼妯℃澘缂栫爜', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'currentStepCode', label: '褰撳墠姝ラ', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'executeResult', label: '鎵ц缁撴灉', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�', minWidth: 170, showOverflowTooltip: true },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.statusType || 'info', effect: 'light' }, () => row?.statusText || '--')
+    },
+    { prop: 'memo', label: '澶囨敞', minWidth: 180, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) => h(ArtButtonTable, { icon: 'ri:eye-line', onClick: () => handleView?.(row) })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/flow-instance/index.vue b/rsf-design/src/views/system/flow-instance/index.vue
new file mode 100644
index 0000000..5e0851c
--- /dev/null
+++ b/rsf-design/src/views/system/flow-instance/index.vue
@@ -0,0 +1,117 @@
+<template>
+  <div class="flow-instance-page art-full-height">
+    <ArtSearchBar v-model="searchForm" :items="searchItems" :showExpand="true" @search="handleSearch" @reset="handleReset" />
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+    <FlowInstanceDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 { fetchExportFlowInstanceReport, fetchFlowInstancePage, fetchGetFlowInstanceDetail, fetchGetFlowInstanceMany } from '@/api/flow-instance'
+  import { buildFlowInstancePageQueryParams, buildFlowInstancePrintRows, buildFlowInstanceSearchParams, createFlowInstanceSearchState, getFlowInstancePaginationKey, getFlowInstanceReportColumns, normalizeFlowInstanceRow, FLOW_INSTANCE_REPORT_TITLE } from './flowInstancePage.helpers'
+  import { createFlowInstanceTableColumns } from './flowInstanceTable.columns'
+  import FlowInstanceDetailDrawer from './modules/flow-instance-detail-drawer.vue'
+
+  defineOptions({ name: 'FlowInstance' })
+  const userStore = useUserStore()
+  const searchForm = ref(createFlowInstanceSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = FLOW_INSTANCE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildFlowInstanceSearchParams(searchForm.value))
+  const reportColumns = getFlowInstanceReportColumns()
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ュ疄渚嬪彿/浠诲姟鍙�/鑺傜偣缂栫爜' } },
+    { label: '娴佺▼瀹炰緥鍙�', key: 'flowInstanceNo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬪疄渚嬪彿' } },
+    { label: '浠诲姟鍙�', key: 'taskNo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヤ换鍔″彿' } },
+    { label: '鑺傜偣缂栫爜', key: 'nodeCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ妭鐐圭紪鐮�' } },
+    { label: '寮�濮嬫棩鏈�', key: 'timeStart', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } },
+    { label: '缁撴潫鏃ユ湡', key: 'timeEnd', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } }
+  ])
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData } =
+    useTable({
+      core: {
+        apiFn: fetchFlowInstancePage,
+        apiParams: buildFlowInstancePageQueryParams(searchForm.value),
+        paginationKey: getFlowInstancePaginationKey(),
+        columnsFactory: () => createFlowInstanceTableColumns({ handleView: openDetail })
+      },
+      transform: {
+        dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeFlowInstanceRow(item)) : [])
+      }
+    })
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetFlowInstanceMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(await fetchFlowInstancePage({ ...reportQueryParams.value, current: 1, pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 })).records
+  }
+  const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } = usePrintExportPage({
+    downloadFileName: 'flow-instance.xlsx',
+    requestExport: (payload) => fetchExportFlowInstanceReport(payload, { headers: { Authorization: userStore.accessToken || '' } }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildFlowInstancePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetFlowInstanceDetail(id)
+    detailData.value = normalizeFlowInstanceRow({ ...fallback, ...detail })
+  }
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+  function handleSearch(params) {
+    replaceSearchParams(buildFlowInstanceSearchParams(params))
+    getData()
+  }
+  function handleReset() {
+    Object.assign(searchForm.value, createFlowInstanceSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-instance/modules/flow-instance-detail-drawer.vue b/rsf-design/src/views/system/flow-instance/modules/flow-instance-detail-drawer.vue
new file mode 100644
index 0000000..746b55d
--- /dev/null
+++ b/rsf-design/src/views/system/flow-instance/modules/flow-instance-detail-drawer.vue
@@ -0,0 +1,35 @@
+<template>
+  <ElDrawer :model-value="visible" title="娴佺▼瀹炰緥璇︽儏" size="72%" @update:model-value="handleVisibleChange">
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="娴佺▼瀹炰緥鍙�">{{ detail.flowInstanceNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鍙�">{{ detail.taskNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣缂栫爜">{{ detail.nodeCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="娴佺▼妯℃澘缂栫爜">{{ detail.flowTemplateCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="褰撳墠姝ラ">{{ detail.currentStepCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц缁撴灉" :span="2">{{ detail.executeResult || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿欒淇℃伅" :span="2">{{ detail.errorMessage || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'FlowInstanceDetailDrawer' })
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+  const emit = defineEmits(['update:visible'])
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-instance/flowStepInstancePage.helpers.js b/rsf-design/src/views/system/flow-step-instance/flowStepInstancePage.helpers.js
new file mode 100644
index 0000000..4339685
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-instance/flowStepInstancePage.helpers.js
@@ -0,0 +1,162 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const FLOW_STEP_INSTANCE_REPORT_TITLE = '娴佺▼姝ラ瀹炰緥鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeDateTime(value) {
+  return normalizeText(value) || '--'
+}
+
+export function createFlowStepInstanceSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    flowInstanceId: '',
+    flowInstanceNo: '',
+    stepOrder: '',
+    stepCode: '',
+    stepName: '',
+    stepType: '',
+    stepTemplateId: '',
+    executeResult: '',
+    errorCode: '',
+    errorMessage: '',
+    startTime: '',
+    endTime: '',
+    durationSeconds: '',
+    inputData: '',
+    outputData: '',
+    retryTimes: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getFlowStepInstancePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildFlowStepInstanceSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'flowInstanceNo',
+    'stepCode',
+    'stepName',
+    'stepType',
+    'executeResult',
+    'errorCode',
+    'errorMessage',
+    'startTime',
+    'endTime',
+    'inputData',
+    'outputData',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['flowInstanceId', 'stepOrder', 'stepTemplateId', 'durationSeconds', 'retryTimes', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildFlowStepInstancePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFlowStepInstanceSearchParams(params)
+  }
+}
+
+export function normalizeFlowStepInstanceRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    flowInstanceNo: normalizeText(record.flowInstanceNo) || '--',
+    stepCode: normalizeText(record.stepCode) || '--',
+    stepName: normalizeText(record.stepName) || '--',
+    stepType: normalizeText(record.stepType) || '--',
+    executeResult: normalizeText(record.executeResult) || '--',
+    errorMessage: normalizeText(record.errorMessage) || '--',
+    startTimeText: normalizeDateTime(record['startTime$'] || record.startTimeText || record.startTime),
+    endTimeText: normalizeDateTime(record['endTime$'] || record.endTimeText || record.endTime),
+    durationSeconds: record.durationSeconds ?? '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getFlowStepInstanceReportColumns() {
+  return [
+    { prop: 'flowInstanceNo', label: '娴佺▼瀹炰緥鍙�' },
+    { prop: 'stepCode', label: '姝ラ缂栫爜' },
+    { prop: 'stepName', label: '姝ラ鍚嶇О' },
+    { prop: 'stepType', label: '姝ラ绫诲瀷' },
+    { prop: 'executeResult', label: '鎵ц缁撴灉' },
+    { prop: 'errorMessage', label: '閿欒淇℃伅' },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�' },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿' },
+    { prop: 'durationSeconds', label: '鑰楁椂(绉�)' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildFlowStepInstancePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeFlowStepInstanceRow(record)
+    return {
+      flowInstanceNo: row.flowInstanceNo,
+      stepCode: row.stepCode,
+      stepName: row.stepName,
+      stepType: row.stepType,
+      executeResult: row.executeResult,
+      errorMessage: row.errorMessage,
+      startTimeText: row.startTimeText,
+      endTimeText: row.endTimeText,
+      durationSeconds: row.durationSeconds,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/flow-step-instance/flowStepInstanceTable.columns.js b/rsf-design/src/views/system/flow-step-instance/flowStepInstanceTable.columns.js
new file mode 100644
index 0000000..967c4bc
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-instance/flowStepInstanceTable.columns.js
@@ -0,0 +1,35 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFlowStepInstanceTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'flowInstanceNo', label: '娴佺▼瀹炰緥鍙�', minWidth: 180, showOverflowTooltip: true },
+    { prop: 'stepCode', label: '姝ラ缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'stepName', label: '姝ラ鍚嶇О', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'stepType', label: '姝ラ绫诲瀷', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'executeResult', label: '鎵ц缁撴灉', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�', minWidth: 170, showOverflowTooltip: true },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿', minWidth: 170, showOverflowTooltip: true },
+    { prop: 'durationSeconds', label: '鑰楁椂(绉�)', width: 110, align: 'right' },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(ElTag, { type: row?.statusType || 'info', effect: 'light' }, () => row?.statusText || '--')
+    },
+    { prop: 'memo', label: '澶囨敞', minWidth: 180, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) => h(ArtButtonTable, { icon: 'ri:eye-line', onClick: () => handleView?.(row) })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/flow-step-instance/index.vue b/rsf-design/src/views/system/flow-step-instance/index.vue
new file mode 100644
index 0000000..22e2cbb
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-instance/index.vue
@@ -0,0 +1,117 @@
+<template>
+  <div class="flow-step-instance-page art-full-height">
+    <ArtSearchBar v-model="searchForm" :items="searchItems" :showExpand="true" @search="handleSearch" @reset="handleReset" />
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+    <FlowStepInstanceDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 { fetchExportFlowStepInstanceReport, fetchFlowStepInstancePage, fetchGetFlowStepInstanceDetail, fetchGetFlowStepInstanceMany } from '@/api/flow-step-instance'
+  import { buildFlowStepInstancePageQueryParams, buildFlowStepInstancePrintRows, buildFlowStepInstanceSearchParams, createFlowStepInstanceSearchState, getFlowStepInstancePaginationKey, getFlowStepInstanceReportColumns, normalizeFlowStepInstanceRow, FLOW_STEP_INSTANCE_REPORT_TITLE } from './flowStepInstancePage.helpers'
+  import { createFlowStepInstanceTableColumns } from './flowStepInstanceTable.columns'
+  import FlowStepInstanceDetailDrawer from './modules/flow-step-instance-detail-drawer.vue'
+
+  defineOptions({ name: 'FlowStepInstance' })
+  const userStore = useUserStore()
+  const searchForm = ref(createFlowStepInstanceSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = FLOW_STEP_INSTANCE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildFlowStepInstanceSearchParams(searchForm.value))
+  const reportColumns = getFlowStepInstanceReportColumns()
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬪疄渚嬪彿/姝ラ缂栫爜/姝ラ鍚嶇О' } },
+    { label: '娴佺▼瀹炰緥鍙�', key: 'flowInstanceNo', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬪疄渚嬪彿' } },
+    { label: '姝ラ缂栫爜', key: 'stepCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ楠ょ紪鐮�' } },
+    { label: '姝ラ鍚嶇О', key: 'stepName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ楠ゅ悕绉�' } },
+    { label: '寮�濮嬫棩鏈�', key: 'timeStart', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } },
+    { label: '缁撴潫鏃ユ湡', key: 'timeEnd', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } }
+  ])
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+  const { columns, columnChecks, data, loading, pagination, getData, replaceSearchParams, resetSearchParams, handleSizeChange, handleCurrentChange, refreshData } =
+    useTable({
+      core: {
+        apiFn: fetchFlowStepInstancePage,
+        apiParams: buildFlowStepInstancePageQueryParams(searchForm.value),
+        paginationKey: getFlowStepInstancePaginationKey(),
+        columnsFactory: () => createFlowStepInstanceTableColumns({ handleView: openDetail })
+      },
+      transform: {
+        dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeFlowStepInstanceRow(item)) : [])
+      }
+    })
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetFlowStepInstanceMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(await fetchFlowStepInstancePage({ ...reportQueryParams.value, current: 1, pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20 })).records
+  }
+  const { previewVisible, previewRows, previewMeta, handlePreviewVisibleChange, handleExport, handlePrint } = usePrintExportPage({
+    downloadFileName: 'flow-step-instance.xlsx',
+    requestExport: (payload) => fetchExportFlowStepInstanceReport(payload, { headers: { Authorization: userStore.accessToken || '' } }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildFlowStepInstancePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetFlowStepInstanceDetail(id)
+    detailData.value = normalizeFlowStepInstanceRow({ ...fallback, ...detail })
+  }
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+  function handleSearch(params) {
+    replaceSearchParams(buildFlowStepInstanceSearchParams(params))
+    getData()
+  }
+  function handleReset() {
+    Object.assign(searchForm.value, createFlowStepInstanceSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-instance/modules/flow-step-instance-detail-drawer.vue b/rsf-design/src/views/system/flow-step-instance/modules/flow-step-instance-detail-drawer.vue
new file mode 100644
index 0000000..da8e5f9
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-instance/modules/flow-step-instance-detail-drawer.vue
@@ -0,0 +1,40 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娴佺▼姝ラ瀹炰緥璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="娴佺▼瀹炰緥鍙�">{{ detail.flowInstanceNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="姝ラ缂栫爜">{{ detail.stepCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="姝ラ鍚嶇О">{{ detail.stepName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="姝ラ绫诲瀷">{{ detail.stepType || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц缁撴灉" :span="2">{{ detail.executeResult || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿欒淇℃伅" :span="2">{{ detail.errorMessage || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑰楁椂(绉�)">{{ detail.durationSeconds ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'FlowStepInstanceDetailDrawer' })
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+  const emit = defineEmits(['update:visible'])
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-log/flowStepLogPage.helpers.js b/rsf-design/src/views/system/flow-step-log/flowStepLogPage.helpers.js
new file mode 100644
index 0000000..0cc9313
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-log/flowStepLogPage.helpers.js
@@ -0,0 +1,121 @@
+export const FLOW_STEP_LOG_REPORT_TITLE = '娴佺▼姝ラ鏃ュ織鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+export function createFlowStepLogSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    flowInstanceId: '',
+    stepInstanceId: '',
+    logType: '',
+    logLevel: '',
+    logContent: '',
+    requestData: '',
+    responseData: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getFlowStepLogPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildFlowStepLogSearchParams(params = {}) {
+  const result = {}
+
+  ;['condition', 'logType', 'logLevel', 'logContent', 'requestData', 'responseData', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['flowInstanceId', 'stepInstanceId', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildFlowStepLogPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFlowStepLogSearchParams(params)
+  }
+}
+
+export function normalizeFlowStepLogRow(record = {}) {
+  return {
+    ...record,
+    id: record.id ?? '--',
+    flowInstanceId: record.flowInstanceId ?? '--',
+    stepInstanceId: record.stepInstanceId ?? '--',
+    logType: normalizeText(record.logType) || '--',
+    logLevel: normalizeText(record.logLevel) || '--',
+    logContent: normalizeText(record.logContent) || '--',
+    requestData: normalizeText(record.requestData) || '--',
+    responseData: normalizeText(record.responseData) || '--',
+    createTimeText: normalizeText(record['createTime$'] || record.createTimeText || record.createTime) || '--',
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getFlowStepLogReportColumns() {
+  return [
+    { prop: 'flowInstanceId', label: '娴佺▼瀹炰緥ID' },
+    { prop: 'stepInstanceId', label: '姝ラ瀹炰緥ID' },
+    { prop: 'logType', label: '鏃ュ織绫诲瀷' },
+    { prop: 'logLevel', label: '鏃ュ織绾у埆' },
+    { prop: 'logContent', label: '鏃ュ織鍐呭' },
+    { prop: 'requestData', label: '璇锋眰鏁版嵁' },
+    { prop: 'responseData', label: '鍝嶅簲鏁版嵁' },
+    { prop: 'createTimeText', label: '鍒涘缓鏃堕棿' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildFlowStepLogPrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeFlowStepLogRow(record)
+    return {
+      flowInstanceId: row.flowInstanceId,
+      stepInstanceId: row.stepInstanceId,
+      logType: row.logType,
+      logLevel: row.logLevel,
+      logContent: row.logContent,
+      requestData: row.requestData,
+      responseData: row.responseData,
+      createTimeText: row.createTimeText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/flow-step-log/flowStepLogTable.columns.js b/rsf-design/src/views/system/flow-step-log/flowStepLogTable.columns.js
new file mode 100644
index 0000000..74b2969
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-log/flowStepLogTable.columns.js
@@ -0,0 +1,75 @@
+import { h } from 'vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFlowStepLogTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'flowInstanceId',
+      label: '娴佺▼瀹炰緥ID',
+      minWidth: 120,
+      align: 'right'
+    },
+    {
+      prop: 'stepInstanceId',
+      label: '姝ラ瀹炰緥ID',
+      minWidth: 120,
+      align: 'right'
+    },
+    {
+      prop: 'logType',
+      label: '鏃ュ織绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'logLevel',
+      label: '鏃ュ織绾у埆',
+      width: 110,
+      align: 'center'
+    },
+    {
+      prop: 'logContent',
+      label: '鏃ュ織鍐呭',
+      minWidth: 260,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'requestData',
+      label: '璇锋眰鏁版嵁',
+      minWidth: 240,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'responseData',
+      label: '鍝嶅簲鏁版嵁',
+      minWidth: 240,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/flow-step-log/index.vue b/rsf-design/src/views/system/flow-step-log/index.vue
new file mode 100644
index 0000000..c51dab1
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-log/index.vue
@@ -0,0 +1,244 @@
+<template>
+  <div class="flow-step-log-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <FlowStepLogDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportFlowStepLogReport,
+    fetchFlowStepLogPage,
+    fetchGetFlowStepLogDetail,
+    fetchGetFlowStepLogMany
+  } from '@/api/flow-step-log'
+  import {
+    buildFlowStepLogPageQueryParams,
+    buildFlowStepLogPrintRows,
+    buildFlowStepLogSearchParams,
+    createFlowStepLogSearchState,
+    getFlowStepLogPaginationKey,
+    getFlowStepLogReportColumns,
+    normalizeFlowStepLogRow,
+    FLOW_STEP_LOG_REPORT_TITLE
+  } from './flowStepLogPage.helpers'
+  import { createFlowStepLogTableColumns } from './flowStepLogTable.columns'
+  import FlowStepLogDetailDrawer from './modules/flow-step-log-detail-drawer.vue'
+
+  defineOptions({ name: 'FlowStepLog' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createFlowStepLogSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = FLOW_STEP_LOG_REPORT_TITLE
+  const reportQueryParams = computed(() => buildFlowStepLogSearchParams(searchForm.value))
+  const reportColumns = getFlowStepLogReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ祦绋嬪疄渚婭D/姝ラ瀹炰緥ID/鏃ュ織鍐呭'
+      }
+    },
+    {
+      label: '娴佺▼瀹炰緥ID',
+      key: 'flowInstanceId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ祦绋嬪疄渚婭D'
+      }
+    },
+    {
+      label: '姝ラ瀹炰緥ID',
+      key: 'stepInstanceId',
+      type: 'inputNumber',
+      props: {
+        clearable: true,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ楠ゅ疄渚婭D'
+      }
+    },
+    {
+      label: '鏃ュ織绫诲瀷',
+      key: 'logType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ棩蹇楃被鍨�'
+      }
+    },
+    {
+      label: '鏃ュ織绾у埆',
+      key: 'logLevel',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ棩蹇楃骇鍒�'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchFlowStepLogPage,
+      apiParams: buildFlowStepLogPageQueryParams(searchForm.value),
+      paginationKey: getFlowStepLogPaginationKey(),
+      columnsFactory: () => createFlowStepLogTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeFlowStepLogRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetFlowStepLogMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchFlowStepLogPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'flow-step-log.xlsx',
+    requestExport: (payload) =>
+      fetchExportFlowStepLogReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildFlowStepLogPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetFlowStepLogDetail(id)
+    detailData.value = normalizeFlowStepLogRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildFlowStepLogSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createFlowStepLogSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-log/modules/flow-step-log-detail-drawer.vue b/rsf-design/src/views/system/flow-step-log/modules/flow-step-log-detail-drawer.vue
new file mode 100644
index 0000000..5890ebb
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-log/modules/flow-step-log-detail-drawer.vue
@@ -0,0 +1,39 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="娴佺▼姝ラ鏃ュ織璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="2" border>
+          <ElDescriptionsItem label="娴佺▼瀹炰緥ID">{{ detail.flowInstanceId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="姝ラ瀹炰緥ID">{{ detail.stepInstanceId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏃ュ織绫诲瀷">{{ detail.logType || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏃ュ織绾у埆">{{ detail.logLevel || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏃ュ織鍐呭" :span="2">{{ detail.logContent || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="璇锋眰鏁版嵁" :span="2">{{ detail.requestData || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍝嶅簲鏁版嵁" :span="2">{{ detail.responseData || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'FlowStepLogDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-template/flowStepTemplatePage.helpers.js b/rsf-design/src/views/system/flow-step-template/flowStepTemplatePage.helpers.js
new file mode 100644
index 0000000..1d7855c
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-template/flowStepTemplatePage.helpers.js
@@ -0,0 +1,165 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const FLOW_STEP_TEMPLATE_REPORT_TITLE = '娴佺▼姝ラ妯℃澘鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeBoolText(value) {
+  const numericValue = Number(value)
+  if (numericValue === 1) return '鏄�'
+  if (numericValue === 0) return '鍚�'
+  return '--'
+}
+
+export function createFlowStepTemplateSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    flowId: '',
+    flowCode: '',
+    stepOrder: '',
+    stepCode: '',
+    stepName: '',
+    stepType: '',
+    actionType: '',
+    actionConfig: '',
+    inputMapping: '',
+    outputMapping: '',
+    conditionExpression: '',
+    skipOnFail: '',
+    retryEnabled: '',
+    retryConfig: '',
+    timeoutSeconds: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getFlowStepTemplatePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildFlowStepTemplateSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'flowCode',
+    'stepCode',
+    'stepName',
+    'stepType',
+    'actionType',
+    'actionConfig',
+    'inputMapping',
+    'outputMapping',
+    'conditionExpression',
+    'retryConfig',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['flowId', 'stepOrder', 'skipOnFail', 'retryEnabled', 'timeoutSeconds', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildFlowStepTemplatePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildFlowStepTemplateSearchParams(params)
+  }
+}
+
+export function normalizeFlowStepTemplateRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+  return {
+    ...record,
+    id: record.id ?? '--',
+    flowId: record.flowId ?? '--',
+    flowCode: normalizeText(record.flowCode) || '--',
+    stepOrder: record.stepOrder ?? '--',
+    stepCode: normalizeText(record.stepCode) || '--',
+    stepName: normalizeText(record.stepName) || '--',
+    stepType: normalizeText(record.stepType) || '--',
+    actionType: normalizeText(record.actionType) || '--',
+    skipOnFailText: normalizeBoolText(record.skipOnFail),
+    retryEnabledText: normalizeBoolText(record.retryEnabled),
+    timeoutSeconds: record.timeoutSeconds ?? '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getFlowStepTemplateReportColumns() {
+  return [
+    { prop: 'flowId', label: '娴佺▼ID' },
+    { prop: 'flowCode', label: '娴佺▼缂栫爜' },
+    { prop: 'stepOrder', label: '姝ラ椤哄簭' },
+    { prop: 'stepCode', label: '姝ラ缂栫爜' },
+    { prop: 'stepName', label: '姝ラ鍚嶇О' },
+    { prop: 'stepType', label: '姝ラ绫诲瀷' },
+    { prop: 'actionType', label: '鍔ㄤ綔绫诲瀷' },
+    { prop: 'skipOnFailText', label: '璺宠繃澶辫触' },
+    { prop: 'retryEnabledText', label: '鍚敤閲嶈瘯' },
+    { prop: 'timeoutSeconds', label: '瓒呮椂(绉�)' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildFlowStepTemplatePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeFlowStepTemplateRow(record)
+    return {
+      flowId: row.flowId,
+      flowCode: row.flowCode,
+      stepOrder: row.stepOrder,
+      stepCode: row.stepCode,
+      stepName: row.stepName,
+      stepType: row.stepType,
+      actionType: row.actionType,
+      skipOnFailText: row.skipOnFailText,
+      retryEnabledText: row.retryEnabledText,
+      timeoutSeconds: row.timeoutSeconds,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/flow-step-template/flowStepTemplateTable.columns.js b/rsf-design/src/views/system/flow-step-template/flowStepTemplateTable.columns.js
new file mode 100644
index 0000000..0af47e3
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-template/flowStepTemplateTable.columns.js
@@ -0,0 +1,48 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createFlowStepTemplateTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'flowId', label: '娴佺▼ID', width: 90, align: 'center' },
+    { prop: 'flowCode', label: '娴佺▼缂栫爜', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'stepOrder', label: '姝ラ椤哄簭', width: 100, align: 'center' },
+    { prop: 'stepCode', label: '姝ラ缂栫爜', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'stepName', label: '姝ラ鍚嶇О', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'stepType', label: '姝ラ绫诲瀷', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'actionType', label: '鍔ㄤ綔绫诲瀷', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'skipOnFailText', label: '璺宠繃澶辫触', width: 100, align: 'center' },
+    { prop: 'retryEnabledText', label: '鍚敤閲嶈瘯', width: 100, align: 'center' },
+    { prop: 'timeoutSeconds', label: '瓒呮椂(绉�)', width: 100, align: 'right' },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    { prop: 'memo', label: '澶囨敞', minWidth: 180, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/flow-step-template/index.vue b/rsf-design/src/views/system/flow-step-template/index.vue
new file mode 100644
index 0000000..a2e6ede
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-template/index.vue
@@ -0,0 +1,185 @@
+<template>
+  <div class="flow-step-template-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <FlowStepTemplateDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import {
+    fetchExportFlowStepTemplateReport,
+    fetchFlowStepTemplatePage,
+    fetchGetFlowStepTemplateDetail,
+    fetchGetFlowStepTemplateMany
+  } from '@/api/flow-step-template'
+  import {
+    buildFlowStepTemplatePageQueryParams,
+    buildFlowStepTemplatePrintRows,
+    buildFlowStepTemplateSearchParams,
+    createFlowStepTemplateSearchState,
+    FLOW_STEP_TEMPLATE_REPORT_TITLE,
+    getFlowStepTemplatePaginationKey,
+    getFlowStepTemplateReportColumns,
+    normalizeFlowStepTemplateRow
+  } from './flowStepTemplatePage.helpers'
+  import { createFlowStepTemplateTableColumns } from './flowStepTemplateTable.columns'
+  import FlowStepTemplateDetailDrawer from './modules/flow-step-template-detail-drawer.vue'
+
+  defineOptions({ name: 'FlowStepTemplate' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createFlowStepTemplateSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = FLOW_STEP_TEMPLATE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildFlowStepTemplateSearchParams(searchForm.value))
+  const reportColumns = getFlowStepTemplateReportColumns()
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬬紪鐮�/姝ラ缂栫爜' } },
+    { label: '娴佺▼ID', key: 'flowId', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋婭D' } },
+    { label: '娴佺▼缂栫爜', key: 'flowCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬬紪鐮�' } },
+    { label: '姝ラ缂栫爜', key: 'stepCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ楠ょ紪鐮�' } },
+    { label: '姝ラ鍚嶇О', key: 'stepName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ楠ゅ悕绉�' } },
+    { label: '姝ラ绫诲瀷', key: 'stepType', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ楠ょ被鍨�' } },
+    { label: '寮�濮嬫棩鏈�', key: 'timeStart', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } },
+    { label: '缁撴潫鏃ユ湡', key: 'timeEnd', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchFlowStepTemplatePage,
+      apiParams: buildFlowStepTemplatePageQueryParams(searchForm.value),
+      paginationKey: getFlowStepTemplatePaginationKey(),
+      columnsFactory: () => createFlowStepTemplateTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeFlowStepTemplateRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetFlowStepTemplateMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchFlowStepTemplatePage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'flow-step-template.xlsx',
+    requestExport: (payload) =>
+      fetchExportFlowStepTemplateReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildFlowStepTemplatePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetFlowStepTemplateDetail(id)
+    detailData.value = normalizeFlowStepTemplateRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildFlowStepTemplateSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createFlowStepTemplateSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/flow-step-template/modules/flow-step-template-detail-drawer.vue b/rsf-design/src/views/system/flow-step-template/modules/flow-step-template-detail-drawer.vue
new file mode 100644
index 0000000..b0357da
--- /dev/null
+++ b/rsf-design/src/views/system/flow-step-template/modules/flow-step-template-detail-drawer.vue
@@ -0,0 +1,33 @@
+<template>
+  <ElDrawer :model-value="visible" title="娴佺▼姝ラ妯℃澘璇︽儏" size="620px" @close="emit('update:visible', false)">
+    <ElDescriptions :column="2" border>
+      <ElDescriptionsItem label="娴佺▼ID">{{ detail.flowId || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="娴佺▼缂栫爜">{{ detail.flowCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="姝ラ椤哄簭">{{ detail.stepOrder || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="姝ラ缂栫爜">{{ detail.stepCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="姝ラ鍚嶇О">{{ detail.stepName || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="姝ラ绫诲瀷">{{ detail.stepType || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍔ㄤ綔绫诲瀷">{{ detail.actionType || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="璺宠繃澶辫触">{{ detail.skipOnFailText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鍚敤閲嶈瘯">{{ detail.retryEnabledText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="瓒呮椂(绉�)">{{ detail.timeoutSeconds || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+    </ElDescriptions>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+</script>
diff --git a/rsf-design/src/views/system/host/hostPage.helpers.js b/rsf-design/src/views/system/host/hostPage.helpers.js
new file mode 100644
index 0000000..e5a62c3
--- /dev/null
+++ b/rsf-design/src/views/system/host/hostPage.helpers.js
@@ -0,0 +1,89 @@
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1 },
+  { label: '绂佺敤', value: 0 }
+]
+
+export function createHostSearchState() {
+  return {
+    condition: '',
+    name: '',
+    status: ''
+  }
+}
+
+export function createHostFormState() {
+  return {
+    id: null,
+    name: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getHostPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getHostStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function getHostStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '绂佺敤', type: 'danger', bool: false }
+}
+
+export function buildHostSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    name: String(params.name || '').trim(),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildHostPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildHostSearchParams(params)
+  }
+}
+
+export function buildHostDialogModel(record = {}) {
+  return {
+    ...createHostFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    name: record.name || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildHostSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    name: String(formData.name || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeHostListRow(record = {}) {
+  const statusMeta = getHostStatusMeta(record.status)
+  return {
+    ...record,
+    name: record.name || '',
+    memo: record.memo || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/host/hostTable.columns.js b/rsf-design/src/views/system/host/hostTable.columns.js
new file mode 100644
index 0000000..72b1f8d
--- /dev/null
+++ b/rsf-design/src/views/system/host/hostTable.columns.js
@@ -0,0 +1,68 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createHostTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'name',
+      label: '鏈烘瀯鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.createTimeText || '-'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/host/index.vue b/rsf-design/src/views/system/host/index.vue
new file mode 100644
index 0000000..87b8f7a
--- /dev/null
+++ b/rsf-design/src/views/system/host/index.vue
@@ -0,0 +1,210 @@
+<template>
+  <div class="host-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板鏈烘瀯</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <HostDialog v-model:visible="dialogVisible" :host-data="currentHostData" @submit="handleDialogSubmit" />
+
+      <HostDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteHost,
+    fetchGetHostDetail,
+    fetchHostPage,
+    fetchSaveHost,
+    fetchUpdateHost
+  } from '@/api/system-manage'
+  import HostDetailDrawer from './modules/host-detail-drawer.vue'
+  import HostDialog from './modules/host-dialog.vue'
+  import { createHostTableColumns } from './hostTable.columns'
+  import {
+    buildHostDialogModel,
+    buildHostPageQueryParams,
+    buildHostSavePayload,
+    buildHostSearchParams,
+    createHostSearchState,
+    getHostPaginationKey,
+    getHostStatusOptions,
+    normalizeHostListRow
+  } from './hostPage.helpers'
+
+  defineOptions({ name: 'Host' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createHostSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ満鏋勫悕绉�'
+      }
+    },
+    {
+      label: '鏈烘瀯鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ満鏋勫悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getHostStatusOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeHostListRow(await fetchGetHostDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鏈烘瀯璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentHostData.value = buildHostDialogModel(await fetchGetHostDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇鏈烘瀯璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchHostPage,
+      apiParams: buildHostPageQueryParams(searchForm.value),
+      paginationKey: getHostPaginationKey(),
+      columnsFactory: () =>
+        createHostTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeHostListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentHostData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildHostDialogModel(),
+    buildEditModel: (record) => buildHostDialogModel(record),
+    buildSavePayload: (formData) => buildHostSavePayload(formData),
+    saveRequest: fetchSaveHost,
+    updateRequest: fetchUpdateHost,
+    deleteRequest: fetchDeleteHost,
+    entityName: '鏈烘瀯',
+    resolveRecordLabel: (record) => record?.name || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildHostSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createHostSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/host/modules/host-detail-drawer.vue b/rsf-design/src/views/system/host/modules/host-detail-drawer.vue
new file mode 100644
index 0000000..630db90
--- /dev/null
+++ b/rsf-design/src/views/system/host/modules/host-detail-drawer.vue
@@ -0,0 +1,37 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鏈烘瀯璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="8">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="鏈烘瀯鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeHostListRow } from '../hostPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeHostListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/host/modules/host-dialog.vue b/rsf-design/src/views/system/host/modules/host-dialog.vue
new file mode 100644
index 0000000..60e423d
--- /dev/null
+++ b/rsf-design/src/views/system/host/modules/host-dialog.vue
@@ -0,0 +1,130 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="720px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import { buildHostDialogModel, createHostFormState, getHostStatusOptions } from '../hostPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    hostData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createHostFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫鏈烘瀯' : '鏂板鏈烘瀯'))
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ユ満鏋勫悕绉�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '鏈烘瀯鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ満鏋勫悕绉�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getHostStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createHostFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildHostDialogModel(props.hostData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.hostData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/menu/index.vue b/rsf-design/src/views/system/menu/index.vue
index 2bb409c..e53a4dd 100644
--- a/rsf-design/src/views/system/menu/index.vue
+++ b/rsf-design/src/views/system/menu/index.vue
@@ -51,8 +51,7 @@
   import MenuDialog from './modules/menu-dialog.vue'
 
   import { formatMenuTitle } from '@/utils/router'
-  import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
-  import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { useTableColumns } from '@/hooks/core/useTableColumns'
   import {
     fetchDeleteMenu,
@@ -60,7 +59,16 @@
     fetchSaveMenu,
     fetchUpdateMenu
   } from '@/api/system-manage'
-  import { ElTag, ElMessage, ElMessageBox } from 'element-plus'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { createMenuTableColumns } from './menuTable.columns'
+  import {
+    buildMenuSubmitPayload,
+    buildMenuTreeOptions,
+    createMenuSearchState,
+    expandMenuAuthChildren,
+    filterMenuTree,
+    getMenuDisplayTitle
+  } from './menuPage.helpers'
 
   defineOptions({ name: 'Menus' })
 
@@ -74,10 +82,7 @@
   const tableData = ref([])
   const menuTreeOptions = ref([])
 
-  const initialSearchState = {
-    name: '',
-    route: ''
-  }
+  const initialSearchState = createMenuSearchState()
 
   const formFilters = reactive({ ...initialSearchState })
   const appliedFilters = reactive({ ...initialSearchState })
@@ -97,39 +102,19 @@
     }
   ])
 
-  const normalizeNumber = (value, fallback = 0) => {
-    if (value === '' || value === null || value === undefined) {
-      return fallback
-    }
-    const normalized = Number(value)
-    return Number.isNaN(normalized) ? fallback : normalized
-  }
-
-  const normalizeMenuTreeOptions = (nodes = []) => {
-    if (!Array.isArray(nodes)) {
-      return []
-    }
-
-    return nodes
-      .map((node) => ({
-        label: formatMenuTitle(node.meta?.title || node.name || ''),
-        value: normalizeNumber(node.id, 0),
-        children: normalizeMenuTreeOptions(node.children)
-      }))
-  }
-
   const loadMenuResources = async () => {
     loading.value = true
     try {
-      const list = await fetchGetMenuList({})
+      const list = await guardRequestWithMessage(fetchGetMenuList({}), null, {
+        timeoutMessage: '鑿滃崟鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      if (list === null) {
+        tableData.value = []
+        menuTreeOptions.value = []
+        return
+      }
       tableData.value = Array.isArray(list) ? list : []
-      menuTreeOptions.value = [
-        {
-          label: '椤剁骇鑿滃崟',
-          value: 0,
-          children: normalizeMenuTreeOptions(tableData.value)
-        }
-      ]
+      menuTreeOptions.value = buildMenuTreeOptions(tableData.value, formatMenuTitle)
     } catch (error) {
       ElMessage.error(error?.message || '鑾峰彇鑿滃崟澶辫触')
     } finally {
@@ -141,237 +126,35 @@
     loadMenuResources()
   })
 
-  const hasNestedMenus = (row) => Array.isArray(row.children) && row.children.some((child) => !child.meta?.isAuthButton)
-
-  const getMenuTypeTag = (row) => {
-    if (row.meta?.isAuthButton || Number(row.type) === 1) return 'danger'
-    if (hasNestedMenus(row)) return 'info'
-    return 'primary'
-  }
-
-  const getMenuTypeText = (row) => {
-    if (row.meta?.isAuthButton || Number(row.type) === 1) return '鎸夐挳'
-    if (hasNestedMenus(row)) return '鐩綍'
-    return '鑿滃崟'
-  }
-
-  const getStatusMeta = (status) => {
-    return normalizeNumber(status, 1) === 1
-      ? { text: '鍚敤', type: 'success' }
-      : { text: '绂佺敤', type: 'danger' }
-  }
-
-  const getMenuDisplayTitle = (row) => {
-    const titleKey = row.meta?.title || row.name || ''
-    const normalizedTitleKey =
-      titleKey && !String(titleKey).includes('.') ? `menu.${titleKey}` : titleKey
-
-    return formatMenuTitle(normalizedTitleKey)
-  }
-
-  const getMenuDisplayIcon = (row) => row.meta?.icon || row.icon || ''
-
-  const { columnChecks, columns } = useTableColumns(() => [
-    {
-      prop: 'meta.title',
-      label: '鑿滃崟鍚嶇О',
-      minWidth: 180,
-      formatter: (row) => getMenuDisplayTitle(row)
-    },
-    {
-      prop: 'meta.icon',
-      label: '鍥炬爣棰勮',
-      width: 96,
-      align: 'center',
-      formatter: (row) => {
-        const icon = getMenuDisplayIcon(row)
-
-        if (!icon) return h('span', { class: 'text-g-400' }, '-')
-
-        return h(
-          'div',
-          {
-            class:
-              'mx-auto flex h-8 w-8 items-center justify-center rounded-md border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)]'
-          },
-          [h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })]
-        )
-      }
-    },
-    {
-      prop: 'type',
-      label: '鑿滃崟绫诲瀷',
-      width: 110,
-      formatter: (row) =>
-        h(ElTag, { type: getMenuTypeTag(row), effect: 'light' }, () => getMenuTypeText(row))
-    },
-    {
-      prop: 'route',
-      label: '璺敱',
-      minWidth: 180,
-      formatter: (row) => {
-        if (row.meta?.isAuthButton) return ''
-        return row.route || ''
-      }
-    },
-    {
-      prop: 'authority',
-      label: '鏉冮檺鏍囪瘑',
-      minWidth: 180,
-      formatter: (row) => {
-        if (row.meta?.isAuthButton) {
-          return row.authority || row.meta?.authMark || ''
-        }
-        if (!row.meta?.authList?.length) return row.authority || ''
-        return `${row.meta.authList.length} 涓潈闄愭爣璇哷
-      }
-    },
-    {
-      prop: 'sort',
-      label: '鎺掑簭',
-      width: 90
-    },
-    {
-      prop: 'status',
-      label: '鐘舵��',
-      width: 100,
-      formatter: (row) => {
-        const statusMeta = getStatusMeta(row.status)
-        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
-      }
-    },
-    {
-      prop: 'memo',
-      label: '澶囨敞',
-      minWidth: 180,
-      showOverflowTooltip: true,
-      formatter: (row) => row.memo || '-'
-    },
-    {
-      prop: 'operation',
-      label: '鎿嶄綔',
-      width: 180,
-      align: 'right',
-      formatter: (row) => {
-        const buttonStyle = { class: 'flex justify-end' }
-        if (row.meta?.isAuthButton) {
-          return h('div', buttonStyle, [
-            h(ArtButtonTable, {
-              type: 'edit',
-              onClick: () => handleEditAuth(row)
-            }),
-            h(ArtButtonTable, {
-              type: 'delete',
-              onClick: () => handleDeleteAuth(row)
-            })
-          ])
-        }
-        return h('div', buttonStyle, [
-          h(ArtButtonTable, {
-            type: 'add',
-            onClick: () => handleAddAuth(row),
-            title: '鏂板鏉冮檺'
-          }),
-          h(ArtButtonTable, {
-            type: 'edit',
-            onClick: () => handleEditMenu(row)
-          }),
-          h(ArtButtonTable, {
-            type: 'delete',
-            onClick: () => handleDeleteMenu(row)
-          })
-        ])
-      }
-    },
-  ])
-
-  const deepClone = (obj) => {
-    if (obj === null || typeof obj !== 'object') return obj
-    if (obj instanceof Date) return new Date(obj)
-    if (Array.isArray(obj)) return obj.map((item) => deepClone(item))
-    const cloned = {}
-    for (const key in obj) {
-      if (Object.prototype.hasOwnProperty.call(obj, key)) {
-        cloned[key] = deepClone(obj[key])
-      }
-    }
-    return cloned
-  }
-
-  const convertAuthListToChildren = (items) => {
-    return items.map((item) => {
-      const clonedItem = deepClone(item)
-      if (clonedItem.children?.length) {
-        clonedItem.children = convertAuthListToChildren(clonedItem.children)
-      }
-      if (item.meta?.authList?.length) {
-        const authChildren = item.meta.authList.map((auth) => ({
-          ...deepClone(auth),
-          route: auth.route || '',
-          component: auth.component || '',
-          meta: {
-            title: auth.title,
-            authMark: auth.authMark,
-            isAuthButton: true,
-            parentPath: item.path,
-            icon: auth.icon,
-            sort: auth.sort,
-            isEnable: normalizeNumber(auth.status, 1) === 1
-          }
-        }))
-        clonedItem.children = clonedItem.children?.length
-          ? [...clonedItem.children, ...authChildren]
-          : authChildren
-      }
-      return clonedItem
+  const { columnChecks, columns } = useTableColumns(() =>
+    createMenuTableColumns({
+      titleFormatter: formatMenuTitle,
+      handleAddAuth,
+      handleEditAuth,
+      handleDeleteAuth,
+      handleEditMenu,
+      handleDeleteMenu
     })
-  }
-
-  const searchMenu = (items) => {
-    const results = []
-    for (const item of items) {
-      const searchName = appliedFilters.name?.toLowerCase().trim() || ''
-      const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
-      const menuTitle = getMenuDisplayTitle(item).toLowerCase()
-      const menuRoute = String(item.route || item.path || item.authority || '').toLowerCase()
-      const nameMatch = !searchName || menuTitle.includes(searchName)
-      const routeMatch = !searchRoute || menuRoute.includes(searchRoute)
-
-      if (item.children?.length) {
-        const matchedChildren = searchMenu(item.children)
-        if (matchedChildren.length > 0) {
-          const clonedItem = deepClone(item)
-          clonedItem.children = matchedChildren
-          results.push(clonedItem)
-          continue
-        }
-      }
-
-      if (nameMatch && routeMatch) {
-        results.push(deepClone(item))
-      }
-    }
-    return results
-  }
+  )
 
   const filteredTableData = computed(() => {
-    const searchedData = searchMenu(tableData.value)
-    return convertAuthListToChildren(searchedData)
+    const searchedData = filterMenuTree(tableData.value, appliedFilters, formatMenuTitle)
+    return expandMenuAuthChildren(searchedData)
   })
 
-  const closeDialog = () => {
+  function closeDialog() {
     dialogVisible.value = false
     editData.value = null
   }
 
-  const handleAddMenu = () => {
+  function handleAddMenu() {
     dialogType.value = 'menu'
     editData.value = null
     lockMenuType.value = true
     dialogVisible.value = true
   }
 
-  const handleAddAuth = (row) => {
+  function handleAddAuth(row) {
     dialogType.value = 'button'
     editData.value = {
       parentId: row.id,
@@ -383,37 +166,21 @@
     dialogVisible.value = true
   }
 
-  const handleEditMenu = (row) => {
+  function handleEditMenu(row) {
     dialogType.value = 'menu'
     editData.value = row
     lockMenuType.value = true
     dialogVisible.value = true
   }
 
-  const handleEditAuth = (row) => {
+  function handleEditAuth(row) {
     dialogType.value = 'button'
     editData.value = row
     lockMenuType.value = true
     dialogVisible.value = true
   }
 
-  const buildMenuSubmitPayload = (formData) => {
-    return {
-      ...(formData.id ? { id: normalizeNumber(formData.id, 0) } : {}),
-      parentId: normalizeNumber(formData.parentId, 0),
-      name: String(formData.name || '').trim(),
-      route: String(formData.route || '').trim(),
-      component: String(formData.component || '').trim(),
-      authority: String(formData.authority || '').trim(),
-      icon: String(formData.icon || '').trim(),
-      sort: normalizeNumber(formData.sort, 0),
-      status: normalizeNumber(formData.status, 1),
-      memo: String(formData.memo || '').trim(),
-      type: formData.menuType === 'button' ? 1 : 0
-    }
-  }
-
-  const handleSubmit = async (formData) => {
+  async function handleSubmit(formData) {
     const payload = buildMenuSubmitPayload(formData)
     if (payload.id && payload.id === payload.parentId) {
       ElMessage.error('涓婄骇鑿滃崟涓嶈兘閫夋嫨褰撳墠鑿滃崟')
@@ -435,10 +202,10 @@
     }
   }
 
-  const handleDeleteMenu = async (row) => {
+  async function handleDeleteMenu(row) {
     try {
       await ElMessageBox.confirm(
-        `纭畾瑕佸垹闄よ彍鍗曘��${formatMenuTitle(row.meta?.title || row.name || '')}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`,
+        `纭畾瑕佸垹闄よ彍鍗曘��${getMenuDisplayTitle(row, formatMenuTitle)}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`,
         '鍒犻櫎纭',
         {
           confirmButtonText: '纭畾',
@@ -456,7 +223,7 @@
     }
   }
 
-  const handleDeleteAuth = async (row) => {
+  async function handleDeleteAuth(row) {
     try {
       await ElMessageBox.confirm(`纭畾瑕佸垹闄ゆ潈闄愩��${row.name || row.authority || row.id}銆嶅悧锛熷垹闄ゅ悗鏃犳硶鎭㈠`, '鍒犻櫎纭', {
         confirmButtonText: '纭畾',
@@ -473,21 +240,21 @@
     }
   }
 
-  const handleReset = () => {
+  function handleReset() {
     Object.assign(formFilters, { ...initialSearchState })
     Object.assign(appliedFilters, { ...initialSearchState })
     loadMenuResources()
   }
 
-  const handleSearch = () => {
+  function handleSearch() {
     Object.assign(appliedFilters, { ...formFilters })
   }
 
-  const handleRefresh = () => {
+  function handleRefresh() {
     loadMenuResources()
   }
 
-  const toggleExpand = () => {
+  function toggleExpand() {
     isExpanded.value = !isExpanded.value
     nextTick(() => {
       if (tableRef.value?.elTableRef && filteredTableData.value) {
diff --git a/rsf-design/src/views/system/menu/menuPage.helpers.js b/rsf-design/src/views/system/menu/menuPage.helpers.js
new file mode 100644
index 0000000..20e605c
--- /dev/null
+++ b/rsf-design/src/views/system/menu/menuPage.helpers.js
@@ -0,0 +1,164 @@
+export function createMenuSearchState() {
+  return {
+    name: '',
+    route: ''
+  }
+}
+
+export function normalizeMenuNumber(value, fallback = 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const normalized = Number(value)
+  return Number.isNaN(normalized) ? fallback : normalized
+}
+
+export function normalizeMenuTitleKey(row = {}) {
+  const titleKey = row.meta?.title || row.name || ''
+  return titleKey && !String(titleKey).includes('.') ? `menu.${titleKey}` : titleKey
+}
+
+export function defaultMenuTitleFormatter(title = '') {
+  if (!title) {
+    return ''
+  }
+  return String(title).split('.').pop() || String(title)
+}
+
+export function getMenuDisplayTitle(row = {}, titleFormatter = defaultMenuTitleFormatter) {
+  return titleFormatter(normalizeMenuTitleKey(row))
+}
+
+export function getMenuDisplayIcon(row = {}) {
+  return row.meta?.icon || row.icon || ''
+}
+
+export function hasNestedMenus(row = {}) {
+  return Array.isArray(row.children) && row.children.some((child) => !child.meta?.isAuthButton)
+}
+
+export function getMenuTypeTag(row = {}) {
+  if (row.meta?.isAuthButton || Number(row.type) === 1) return 'danger'
+  if (hasNestedMenus(row)) return 'info'
+  return 'primary'
+}
+
+export function getMenuTypeText(row = {}) {
+  if (row.meta?.isAuthButton || Number(row.type) === 1) return '鎸夐挳'
+  if (hasNestedMenus(row)) return '鐩綍'
+  return '鑿滃崟'
+}
+
+export function getMenuStatusMeta(status) {
+  return normalizeMenuNumber(status, 1) === 1
+    ? { text: '鍚敤', type: 'success' }
+    : { text: '绂佺敤', type: 'danger' }
+}
+
+export function normalizeMenuTreeOptions(nodes = [], titleFormatter = defaultMenuTitleFormatter) {
+  if (!Array.isArray(nodes)) {
+    return []
+  }
+
+  return nodes.map((node) => ({
+    label: getMenuDisplayTitle(node, titleFormatter),
+    value: normalizeMenuNumber(node.id, 0),
+    children: normalizeMenuTreeOptions(node.children, titleFormatter)
+  }))
+}
+
+export function buildMenuTreeOptions(tree = [], titleFormatter = defaultMenuTitleFormatter) {
+  return [
+    {
+      label: '椤剁骇鑿滃崟',
+      value: 0,
+      children: normalizeMenuTreeOptions(tree, titleFormatter)
+    }
+  ]
+}
+
+export function buildMenuSubmitPayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: normalizeMenuNumber(formData.id, 0) } : {}),
+    parentId: normalizeMenuNumber(formData.parentId, 0),
+    name: String(formData.name || '').trim(),
+    route: String(formData.route || '').trim(),
+    component: String(formData.component || '').trim(),
+    authority: String(formData.authority || '').trim(),
+    icon: String(formData.icon || '').trim(),
+    sort: normalizeMenuNumber(formData.sort, 0),
+    status: normalizeMenuNumber(formData.status, 1),
+    memo: String(formData.memo || '').trim(),
+    type: formData.menuType === 'button' ? 1 : 0
+  }
+}
+
+export function cloneMenuTree(source) {
+  if (source === null || typeof source !== 'object') return source
+  if (source instanceof Date) return new Date(source)
+  if (Array.isArray(source)) return source.map((item) => cloneMenuTree(item))
+  const cloned = {}
+  for (const key in source) {
+    if (Object.prototype.hasOwnProperty.call(source, key)) {
+      cloned[key] = cloneMenuTree(source[key])
+    }
+  }
+  return cloned
+}
+
+export function expandMenuAuthChildren(items = []) {
+  return items.map((item) => {
+    const clonedItem = cloneMenuTree(item)
+    if (clonedItem.children?.length) {
+      clonedItem.children = expandMenuAuthChildren(clonedItem.children)
+    }
+    if (item.meta?.authList?.length) {
+      const authChildren = item.meta.authList.map((auth) => ({
+        ...cloneMenuTree(auth),
+        route: auth.route || '',
+        component: auth.component || '',
+        meta: {
+          title: auth.title,
+          authMark: auth.authMark,
+          isAuthButton: true,
+          parentPath: item.path,
+          icon: auth.icon,
+          sort: auth.sort,
+          isEnable: normalizeMenuNumber(auth.status, 1) === 1
+        }
+      }))
+      clonedItem.children = clonedItem.children?.length
+        ? [...clonedItem.children, ...authChildren]
+        : authChildren
+    }
+    return clonedItem
+  })
+}
+
+export function filterMenuTree(items = [], filters = {}, titleFormatter = defaultMenuTitleFormatter) {
+  const results = []
+  const searchName = String(filters.name || '').toLowerCase().trim()
+  const searchRoute = String(filters.route || '').toLowerCase().trim()
+
+  for (const item of items) {
+    const menuTitle = getMenuDisplayTitle(item, titleFormatter).toLowerCase()
+    const menuRoute = String(item.route || item.path || item.authority || '').toLowerCase()
+    const nameMatch = !searchName || menuTitle.includes(searchName)
+    const routeMatch = !searchRoute || menuRoute.includes(searchRoute)
+
+    if (item.children?.length) {
+      const matchedChildren = filterMenuTree(item.children, filters, titleFormatter)
+      if (matchedChildren.length > 0) {
+        const clonedItem = cloneMenuTree(item)
+        clonedItem.children = matchedChildren
+        results.push(clonedItem)
+        continue
+      }
+    }
+
+    if (nameMatch && routeMatch) {
+      results.push(cloneMenuTree(item))
+    }
+  }
+  return results
+}
diff --git a/rsf-design/src/views/system/menu/menuTable.columns.js b/rsf-design/src/views/system/menu/menuTable.columns.js
new file mode 100644
index 0000000..a27b767
--- /dev/null
+++ b/rsf-design/src/views/system/menu/menuTable.columns.js
@@ -0,0 +1,134 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+import {
+  getMenuDisplayIcon,
+  getMenuDisplayTitle,
+  getMenuStatusMeta,
+  getMenuTypeTag,
+  getMenuTypeText
+} from './menuPage.helpers'
+
+export function createMenuTableColumns({
+  titleFormatter,
+  handleAddAuth,
+  handleEditAuth,
+  handleDeleteAuth,
+  handleEditMenu,
+  handleDeleteMenu
+}) {
+  return [
+    {
+      prop: 'meta.title',
+      label: '鑿滃崟鍚嶇О',
+      minWidth: 180,
+      formatter: (row) => getMenuDisplayTitle(row, titleFormatter)
+    },
+    {
+      prop: 'meta.icon',
+      label: '鍥炬爣棰勮',
+      width: 96,
+      align: 'center',
+      formatter: (row) => {
+        const icon = getMenuDisplayIcon(row)
+
+        if (!icon) return h('span', { class: 'text-g-400' }, '-')
+
+        return h(
+          'div',
+          {
+            class:
+              'mx-auto flex h-8 w-8 items-center justify-center rounded-md border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)]'
+          },
+          [h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })]
+        )
+      }
+    },
+    {
+      prop: 'type',
+      label: '鑿滃崟绫诲瀷',
+      width: 110,
+      formatter: (row) =>
+        h(ElTag, { type: getMenuTypeTag(row), effect: 'light' }, () => getMenuTypeText(row))
+    },
+    {
+      prop: 'route',
+      label: '璺敱',
+      minWidth: 180,
+      formatter: (row) => {
+        if (row.meta?.isAuthButton) return ''
+        return row.route || ''
+      }
+    },
+    {
+      prop: 'authority',
+      label: '鏉冮檺鏍囪瘑',
+      minWidth: 180,
+      formatter: (row) => {
+        if (row.meta?.isAuthButton) {
+          return row.authority || row.meta?.authMark || ''
+        }
+        if (!row.meta?.authList?.length) return row.authority || ''
+        return `${row.meta.authList.length} 涓潈闄愭爣璇哷
+      }
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭',
+      width: 90
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => {
+        const statusMeta = getMenuStatusMeta(row.status)
+        return h(ElTag, { type: statusMeta.type, effect: 'light' }, () => statusMeta.text)
+      }
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 180,
+      align: 'right',
+      formatter: (row) => {
+        const buttonStyle = { class: 'flex justify-end' }
+        if (row.meta?.isAuthButton) {
+          return h('div', buttonStyle, [
+            h(ArtButtonTable, {
+              type: 'edit',
+              onClick: () => handleEditAuth(row)
+            }),
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDeleteAuth(row)
+            })
+          ])
+        }
+        return h('div', buttonStyle, [
+          h(ArtButtonTable, {
+            type: 'add',
+            onClick: () => handleAddAuth(row),
+            title: '鏂板鏉冮檺'
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEditMenu(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'delete',
+            onClick: () => handleDeleteMenu(row)
+          })
+        ])
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/operation-record/index.vue b/rsf-design/src/views/system/operation-record/index.vue
new file mode 100644
index 0000000..b8005c6
--- /dev/null
+++ b/rsf-design/src/views/system/operation-record/index.vue
@@ -0,0 +1,281 @@
+<template>
+  <div class="operation-record-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <span v-auth="'list'" class="inline-flex">
+              <ListExportPrint
+                :preview-visible="previewVisible"
+                @update:previewVisible="handlePreviewVisibleChange"
+                :report-title="reportTitle"
+                :selected-rows="selectedRows"
+                :query-params="reportQueryParams"
+                :columns="reportColumns"
+                :preview-rows="previewRows"
+                :preview-meta="previewMeta"
+                :total="pagination.total"
+                :disabled="loading"
+                @export="handleExport"
+                @print="handlePrint"
+              />
+            </span>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <OperationRecordDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  import { useAuth } from '@/hooks/core/useAuth'
+  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 {
+    fetchDeleteOperationRecord,
+    fetchExportOperationRecordReport,
+    fetchGetOperationRecordDetail,
+    fetchGetOperationRecordMany,
+    fetchOperationRecordPage
+  } from '@/api/system-manage'
+  import {
+    buildOperationRecordPageQueryParams,
+    buildOperationRecordPrintRows,
+    buildOperationRecordSearchParams,
+    createOperationRecordSearchState,
+    getOperationRecordPaginationKey,
+    getOperationRecordReportColumns,
+    mergeOperationRecordDetail,
+    normalizeOperationRecordRow,
+    OPERATION_RECORD_REPORT_TITLE
+  } from './operationRecordPage.helpers'
+  import { createOperationRecordTableColumns } from './operationRecordTable.columns'
+  import OperationRecordDetailDrawer from './modules/operation-record-detail-drawer.vue'
+
+  defineOptions({ name: 'OperationRecord' })
+
+  const userStore = useUserStore()
+  const { hasAuth } = useAuth()
+
+  const searchForm = ref(createOperationRecordSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = OPERATION_RECORD_REPORT_TITLE
+  const reportQueryParams = computed(() => buildOperationRecordSearchParams(searchForm.value))
+  const reportColumns = getOperationRecordReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悕绉扮┖闂�'
+      }
+    },
+    {
+      label: '鎺ュ彛鍦板潃',
+      key: 'url',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ帴鍙e湴鍧�'
+      }
+    },
+    {
+      label: '瀹㈡埛绔疘P',
+      key: 'clientIp',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ鎴风IP'
+      }
+    },
+    {
+      label: '缁撴灉',
+      key: 'result',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '鎴愬姛', value: 1 },
+          { label: '澶辫触', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        type: 'date',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadOperationDetail(row.id, row)
+  }
+
+  async function handleDelete(row) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄ゆ搷浣滄棩蹇椼��${row.namespace || row.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteOperationRecord(row.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchOperationRecordPage,
+      apiParams: buildOperationRecordPageQueryParams(searchForm.value),
+      paginationKey: getOperationRecordPaginationKey(),
+      columnsFactory: () =>
+        createOperationRecordTableColumns({
+          handleView: openDetail,
+          handleDelete: hasAuth('delete') ? handleDelete : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeOperationRecordRow(item))
+      }
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetOperationRecordMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchOperationRecordPage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'operation-record.xlsx',
+    requestExport: (payload) =>
+      fetchExportOperationRecordReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildOperationRecordPrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadOperationDetail(id, fallback) {
+    detailLoading.value = true
+    try {
+      const detail = await fetchGetOperationRecordDetail(id)
+      detailData.value = mergeOperationRecordDetail(detail, fallback)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鎿嶄綔鏃ュ織璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildOperationRecordSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createOperationRecordSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/operation-record/modules/operation-record-detail-drawer.vue b/rsf-design/src/views/system/operation-record/modules/operation-record-detail-drawer.vue
new file mode 100644
index 0000000..98ab2c1
--- /dev/null
+++ b/rsf-design/src/views/system/operation-record/modules/operation-record-detail-drawer.vue
@@ -0,0 +1,53 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="鎿嶄綔鏃ュ織璇︽儏"
+    size="640px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="12">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="鍚嶇О绌洪棿">{{ displayData.namespace || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎺ュ彛鍦板潃">{{ displayData.url || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="骞冲彴瀵嗛挜">{{ displayData.appkey || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎿嶄綔鐢ㄦ埛">{{ displayData.userLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀹㈡埛绔疘P">{{ displayData.clientIp || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="缁撴灉">
+          <ElTag :type="displayData.resultType" effect="light">{{ displayData.resultText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鑰楁椂(ms)">{{ displayData.spendTime ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎿嶄綔鏃堕棿">{{ displayData.timestampText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="寮傚父淇℃伅">{{ displayData.err || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璇锋眰鍐呭">
+          <pre class="whitespace-pre-wrap break-all rounded-lg bg-[var(--art-main-bg-color)] p-3 text-xs leading-6 text-g-700">{{
+            displayData.request || '--'
+          }}</pre>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鍝嶅簲鍐呭">
+          <pre class="whitespace-pre-wrap break-all rounded-lg bg-[var(--art-main-bg-color)] p-3 text-xs leading-6 text-g-700">{{
+            displayData.response || '--'
+          }}</pre>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { mergeOperationRecordDetail } from '../operationRecordPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const displayData = computed(() => mergeOperationRecordDetail(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/operation-record/operationRecordPage.helpers.js b/rsf-design/src/views/system/operation-record/operationRecordPage.helpers.js
new file mode 100644
index 0000000..fb55e27
--- /dev/null
+++ b/rsf-design/src/views/system/operation-record/operationRecordPage.helpers.js
@@ -0,0 +1,123 @@
+const OPERATION_RESULT_META = {
+  1: { text: '鎴愬姛', type: 'success' },
+  0: { text: '澶辫触', type: 'danger' }
+}
+
+export const OPERATION_RECORD_REPORT_TITLE = '鎿嶄綔鏃ュ織鎶ヨ〃'
+
+export function createOperationRecordSearchState() {
+  return {
+    condition: '',
+    namespace: '',
+    url: '',
+    clientIp: '',
+    result: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function getOperationRecordPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildOperationRecordSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    namespace: String(params.namespace || '').trim(),
+    url: String(params.url || '').trim(),
+    clientIp: String(params.clientIp || '').trim(),
+    ...(params.result !== '' && params.result !== null && params.result !== undefined
+      ? { result: Number(params.result) }
+      : {}),
+    ...(params.timeStart ? { timeStart: params.timeStart } : {}),
+    ...(params.timeEnd ? { timeEnd: params.timeEnd } : {})
+  }
+}
+
+export function buildOperationRecordPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOperationRecordSearchParams(params)
+  }
+}
+
+export function getOperationResultMeta(value) {
+  return OPERATION_RESULT_META[Number(value)] || { text: '-', type: 'info' }
+}
+
+export function formatOperationTimestamp(value) {
+  if (value === '' || value === null || value === undefined) {
+    return ''
+  }
+
+  const timestamp = Number(value)
+  const date = Number.isNaN(timestamp) ? new Date(value) : new Date(timestamp)
+  if (Number.isNaN(date.getTime())) {
+    return String(value)
+  }
+
+  const pad = (segment) => String(segment).padStart(2, '0')
+  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(
+    date.getMinutes()
+  )}:${pad(date.getSeconds())}`
+}
+
+export function normalizeOperationRecordRow(record = {}) {
+  const resultMeta = getOperationResultMeta(record.result)
+  return {
+    ...record,
+    namespace: record.namespace || '',
+    url: record.url || '',
+    appkey: record.appkey || '',
+    request: record.request || '',
+    response: record.response || '',
+    err: record.err || '',
+    memo: record.memo || '',
+    clientIp: record.clientIp || '',
+    spendTime: record.spendTime ?? 0,
+    userLabel: record['userId$'] || record.userLabel || (record.userId ? String(record.userId) : ''),
+    timestampText: formatOperationTimestamp(record.timestamp),
+    createTimeText: record['createTime$'] || record.createTime || '',
+    resultText: resultMeta.text,
+    resultType: resultMeta.type
+  }
+}
+
+export function mergeOperationRecordDetail(detail = {}, fallback = {}) {
+  return normalizeOperationRecordRow({
+    ...fallback,
+    ...detail
+  })
+}
+
+export function getOperationRecordReportColumns() {
+  return [
+    { prop: 'namespace', label: '鍚嶇О绌洪棿' },
+    { prop: 'url', label: '鎺ュ彛鍦板潃' },
+    { prop: 'userLabel', label: '鎿嶄綔鐢ㄦ埛' },
+    { prop: 'clientIp', label: '瀹㈡埛绔疘P' },
+    { prop: 'spendTimeText', label: '鑰楁椂(ms)' },
+    { prop: 'resultText', label: '缁撴灉' },
+    { prop: 'timestampText', label: '鎿嶄綔鏃堕棿' }
+  ]
+}
+
+export function buildOperationRecordPrintRows(records = []) {
+  return records.map((record) => {
+    const row = normalizeOperationRecordRow(record)
+    return {
+      namespace: row.namespace || '--',
+      url: row.url || '--',
+      userLabel: row.userLabel || '--',
+      clientIp: row.clientIp || '--',
+      spendTimeText: row.spendTime ?? '--',
+      resultText: row.resultText || '--',
+      timestampText: row.timestampText || '--'
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/operation-record/operationRecordTable.columns.js b/rsf-design/src/views/system/operation-record/operationRecordTable.columns.js
new file mode 100644
index 0000000..24f270f
--- /dev/null
+++ b/rsf-design/src/views/system/operation-record/operationRecordTable.columns.js
@@ -0,0 +1,76 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createOperationRecordTableColumns({ handleView, handleDelete }) {
+  return [
+    {
+      prop: 'namespace',
+      label: '鍚嶇О绌洪棿',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'url',
+      label: '鎺ュ彛鍦板潃',
+      minWidth: 240,
+      showOverflowTooltip: true,
+      formatter: (row) => row.url || '-'
+    },
+    {
+      prop: 'userLabel',
+      label: '鎿嶄綔鐢ㄦ埛',
+      minWidth: 140,
+      formatter: (row) => row.userLabel || '-'
+    },
+    {
+      prop: 'clientIp',
+      label: '瀹㈡埛绔疘P',
+      minWidth: 140,
+      formatter: (row) => row.clientIp || '-'
+    },
+    {
+      prop: 'spendTime',
+      label: '鑰楁椂(ms)',
+      width: 110,
+      formatter: (row) => row.spendTime ?? '-'
+    },
+    {
+      prop: 'result',
+      label: '缁撴灉',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.resultType, effect: 'light' }, () => row.resultText || '-')
+    },
+    {
+      prop: 'timestampText',
+      label: '鎿嶄綔鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.timestampText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 120 : 70,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/role/modules/role-permission-dialog.vue b/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
index 779b85f..c7c58ec 100644
--- a/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
+++ b/rsf-design/src/views/system/role/modules/role-permission-dialog.vue
@@ -78,6 +78,7 @@
   } from '../rolePage.helpers'
   import { fetchGetRoleScopeList, fetchGetRoleScopeTree, fetchUpdateRoleScope } from '@/api/system-manage'
   import { resolveBackendMenuTitle } from '@/utils/backend-menu-title'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import { ElMessage } from 'element-plus'
 
   const props = defineProps({
@@ -128,7 +129,21 @@
         requests.unshift(fetchGetRoleScopeList(config.scopeType, props.roleData.id))
       }
 
-      const [checkedIds, treeData] = reloadSelection ? await Promise.all(requests) : [state.checkedKeys, await requests[0]]
+      const guardedResult = await guardRequestWithMessage(
+        reloadSelection ? Promise.all(requests) : Promise.resolve([state.checkedKeys, await requests[0]]),
+        null,
+        {
+          timeoutMessage: `${config.title}鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟`
+        }
+      )
+      if (!guardedResult) {
+        state.treeData = []
+        state.checkedKeys = []
+        state.halfCheckedKeys = []
+        state.loaded = true
+        return
+      }
+      const [checkedIds, treeData] = guardedResult
       state.treeData = normalizeRoleScopeTreeData(config.scopeType, treeData)
       state.checkedKeys = normalizeScopeKeys(checkedIds)
       state.halfCheckedKeys = []
diff --git a/rsf-design/src/views/system/serial-rule-item/index.vue b/rsf-design/src/views/system/serial-rule-item/index.vue
new file mode 100644
index 0000000..08b7cc0
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule-item/index.vue
@@ -0,0 +1,430 @@
+<template>
+  <div class="serial-rule-item-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板缂栫爜瑙勫垯鏄庣粏</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </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>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <SerialRuleItemDialog
+        v-model:visible="dialogVisible"
+        :serial-rule-item-data="currentSerialRuleItemData"
+        :wk-type-options="wkTypeOptions"
+        @submit="handleDialogSubmit"
+      />
+
+      <SerialRuleItemDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage } from 'element-plus'
+  import { useUserStore } from '@/store/modules/user'
+  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 ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import { fetchDictDataPage } from '@/api/system-manage'
+  import {
+    fetchDeleteSerialRuleItem,
+    fetchExportSerialRuleItemReport,
+    fetchGetSerialRuleItemDetail,
+    fetchGetSerialRuleItemMany,
+    fetchSaveSerialRuleItem,
+    fetchSerialRuleItemPage,
+    fetchUpdateSerialRuleItem
+  } from '@/api/serial-rule-item'
+  import SerialRuleItemDialog from './modules/serial-rule-item-dialog.vue'
+  import SerialRuleItemDetailDrawer from './modules/serial-rule-item-detail-drawer.vue'
+  import { createSerialRuleItemTableColumns } from './serialRuleItemTable.columns'
+  import {
+    buildSerialRuleItemDialogModel,
+    buildSerialRuleItemPageQueryParams,
+    buildSerialRuleItemPrintRows,
+    buildSerialRuleItemReportMeta,
+    buildSerialRuleItemSavePayload,
+    buildSerialRuleItemSearchParams,
+    createSerialRuleItemSearchState,
+    getSerialRuleItemPaginationKey,
+    getSerialRuleItemStatusOptions,
+    normalizeSerialRuleItemListRow,
+    SERIAL_RULE_ITEM_REPORT_STYLE,
+    SERIAL_RULE_ITEM_REPORT_TITLE
+  } from './serialRuleItemPage.helpers'
+
+  defineOptions({ name: 'SerialRuleItem' })
+
+  const { hasAuth } = useAuth()
+  const userStore = useUserStore()
+
+  const searchForm = ref(createSerialRuleItemSearchState())
+  const wkTypeOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const reportTitle = SERIAL_RULE_ITEM_REPORT_TITLE
+  const reportQueryParams = computed(() => buildSerialRuleItemSearchParams(searchForm.value))
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ鍒欎富琛ㄦ爣璇�/瀛楁鍊�'
+      }
+    },
+    {
+      label: '瑙勫垯涓昏〃鏍囪瘑',
+      key: 'ruleId',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ鍒欎富琛ㄦ爣璇�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '瑙勫垯绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        clearable: true,
+        filterable: true,
+        options: wkTypeOptions.value
+      }
+    },
+    {
+      label: '瀛楁鍊�',
+      key: 'feildValue',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ瓧娈靛��'
+      }
+    },
+    {
+      label: '鎴彇闀垮害',
+      key: 'len',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ埅鍙栭暱搴�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '璧峰浣嶇疆',
+      key: 'lenStr',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ捣濮嬩綅缃�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鎺掑簭椤哄簭',
+      key: 'sort',
+      type: 'number',
+      props: {
+        clearable: true,
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ帓搴忛『搴�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getSerialRuleItemStatusOptions()
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      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: '璇烽�夋嫨缁撴潫鏃堕棿'
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetSerialRuleItemDetail(row.id), {}, {
+        timeoutMessage: '缂栫爜瑙勫垯鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeSerialRuleItemListRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇缂栫爜瑙勫垯鏄庣粏璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      const detail = await guardRequestWithMessage(fetchGetSerialRuleItemDetail(row.id), {}, {
+        timeoutMessage: '缂栫爜瑙勫垯鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      currentSerialRuleItemData.value = buildSerialRuleItemDialogModel(detail)
+      dialogVisible.value = true
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇缂栫爜瑙勫垯鏄庣粏璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchSerialRuleItemPage,
+      apiParams: buildSerialRuleItemPageQueryParams(searchForm.value),
+      paginationKey: getSerialRuleItemPaginationKey(),
+      columnsFactory: () =>
+        createSerialRuleItemTableColumns({
+          handleView: openDetail,
+          handleEdit: hasAuth('update') ? openEditDialog : null,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeSerialRuleItemListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    currentRecord: currentSerialRuleItemData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildSerialRuleItemDialogModel(),
+    buildEditModel: (record) => buildSerialRuleItemDialogModel(record),
+    buildSavePayload: (formData) => buildSerialRuleItemSavePayload(formData),
+    saveRequest: fetchSaveSerialRuleItem,
+    updateRequest: fetchUpdateSerialRuleItem,
+    deleteRequest: fetchDeleteSerialRuleItem,
+    entityName: '缂栫爜瑙勫垯鏄庣粏',
+    resolveRecordLabel: (record) => record?.feildValue || record?.wkTypeText || record?.ruleId || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  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: { ...SERIAL_RULE_ITEM_REPORT_STYLE }
+    }
+  }
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetSerialRuleItemMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchSerialRuleItemPage({
+        ...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: 'serial-rule-item.xlsx',
+    requestExport: (payload) =>
+      fetchExportSerialRuleItemReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildSerialRuleItemPrintRows(records),
+    buildPreviewMeta: buildPreviewMeta
+  })
+
+  const resolvedPreviewMeta = computed(() =>
+    buildSerialRuleItemReportMeta({
+      previewMeta: previewMeta.value,
+      count: previewRows.value.length,
+      orientation: previewMeta.value?.reportStyle?.orientation || SERIAL_RULE_ITEM_REPORT_STYLE.orientation
+    })
+  )
+
+  function handleSearch(params) {
+    replaceSearchParams(buildSerialRuleItemSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createSerialRuleItemSearchState())
+    resetSearchParams()
+  }
+
+  async function loadWkTypeOptions() {
+    const response = await guardRequestWithMessage(
+      fetchDictDataPage({
+        current: 1,
+        pageSize: 200,
+        dictTypeCode: 'sys_rule_item_type',
+        status: 1
+      }),
+      { records: [] },
+      { timeoutMessage: '瑙勫垯绫诲瀷鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟' }
+    )
+    const records = defaultResponseAdapter(response).records
+    wkTypeOptions.value = Array.isArray(records)
+      ? records
+          .map((item) => {
+            if (!item || typeof item !== 'object') {
+              return null
+            }
+            const value = item.value ?? item.code ?? item.dictValue ?? item.id
+            const label = item.label ?? item.name ?? item.dictLabel ?? item.value
+            if (value === void 0 || value === null || value === '') {
+              return null
+            }
+            return {
+              label: String(label ?? value),
+              value: String(value)
+            }
+          })
+          .filter(Boolean)
+      : []
+  }
+
+  onMounted(async () => {
+    await loadWkTypeOptions()
+  })
+</script>
diff --git a/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-detail-drawer.vue b/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-detail-drawer.vue
new file mode 100644
index 0000000..2ef4cd5
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-detail-drawer.vue
@@ -0,0 +1,44 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缂栫爜瑙勫垯鏄庣粏璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="瑙勫垯涓昏〃鏍囪瘑">{{ displayData.ruleId || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瑙勫垯绫诲瀷">{{ displayData.wkTypeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀛楁鍊�">{{ displayData.feildValue || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎴彇闀垮害">{{ displayData.len ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="璧峰浣嶇疆">{{ displayData.lenStr ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎺掑簭椤哄簭">{{ displayData.sort ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeSerialRuleItemListRow } from '../serialRuleItemPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeSerialRuleItemListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-dialog.vue b/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-dialog.vue
new file mode 100644
index 0000000..612c7f3
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule-item/modules/serial-rule-item-dialog.vue
@@ -0,0 +1,194 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="120px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildSerialRuleItemDialogModel,
+    createSerialRuleItemFormState,
+    getSerialRuleItemStatusOptions
+  } from '../serialRuleItemPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    serialRuleItemData: { type: Object, default: () => ({}) },
+    wkTypeOptions: { type: Array, default: () => [] }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createSerialRuleItemFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫缂栫爜瑙勫垯鏄庣粏' : '鏂板缂栫爜瑙勫垯鏄庣粏'))
+
+  const rules = computed(() => ({
+    ruleId: [{ required: true, message: '璇疯緭鍏ヨ鍒欎富琛ㄦ爣璇�', trigger: 'blur' }],
+    wkType: [{ required: true, message: '璇烽�夋嫨瑙勫垯绫诲瀷', trigger: 'change' }],
+    lenStr: [{ required: true, message: '璇疯緭鍏ヨ捣濮嬩綅缃�', trigger: 'blur' }],
+    sort: [{ required: true, message: '璇疯緭鍏ユ帓搴忛『搴�', trigger: 'blur' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '瑙勫垯涓昏〃鏍囪瘑',
+      key: 'ruleId',
+      type: 'number',
+      props: {
+        min: 1,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ鍒欎富琛ㄦ爣璇�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '瑙勫垯绫诲瀷',
+      key: 'wkType',
+      type: 'select',
+      props: {
+        options: props.wkTypeOptions.length > 0 ? props.wkTypeOptions : [],
+        placeholder: '璇烽�夋嫨瑙勫垯绫诲瀷',
+        filterable: true,
+        clearable: true
+      }
+    },
+    {
+      label: '瀛楁鍊�',
+      key: 'feildValue',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ瓧娈靛��',
+        clearable: true
+      }
+    },
+    {
+      label: '鎴彇闀垮害',
+      key: 'len',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ埅鍙栭暱搴�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '璧峰浣嶇疆',
+      key: 'lenStr',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ヨ捣濮嬩綅缃�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鎺掑簭椤哄簭',
+      key: 'sort',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        placeholder: '璇疯緭鍏ユ帓搴忛『搴�',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getSerialRuleItemStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��',
+        clearable: false
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createSerialRuleItemFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildSerialRuleItemDialogModel(props.serialRuleItemData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.serialRuleItemData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/serial-rule-item/serialRuleItemPage.helpers.js b/rsf-design/src/views/system/serial-rule-item/serialRuleItemPage.helpers.js
new file mode 100644
index 0000000..a4d0f5a
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule-item/serialRuleItemPage.helpers.js
@@ -0,0 +1,206 @@
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1 },
+  { label: '鍐荤粨', value: 0 }
+]
+
+export const SERIAL_RULE_ITEM_REPORT_TITLE = '缂栫爜瑙勫垯鏄庣粏鎶ヨ〃'
+
+export const SERIAL_RULE_ITEM_REPORT_STYLE = {
+  titleAlign: 'center',
+  titleLevel: 'strong',
+  orientation: 'landscape',
+  density: 'compact',
+  showSequence: true,
+  showBorder: true
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value, fallback = void 0) {
+  if (value === '' || value === null || value === undefined) {
+    return fallback
+  }
+  const parsed = Number(value)
+  return Number.isNaN(parsed) ? fallback : parsed
+}
+
+export function createSerialRuleItemSearchState() {
+  return {
+    condition: '',
+    ruleId: '',
+    wkType: '',
+    feildValue: '',
+    len: '',
+    lenStr: '',
+    sort: '',
+    status: '',
+    memo: '',
+    timeStart: '',
+    timeEnd: ''
+  }
+}
+
+export function createSerialRuleItemFormState() {
+  return {
+    id: void 0,
+    ruleId: void 0,
+    wkType: '',
+    feildValue: '',
+    len: void 0,
+    lenStr: void 0,
+    sort: void 0,
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getSerialRuleItemPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getSerialRuleItemStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function getSerialRuleItemStatusMeta(status) {
+  if (status === true || Number(status) === 1) {
+    return { text: '姝e父', type: 'success', bool: true }
+  }
+  if (status === false || Number(status) === 0) {
+    return { text: '鍐荤粨', type: 'danger', bool: false }
+  }
+  return { text: '鏈煡', type: 'info', bool: false }
+}
+
+export function buildSerialRuleItemSearchParams(params = {}) {
+  const searchParams = {
+    condition: normalizeText(params.condition),
+    ruleId: normalizeNumber(params.ruleId, void 0),
+    wkType: normalizeText(params.wkType),
+    feildValue: normalizeText(params.feildValue),
+    len: normalizeNumber(params.len, void 0),
+    lenStr: normalizeNumber(params.lenStr, void 0),
+    sort: normalizeNumber(params.sort, void 0),
+    status: normalizeNumber(params.status, void 0),
+    memo: normalizeText(params.memo),
+    timeStart: normalizeText(params.timeStart),
+    timeEnd: normalizeText(params.timeEnd)
+  }
+
+  return Object.fromEntries(
+    Object.entries(searchParams).filter(([, value]) => value !== '' && value !== void 0 && value !== null)
+  )
+}
+
+export function buildSerialRuleItemPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSerialRuleItemSearchParams(params)
+  }
+}
+
+export function buildSerialRuleItemDialogModel(record = {}) {
+  return {
+    ...createSerialRuleItemFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    ruleId:
+      record.ruleId !== void 0 && record.ruleId !== null && record.ruleId !== ''
+        ? Number(record.ruleId)
+        : void 0,
+    wkType: record.wkType || '',
+    feildValue: record.feildValue || '',
+    len: record.len !== void 0 && record.len !== null && record.len !== '' ? Number(record.len) : void 0,
+    lenStr:
+      record.lenStr !== void 0 && record.lenStr !== null && record.lenStr !== ''
+        ? Number(record.lenStr)
+        : void 0,
+    sort: record.sort !== void 0 && record.sort !== null && record.sort !== '' ? Number(record.sort) : void 0,
+    status: record.status !== void 0 && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildSerialRuleItemSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    ...(formData.ruleId !== void 0 && formData.ruleId !== null && formData.ruleId !== ''
+      ? { ruleId: Number(formData.ruleId) }
+      : {}),
+    wkType: normalizeText(formData.wkType) || '',
+    feildValue: normalizeText(formData.feildValue) || '',
+    ...(formData.len !== void 0 && formData.len !== null && formData.len !== ''
+      ? { len: Number(formData.len) }
+      : {}),
+    ...(formData.lenStr !== void 0 && formData.lenStr !== null && formData.lenStr !== ''
+      ? { lenStr: Number(formData.lenStr) }
+      : {}),
+    ...(formData.sort !== void 0 && formData.sort !== null && formData.sort !== ''
+      ? { sort: Number(formData.sort) }
+      : {}),
+    status: formData.status !== void 0 && formData.status !== null ? Number(formData.status) : 1,
+    memo: normalizeText(formData.memo) || ''
+  }
+}
+
+export function normalizeSerialRuleItemListRow(record = {}) {
+  const statusMeta = getSerialRuleItemStatusMeta(record.statusBool ?? record.status)
+  return {
+    ...record,
+    ruleId: record.ruleId ?? '-',
+    wkType: record.wkType || '',
+    wkTypeText: record['wkType$'] || record.wkType || '-',
+    feildValue: record.feildValue || '-',
+    len: record.len ?? '-',
+    lenStr: record.lenStr ?? '-',
+    sort: record.sort ?? '-',
+    memo: record.memo || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || record.updateBy || '',
+    createByLabel: record['createBy$'] || record.createBy || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
+
+export function buildSerialRuleItemPrintRows(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  return records.map((record) => normalizeSerialRuleItemListRow(record))
+}
+
+export function buildSerialRuleItemReportMeta({
+  previewMeta = {},
+  count = 0,
+  titleAlign = SERIAL_RULE_ITEM_REPORT_STYLE.titleAlign,
+  titleLevel = SERIAL_RULE_ITEM_REPORT_STYLE.titleLevel,
+  orientation = SERIAL_RULE_ITEM_REPORT_STYLE.orientation,
+  density = SERIAL_RULE_ITEM_REPORT_STYLE.density,
+  showSequence = SERIAL_RULE_ITEM_REPORT_STYLE.showSequence,
+  showBorder = SERIAL_RULE_ITEM_REPORT_STYLE.showBorder
+} = {}) {
+  return {
+    reportTitle: SERIAL_RULE_ITEM_REPORT_TITLE,
+    reportDate: previewMeta.reportDate,
+    printedAt: previewMeta.printedAt,
+    operator: previewMeta.operator,
+    count,
+    reportStyle: {
+      titleAlign,
+      titleLevel,
+      orientation,
+      density,
+      showSequence,
+      showBorder
+    }
+  }
+}
diff --git a/rsf-design/src/views/system/serial-rule-item/serialRuleItemTable.columns.js b/rsf-design/src/views/system/serial-rule-item/serialRuleItemTable.columns.js
new file mode 100644
index 0000000..d51cf71
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule-item/serialRuleItemTable.columns.js
@@ -0,0 +1,103 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createSerialRuleItemTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      type: 'globalIndex',
+      label: '搴忓彿',
+      width: 72,
+      align: 'center'
+    },
+    {
+      prop: 'ruleId',
+      label: '瑙勫垯涓昏〃鏍囪瘑',
+      width: 120,
+      align: 'center'
+    },
+    {
+      prop: 'wkTypeText',
+      label: '瑙勫垯绫诲瀷',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'feildValue',
+      label: '瀛楁鍊�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'len',
+      label: '鎴彇闀垮害',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.len ?? '-'
+    },
+    {
+      prop: 'lenStr',
+      label: '璧峰浣嶇疆',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.lenStr ?? '-'
+    },
+    {
+      prop: 'sort',
+      label: '鎺掑簭椤哄簭',
+      width: 110,
+      align: 'center',
+      formatter: (row) => row.sort ?? '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/serial-rule/index.vue b/rsf-design/src/views/system/serial-rule/index.vue
new file mode 100644
index 0000000..a1d16cc
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule/index.vue
@@ -0,0 +1,223 @@
+<template>
+  <div class="serial-rule-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showDialog('add')" v-ripple>鏂板缂栫爜瑙勫垯</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <SerialRuleDialog
+        v-model:visible="dialogVisible"
+        :serial-rule-data="currentSerialRuleData"
+        @submit="handleDialogSubmit"
+      />
+
+      <SerialRuleDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useCrudPage } from '@/views/system/common/useCrudPage'
+  import {
+    fetchDeleteSerialRule,
+    fetchGetSerialRuleDetail,
+    fetchSaveSerialRule,
+    fetchSerialRulePage,
+    fetchUpdateSerialRule
+  } from '@/api/system-manage'
+  import SerialRuleDialog from './modules/serial-rule-dialog.vue'
+  import SerialRuleDetailDrawer from './modules/serial-rule-detail-drawer.vue'
+  import { createSerialRuleTableColumns } from './serialRuleTable.columns'
+  import {
+    buildSerialRuleDialogModel,
+    buildSerialRulePageQueryParams,
+    buildSerialRuleSavePayload,
+    buildSerialRuleSearchParams,
+    createSerialRuleSearchState,
+    getSerialRulePaginationKey,
+    getSerialRuleResetOptions,
+    normalizeSerialRuleListRow
+  } from './serialRulePage.helpers'
+
+  defineOptions({ name: 'SerialRule' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createSerialRuleSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙锋垨鍚嶇О'
+      }
+    },
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ紪鍙�'
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ悕绉�'
+      }
+    },
+    {
+      label: '閲嶇疆瑙勫垯',
+      key: 'reset',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getSerialRuleResetOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeSerialRuleListRow(await fetchGetSerialRuleDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇缂栫爜瑙勫垯璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentSerialRuleData.value = buildSerialRuleDialogModel(await fetchGetSerialRuleDetail(row.id))
+      dialogVisible.value = true
+      dialogType.value = 'edit'
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇缂栫爜瑙勫垯璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchSerialRulePage,
+      apiParams: buildSerialRulePageQueryParams(searchForm.value),
+      paginationKey: getSerialRulePaginationKey(),
+      columnsFactory: () =>
+        createSerialRuleTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeSerialRuleListRow(item))
+      }
+    }
+  })
+
+  const {
+    dialogVisible,
+    dialogType,
+    currentRecord: currentSerialRuleData,
+    selectedRows,
+    handleSelectionChange,
+    showDialog,
+    handleDialogSubmit,
+    handleDelete,
+    handleBatchDelete
+  } = useCrudPage({
+    createEmptyModel: () => buildSerialRuleDialogModel(),
+    buildEditModel: (record) => buildSerialRuleDialogModel(record),
+    buildSavePayload: (formData) => buildSerialRuleSavePayload(formData),
+    saveRequest: fetchSaveSerialRule,
+    updateRequest: fetchUpdateSerialRule,
+    deleteRequest: fetchDeleteSerialRule,
+    entityName: '缂栫爜瑙勫垯',
+    resolveRecordLabel: (record) => record?.code || record?.name || record?.id,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  })
+  handleDeleteAction = handleDelete
+
+  function handleSearch(params) {
+    replaceSearchParams(buildSerialRuleSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createSerialRuleSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/serial-rule/modules/serial-rule-detail-drawer.vue b/rsf-design/src/views/system/serial-rule/modules/serial-rule-detail-drawer.vue
new file mode 100644
index 0000000..6bc70c9
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule/modules/serial-rule-detail-drawer.vue
@@ -0,0 +1,45 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="缂栫爜瑙勫垯璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="缂栧彿">{{ displayData.code || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒嗛殧绗�">{{ displayData.delimit || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="閲嶇疆瑙勫垯">{{ displayData.resetText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="閲嶇疆渚濊禆">{{ displayData.resetDep || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="褰撳墠鍊�">{{ displayData.currValue ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏈�杩戠敓鎴愮紪鐮�">{{ displayData.lastCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊浜�">{{ displayData.updateByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓浜�">{{ displayData.createByLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeSerialRuleListRow } from '../serialRulePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeSerialRuleListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/serial-rule/modules/serial-rule-dialog.vue b/rsf-design/src/views/system/serial-rule/modules/serial-rule-dialog.vue
new file mode 100644
index 0000000..513a942
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule/modules/serial-rule-dialog.vue
@@ -0,0 +1,195 @@
+<template>
+  <ElDialog
+    :title="dialogTitle"
+    :model-value="visible"
+    width="820px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildSerialRuleDialogModel,
+    createSerialRuleFormState,
+    getSerialRuleResetOptions
+  } from '../serialRulePage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    serialRuleData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createSerialRuleFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => (isEdit.value ? '缂栬緫缂栫爜瑙勫垯' : '鏂板缂栫爜瑙勫垯'))
+
+  const rules = computed(() => ({
+    code: [{ required: true, message: '璇疯緭鍏ョ紪鍙�', trigger: 'blur' }],
+    name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }],
+    reset: [{ required: true, message: '璇烽�夋嫨閲嶇疆瑙勫垯', trigger: 'change' }]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '缂栧彿',
+      key: 'code',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ョ紪鍙�',
+        clearable: true,
+        disabled: isEdit.value
+      }
+    },
+    {
+      label: '鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ悕绉�',
+        clearable: true
+      }
+    },
+    {
+      label: '鍒嗛殧绗�',
+      key: 'delimit',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ュ垎闅旂',
+        clearable: true
+      }
+    },
+    {
+      label: '閲嶇疆瑙勫垯',
+      key: 'reset',
+      type: 'select',
+      props: {
+        options: getSerialRuleResetOptions(),
+        placeholder: '璇烽�夋嫨閲嶇疆瑙勫垯'
+      }
+    },
+    {
+      label: '閲嶇疆渚濊禆',
+      key: 'resetDep',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ラ噸缃緷璧�',
+        clearable: true
+      }
+    },
+    {
+      label: '褰撳墠鍊�',
+      key: 'currValue',
+      type: 'number',
+      props: {
+        min: 0,
+        controlsPosition: 'right',
+        style: { width: '100%' }
+      }
+    },
+    {
+      label: '鏈�杩戠敓鎴愮紪鐮�',
+      key: 'lastCode',
+      type: 'input',
+      props: {
+        placeholder: '璇疯緭鍏ユ渶杩戠敓鎴愮紪鐮�',
+        clearable: true
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ],
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createSerialRuleFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildSerialRuleDialogModel(props.serialRuleData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.serialRuleData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/serial-rule/serialRulePage.helpers.js b/rsf-design/src/views/system/serial-rule/serialRulePage.helpers.js
new file mode 100644
index 0000000..e21e761
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule/serialRulePage.helpers.js
@@ -0,0 +1,124 @@
+const SERIAL_RULE_RESET_OPTIONS = [
+  { label: '骞撮噸缃�', value: 'year' },
+  { label: '鏈堥噸缃�', value: 'month' },
+  { label: '鏃ラ噸缃�', value: 'day' }
+]
+
+export function createSerialRuleSearchState() {
+  return {
+    condition: '',
+    code: '',
+    name: '',
+    reset: '',
+    status: ''
+  }
+}
+
+export function createSerialRuleFormState() {
+  return {
+    id: null,
+    code: '',
+    name: '',
+    delimit: '',
+    reset: '',
+    resetDep: '',
+    currValue: 0,
+    lastCode: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getSerialRulePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getSerialRuleResetOptions() {
+  return SERIAL_RULE_RESET_OPTIONS
+}
+
+export function getSerialRuleResetLabel(value) {
+  return SERIAL_RULE_RESET_OPTIONS.find((item) => item.value === value)?.label || value || '-'
+}
+
+export function getSerialRuleStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '鍐荤粨', type: 'danger', bool: false }
+}
+
+export function buildSerialRuleSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    code: String(params.code || '').trim(),
+    name: String(params.name || '').trim(),
+    ...(params.reset ? { reset: String(params.reset) } : {}),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildSerialRulePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSerialRuleSearchParams(params)
+  }
+}
+
+export function buildSerialRuleDialogModel(record = {}) {
+  return {
+    ...createSerialRuleFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    code: record.code || '',
+    name: record.name || '',
+    delimit: record.delimit || '',
+    reset: record.reset || '',
+    resetDep: record.resetDep || '',
+    currValue: record.currValue !== undefined && record.currValue !== null ? Number(record.currValue) : 0,
+    lastCode: record.lastCode || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildSerialRuleSavePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    code: String(formData.code || '').trim(),
+    name: String(formData.name || '').trim(),
+    delimit: String(formData.delimit || '').trim(),
+    reset: String(formData.reset || '').trim(),
+    resetDep: String(formData.resetDep || '').trim(),
+    currValue: Number(formData.currValue || 0),
+    lastCode: String(formData.lastCode || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeSerialRuleListRow(record = {}) {
+  const statusMeta = getSerialRuleStatusMeta(record.status)
+  return {
+    ...record,
+    code: record.code || '',
+    name: record.name || '',
+    delimit: record.delimit || '-',
+    resetText: record['reset$'] || getSerialRuleResetLabel(record.reset),
+    resetDep: record.resetDep || '-',
+    currValue: record.currValue ?? 0,
+    lastCode: record.lastCode || '-',
+    memo: record.memo || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateByLabel: record['updateBy$'] || '',
+    createByLabel: record['createBy$'] || '',
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/serial-rule/serialRuleTable.columns.js b/rsf-design/src/views/system/serial-rule/serialRuleTable.columns.js
new file mode 100644
index 0000000..02faecc
--- /dev/null
+++ b/rsf-design/src/views/system/serial-rule/serialRuleTable.columns.js
@@ -0,0 +1,92 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createSerialRuleTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'code',
+      label: '缂栧彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'name',
+      label: '鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'delimit',
+      label: '鍒嗛殧绗�',
+      width: 100,
+      formatter: (row) => row.delimit || '-'
+    },
+    {
+      prop: 'resetText',
+      label: '閲嶇疆瑙勫垯',
+      minWidth: 120,
+      formatter: (row) => row.resetText || '-'
+    },
+    {
+      prop: 'resetDep',
+      label: '閲嶇疆渚濊禆',
+      minWidth: 120,
+      formatter: (row) => row.resetDep || '-'
+    },
+    {
+      prop: 'currValue',
+      label: '褰撳墠鍊�',
+      width: 100,
+      formatter: (row) => row.currValue ?? 0
+    },
+    {
+      prop: 'lastCode',
+      label: '鏈�杩戠敓鎴愮紪鐮�',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.lastCode || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/subsystem-flow-template/index.vue b/rsf-design/src/views/system/subsystem-flow-template/index.vue
new file mode 100644
index 0000000..8f83909
--- /dev/null
+++ b/rsf-design/src/views/system/subsystem-flow-template/index.vue
@@ -0,0 +1,185 @@
+<template>
+  <div class="subsystem-flow-template-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <SubsystemFlowTemplateDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  import { useUserStore } from '@/store/modules/user'
+  import { useTable } from '@/hooks/core/useTable'
+  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
+  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
+  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
+  import {
+    fetchExportSubsystemFlowTemplateReport,
+    fetchGetSubsystemFlowTemplateDetail,
+    fetchGetSubsystemFlowTemplateMany,
+    fetchSubsystemFlowTemplatePage
+  } from '@/api/subsystem-flow-template'
+  import {
+    buildSubsystemFlowTemplatePageQueryParams,
+    buildSubsystemFlowTemplatePrintRows,
+    buildSubsystemFlowTemplateSearchParams,
+    createSubsystemFlowTemplateSearchState,
+    getSubsystemFlowTemplatePaginationKey,
+    getSubsystemFlowTemplateReportColumns,
+    normalizeSubsystemFlowTemplateRow,
+    SUBSYSTEM_FLOW_TEMPLATE_REPORT_TITLE
+  } from './subsystemFlowTemplatePage.helpers'
+  import { createSubsystemFlowTemplateTableColumns } from './subsystemFlowTemplateTable.columns'
+  import SubsystemFlowTemplateDetailDrawer from './modules/subsystem-flow-template-detail-drawer.vue'
+
+  defineOptions({ name: 'SubsystemFlowTemplate' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createSubsystemFlowTemplateSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = SUBSYSTEM_FLOW_TEMPLATE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildSubsystemFlowTemplateSearchParams(searchForm.value))
+  const reportColumns = getSubsystemFlowTemplateReportColumns()
+
+  const searchItems = computed(() => [
+    { label: '鍏抽敭瀛�', key: 'condition', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬬紪鐮�/娴佺▼鍚嶇О' } },
+    { label: '娴佺▼缂栫爜', key: 'flowCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬬紪鐮�' } },
+    { label: '娴佺▼鍚嶇О', key: 'flowName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ユ祦绋嬪悕绉�' } },
+    { label: '绯荤粺缂栫爜', key: 'systemCode', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ郴缁熺紪鐮�' } },
+    { label: '绯荤粺鍚嶇О', key: 'systemName', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ョ郴缁熷悕绉�' } },
+    { label: '鑺傜偣绫诲瀷', key: 'nodeType', type: 'input', props: { clearable: true, placeholder: '璇疯緭鍏ヨ妭鐐圭被鍨�' } },
+    { label: '寮�濮嬫棩鏈�', key: 'timeStart', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } },
+    { label: '缁撴潫鏃ユ湡', key: 'timeEnd', type: 'date', props: { clearable: true, valueFormat: 'YYYY-MM-DD', type: 'date' } }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchSubsystemFlowTemplatePage,
+      apiParams: buildSubsystemFlowTemplatePageQueryParams(searchForm.value),
+      paginationKey: getSubsystemFlowTemplatePaginationKey(),
+      columnsFactory: () => createSubsystemFlowTemplateTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeSubsystemFlowTemplateRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetSubsystemFlowTemplateMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchSubsystemFlowTemplatePage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'subsystem-flow-template.xlsx',
+    requestExport: (payload) =>
+      fetchExportSubsystemFlowTemplateReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildSubsystemFlowTemplatePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetSubsystemFlowTemplateDetail(id)
+    detailData.value = normalizeSubsystemFlowTemplateRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildSubsystemFlowTemplateSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createSubsystemFlowTemplateSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/subsystem-flow-template/modules/subsystem-flow-template-detail-drawer.vue b/rsf-design/src/views/system/subsystem-flow-template/modules/subsystem-flow-template-detail-drawer.vue
new file mode 100644
index 0000000..3ba9724
--- /dev/null
+++ b/rsf-design/src/views/system/subsystem-flow-template/modules/subsystem-flow-template-detail-drawer.vue
@@ -0,0 +1,33 @@
+<template>
+  <ElDrawer :model-value="visible" title="瀛愮郴缁熸祦绋嬫ā鏉胯鎯�" size="620px" @close="emit('update:visible', false)">
+    <ElDescriptions :column="2" border>
+      <ElDescriptionsItem label="娴佺▼缂栫爜">{{ detail.flowCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="娴佺▼鍚嶇О">{{ detail.flowName || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="绯荤粺缂栫爜">{{ detail.systemCode || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="绯荤粺鍚嶇О">{{ detail.systemName || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鑺傜偣绫诲瀷">{{ detail.nodeType || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐗堟湰">{{ detail.version || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="褰撳墠鐗堟湰">{{ detail.isCurrentText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="瓒呮椂绛栫暐">{{ detail.timeoutStrategy || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="瓒呮椂(绉�)">{{ detail.timeoutSeconds || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鏈�澶ч噸璇曟鏁�">{{ detail.maxRetryTimes || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+      <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+    </ElDescriptions>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+</script>
diff --git a/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplatePage.helpers.js b/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplatePage.helpers.js
new file mode 100644
index 0000000..062bc7b
--- /dev/null
+++ b/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplatePage.helpers.js
@@ -0,0 +1,163 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const SUBSYSTEM_FLOW_TEMPLATE_REPORT_TITLE = '瀛愮郴缁熸祦绋嬫ā鏉挎姤琛�'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeBoolText(value) {
+  const numericValue = Number(value)
+  if (numericValue === 1) return '鏄�'
+  if (numericValue === 0) return '鍚�'
+  return '--'
+}
+
+export function createSubsystemFlowTemplateSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    flowCode: '',
+    flowName: '',
+    systemCode: '',
+    systemName: '',
+    nodeType: '',
+    version: '',
+    isCurrent: '',
+    effectiveTime: '',
+    timeoutStrategy: '',
+    timeoutSeconds: '',
+    maxRetryTimes: '',
+    needNotify: '',
+    notifyTemplate: '',
+    remark: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getSubsystemFlowTemplatePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildSubsystemFlowTemplateSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'flowCode',
+    'flowName',
+    'systemCode',
+    'systemName',
+    'nodeType',
+    'effectiveTime',
+    'timeoutStrategy',
+    'notifyTemplate',
+    'remark',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['version', 'isCurrent', 'timeoutSeconds', 'maxRetryTimes', 'needNotify', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildSubsystemFlowTemplatePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildSubsystemFlowTemplateSearchParams(params)
+  }
+}
+
+export function normalizeSubsystemFlowTemplateRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+  return {
+    ...record,
+    id: record.id ?? '--',
+    flowCode: normalizeText(record.flowCode) || '--',
+    flowName: normalizeText(record.flowName) || '--',
+    systemCode: normalizeText(record.systemCode) || '--',
+    systemName: normalizeText(record.systemName) || '--',
+    nodeType: normalizeText(record.nodeType) || '--',
+    version: record.version ?? '--',
+    isCurrentText: normalizeBoolText(record.isCurrent),
+    timeoutStrategy: normalizeText(record.timeoutStrategy) || '--',
+    timeoutSeconds: record.timeoutSeconds ?? '--',
+    maxRetryTimes: record.maxRetryTimes ?? '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getSubsystemFlowTemplateReportColumns() {
+  return [
+    { prop: 'flowCode', label: '娴佺▼缂栫爜' },
+    { prop: 'flowName', label: '娴佺▼鍚嶇О' },
+    { prop: 'systemCode', label: '绯荤粺缂栫爜' },
+    { prop: 'systemName', label: '绯荤粺鍚嶇О' },
+    { prop: 'nodeType', label: '鑺傜偣绫诲瀷' },
+    { prop: 'version', label: '鐗堟湰' },
+    { prop: 'isCurrentText', label: '褰撳墠鐗堟湰' },
+    { prop: 'timeoutStrategy', label: '瓒呮椂绛栫暐' },
+    { prop: 'timeoutSeconds', label: '瓒呮椂(绉�)' },
+    { prop: 'maxRetryTimes', label: '鏈�澶ч噸璇曟鏁�' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildSubsystemFlowTemplatePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeSubsystemFlowTemplateRow(record)
+    return {
+      flowCode: row.flowCode,
+      flowName: row.flowName,
+      systemCode: row.systemCode,
+      systemName: row.systemName,
+      nodeType: row.nodeType,
+      version: row.version,
+      isCurrentText: row.isCurrentText,
+      timeoutStrategy: row.timeoutStrategy,
+      timeoutSeconds: row.timeoutSeconds,
+      maxRetryTimes: row.maxRetryTimes,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplateTable.columns.js b/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplateTable.columns.js
new file mode 100644
index 0000000..e465177
--- /dev/null
+++ b/rsf-design/src/views/system/subsystem-flow-template/subsystemFlowTemplateTable.columns.js
@@ -0,0 +1,48 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createSubsystemFlowTemplateTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    { prop: 'flowCode', label: '娴佺▼缂栫爜', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'flowName', label: '娴佺▼鍚嶇О', minWidth: 160, showOverflowTooltip: true },
+    { prop: 'systemCode', label: '绯荤粺缂栫爜', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'systemName', label: '绯荤粺鍚嶇О', minWidth: 140, showOverflowTooltip: true },
+    { prop: 'nodeType', label: '鑺傜偣绫诲瀷', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'version', label: '鐗堟湰', width: 90, align: 'center' },
+    { prop: 'isCurrentText', label: '褰撳墠鐗堟湰', width: 100, align: 'center' },
+    { prop: 'timeoutStrategy', label: '瓒呮椂绛栫暐', minWidth: 120, showOverflowTooltip: true },
+    { prop: 'timeoutSeconds', label: '瓒呮椂(绉�)', width: 100, align: 'right' },
+    { prop: 'maxRetryTimes', label: '鏈�澶ч噸璇曟鏁�', width: 120, align: 'right' },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    { prop: 'memo', label: '澶囨敞', minWidth: 180, showOverflowTooltip: true },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/task-instance-node/index.vue b/rsf-design/src/views/system/task-instance-node/index.vue
new file mode 100644
index 0000000..aea1479
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance-node/index.vue
@@ -0,0 +1,233 @@
+<template>
+  <div class="task-instance-node-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskInstanceNodeDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportTaskInstanceNodeReport,
+    fetchGetTaskInstanceNodeDetail,
+    fetchGetTaskInstanceNodeMany,
+    fetchTaskInstanceNodePage
+  } from '@/api/task-instance-node'
+  import {
+    buildTaskInstanceNodePageQueryParams,
+    buildTaskInstanceNodePrintRows,
+    buildTaskInstanceNodeSearchParams,
+    createTaskInstanceNodeSearchState,
+    getTaskInstanceNodePaginationKey,
+    getTaskInstanceNodeReportColumns,
+    normalizeTaskInstanceNodeRow,
+    TASK_INSTANCE_NODE_REPORT_TITLE
+  } from './taskInstanceNodePage.helpers'
+  import { createTaskInstanceNodeTableColumns } from './taskInstanceNodeTable.columns'
+  import TaskInstanceNodeDetailDrawer from './modules/task-instance-node-detail-drawer.vue'
+
+  defineOptions({ name: 'TaskInstanceNode' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createTaskInstanceNodeSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = TASK_INSTANCE_NODE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskInstanceNodeSearchParams(searchForm.value))
+  const reportColumns = getTaskInstanceNodeReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿/鑺傜偣缂栫爜/鑺傜偣鍚嶇О'
+      }
+    },
+    {
+      label: '浠诲姟鍙�',
+      key: 'taskNo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿'
+      }
+    },
+    {
+      label: '鑺傜偣缂栫爜',
+      key: 'nodeCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ妭鐐圭紪鐮�'
+      }
+    },
+    {
+      label: '鑺傜偣鍚嶇О',
+      key: 'nodeName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ妭鐐瑰悕绉�'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTaskInstanceNodePage,
+      apiParams: buildTaskInstanceNodePageQueryParams(searchForm.value),
+      paginationKey: getTaskInstanceNodePaginationKey(),
+      columnsFactory: () => createTaskInstanceNodeTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeTaskInstanceNodeRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskInstanceNodeMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskInstanceNodePage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'task-instance-node.xlsx',
+    requestExport: (payload) =>
+      fetchExportTaskInstanceNodeReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTaskInstanceNodePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetTaskInstanceNodeDetail(id)
+    detailData.value = normalizeTaskInstanceNodeRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskInstanceNodeSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskInstanceNodeSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/task-instance-node/modules/task-instance-node-detail-drawer.vue b/rsf-design/src/views/system/task-instance-node/modules/task-instance-node-detail-drawer.vue
new file mode 100644
index 0000000..e6888c4
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance-node/modules/task-instance-node-detail-drawer.vue
@@ -0,0 +1,45 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟瀹炰緥鑺傜偣璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="浠诲姟ID">{{ detail.taskId ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="浠诲姟鍙�">{{ detail.taskNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣缂栫爜">{{ detail.nodeCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣鍚嶇О">{{ detail.nodeName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣椤哄簭">{{ detail.nodeOrder ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="绯荤粺鍚嶇О">{{ detail.systemName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鎵ц缁撴灉" :span="2">{{ detail.executeResult || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="閿欒淇℃伅" :span="2">{{ detail.errorMessage || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹為檯寮�濮嬫椂闂�">{{ detail.actualStartTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="瀹為檯缁撴潫鏃堕棿">{{ detail.actualEndTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑰楁椂(绉�)">{{ detail.durationSeconds ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'TaskInstanceNodeDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/task-instance-node/taskInstanceNodePage.helpers.js b/rsf-design/src/views/system/task-instance-node/taskInstanceNodePage.helpers.js
new file mode 100644
index 0000000..19f3ae3
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance-node/taskInstanceNodePage.helpers.js
@@ -0,0 +1,176 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const TASK_INSTANCE_NODE_REPORT_TITLE = '浠诲姟瀹炰緥鑺傜偣鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeDateTime(value) {
+  return normalizeText(value) || '--'
+}
+
+export function createTaskInstanceNodeSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    taskId: '',
+    taskNo: '',
+    nodeOrder: '',
+    nodeCode: '',
+    nodeName: '',
+    nodeType: '',
+    systemCode: '',
+    systemName: '',
+    executeParams: '',
+    executeResult: '',
+    errorCode: '',
+    errorMessage: '',
+    estimatedStartTime: '',
+    actualStartTime: '',
+    actualEndTime: '',
+    timeoutAt: '',
+    durationSeconds: '',
+    retryTimes: '',
+    maxRetryTimes: '',
+    dependsOnNodes: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getTaskInstanceNodePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildTaskInstanceNodeSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'taskNo',
+    'nodeCode',
+    'nodeName',
+    'nodeType',
+    'systemCode',
+    'systemName',
+    'executeParams',
+    'executeResult',
+    'errorCode',
+    'errorMessage',
+    'estimatedStartTime',
+    'actualStartTime',
+    'actualEndTime',
+    'timeoutAt',
+    'dependsOnNodes',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;['taskId', 'nodeOrder', 'durationSeconds', 'retryTimes', 'maxRetryTimes', 'status'].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildTaskInstanceNodePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskInstanceNodeSearchParams(params)
+  }
+}
+
+export function normalizeTaskInstanceNodeRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    taskId: record.taskId ?? '--',
+    taskNo: normalizeText(record.taskNo) || '--',
+    nodeOrder: record.nodeOrder ?? '--',
+    nodeCode: normalizeText(record.nodeCode) || '--',
+    nodeName: normalizeText(record.nodeName) || '--',
+    systemName: normalizeText(record.systemName) || '--',
+    executeResult: normalizeText(record.executeResult) || '--',
+    errorMessage: normalizeText(record.errorMessage) || '--',
+    actualStartTimeText: normalizeDateTime(record['actualStartTime$'] || record.actualStartTimeText || record.actualStartTime),
+    actualEndTimeText: normalizeDateTime(record['actualEndTime$'] || record.actualEndTimeText || record.actualEndTime),
+    durationSeconds: record.durationSeconds ?? '--',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--'
+  }
+}
+
+export function getTaskInstanceNodeReportColumns() {
+  return [
+    { prop: 'taskId', label: '浠诲姟ID' },
+    { prop: 'taskNo', label: '浠诲姟鍙�' },
+    { prop: 'nodeOrder', label: '鑺傜偣椤哄簭' },
+    { prop: 'nodeCode', label: '鑺傜偣缂栫爜' },
+    { prop: 'nodeName', label: '鑺傜偣鍚嶇О' },
+    { prop: 'systemName', label: '绯荤粺鍚嶇О' },
+    { prop: 'executeResult', label: '鎵ц缁撴灉' },
+    { prop: 'errorMessage', label: '閿欒淇℃伅' },
+    { prop: 'actualStartTimeText', label: '瀹為檯寮�濮嬫椂闂�' },
+    { prop: 'actualEndTimeText', label: '瀹為檯缁撴潫鏃堕棿' },
+    { prop: 'durationSeconds', label: '鑰楁椂(绉�)' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildTaskInstanceNodePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeTaskInstanceNodeRow(record)
+    return {
+      taskId: row.taskId,
+      taskNo: row.taskNo,
+      nodeOrder: row.nodeOrder,
+      nodeCode: row.nodeCode,
+      nodeName: row.nodeName,
+      systemName: row.systemName,
+      executeResult: row.executeResult,
+      errorMessage: row.errorMessage,
+      actualStartTimeText: row.actualStartTimeText,
+      actualEndTimeText: row.actualEndTimeText,
+      durationSeconds: row.durationSeconds,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/task-instance-node/taskInstanceNodeTable.columns.js b/rsf-design/src/views/system/task-instance-node/taskInstanceNodeTable.columns.js
new file mode 100644
index 0000000..e2bf251
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance-node/taskInstanceNodeTable.columns.js
@@ -0,0 +1,97 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTaskInstanceNodeTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'taskId',
+      label: '浠诲姟ID',
+      minWidth: 110,
+      align: 'right'
+    },
+    {
+      prop: 'taskNo',
+      label: '浠诲姟鍙�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'nodeOrder',
+      label: '鑺傜偣椤哄簭',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'nodeCode',
+      label: '鑺傜偣缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'nodeName',
+      label: '鑺傜偣鍚嶇О',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'systemName',
+      label: '绯荤粺鍚嶇О',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'executeResult',
+      label: '鎵ц缁撴灉',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'actualStartTimeText',
+      label: '瀹為檯寮�濮嬫椂闂�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'actualEndTimeText',
+      label: '瀹為檯缁撴潫鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/task-instance/index.vue b/rsf-design/src/views/system/task-instance/index.vue
new file mode 100644
index 0000000..19dd2c1
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance/index.vue
@@ -0,0 +1,242 @@
+<template>
+  <div class="task-instance-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ListExportPrint
+            class="inline-flex"
+            :preview-visible="previewVisible"
+            @update:previewVisible="handlePreviewVisibleChange"
+            :report-title="reportTitle"
+            :selected-rows="selectedRows"
+            :query-params="reportQueryParams"
+            :columns="reportColumns"
+            :preview-rows="previewRows"
+            :preview-meta="previewMeta"
+            :total="pagination.total"
+            :disabled="loading"
+            @export="handleExport"
+            @print="handlePrint"
+          />
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <TaskInstanceDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref } from 'vue'
+  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 {
+    fetchExportTaskInstanceReport,
+    fetchGetTaskInstanceDetail,
+    fetchGetTaskInstanceMany,
+    fetchTaskInstancePage
+  } from '@/api/task-instance'
+  import {
+    buildTaskInstancePageQueryParams,
+    buildTaskInstancePrintRows,
+    buildTaskInstanceSearchParams,
+    createTaskInstanceSearchState,
+    getTaskInstancePaginationKey,
+    getTaskInstanceReportColumns,
+    normalizeTaskInstanceRow,
+    TASK_INSTANCE_REPORT_TITLE
+  } from './taskInstancePage.helpers'
+  import { createTaskInstanceTableColumns } from './taskInstanceTable.columns'
+  import TaskInstanceDetailDrawer from './modules/task-instance-detail-drawer.vue'
+
+  defineOptions({ name: 'TaskInstance' })
+
+  const userStore = useUserStore()
+  const searchForm = ref(createTaskInstanceSearchState())
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const selectedRows = ref([])
+  const reportTitle = TASK_INSTANCE_REPORT_TITLE
+  const reportQueryParams = computed(() => buildTaskInstanceSearchParams(searchForm.value))
+  const reportColumns = getTaskInstanceReportColumns()
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿/涓氬姟鍗曞彿/鑺傜偣鍚�'
+      }
+    },
+    {
+      label: '浠诲姟鍙�',
+      key: 'taskNo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″彿'
+      }
+    },
+    {
+      label: '涓氬姟鍗曞彿',
+      key: 'bizNo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔″崟鍙�'
+      }
+    },
+    {
+      label: '涓氬姟绫诲瀷',
+      key: 'bizType',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔$被鍨�'
+      }
+    },
+    {
+      label: '褰撳墠鑺傜偣',
+      key: 'currentNodeName',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ綋鍓嶈妭鐐�'
+      }
+    },
+    {
+      label: '寮�濮嬫棩鏈�',
+      key: 'timeStart',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    },
+    {
+      label: '缁撴潫鏃ユ湡',
+      key: 'timeEnd',
+      type: 'date',
+      props: {
+        clearable: true,
+        valueFormat: 'YYYY-MM-DD',
+        type: 'date'
+      }
+    }
+  ])
+
+  function openDetail(row) {
+    detailDrawerVisible.value = true
+    loadDetail(row.id, row)
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchTaskInstancePage,
+      apiParams: buildTaskInstancePageQueryParams(searchForm.value),
+      paginationKey: getTaskInstancePaginationKey(),
+      columnsFactory: () => createTaskInstanceTableColumns({ handleView: openDetail })
+    },
+    transform: {
+      dataTransformer: (records) =>
+        Array.isArray(records) ? records.map((item) => normalizeTaskInstanceRow(item)) : []
+    }
+  })
+
+  const resolvePrintRecords = async (payload) => {
+    if (Array.isArray(payload?.ids) && payload.ids.length > 0) {
+      return defaultResponseAdapter(await fetchGetTaskInstanceMany(payload.ids)).records
+    }
+    return defaultResponseAdapter(
+      await fetchTaskInstancePage({
+        ...reportQueryParams.value,
+        current: 1,
+        pageSize: Number(pagination.total) > 0 ? Number(pagination.total) : 20
+      })
+    ).records
+  }
+
+  const {
+    previewVisible,
+    previewRows,
+    previewMeta,
+    handlePreviewVisibleChange,
+    handleExport,
+    handlePrint
+  } = usePrintExportPage({
+    downloadFileName: 'task-instance.xlsx',
+    requestExport: (payload) =>
+      fetchExportTaskInstanceReport(payload, {
+        headers: {
+          Authorization: userStore.accessToken || ''
+        }
+      }),
+    resolvePrintRecords,
+    buildPreviewRows: (records) => buildTaskInstancePrintRows(records),
+    buildPreviewMeta: (rows) => ({
+      reportTitle,
+      reportDate: new Date().toLocaleDateString('zh-CN'),
+      printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+      operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
+      count: rows.length
+    })
+  })
+
+  async function loadDetail(id, fallback) {
+    const detail = await fetchGetTaskInstanceDetail(id)
+    detailData.value = normalizeTaskInstanceRow({
+      ...fallback,
+      ...detail
+    })
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTaskInstanceSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTaskInstanceSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/task-instance/modules/task-instance-detail-drawer.vue b/rsf-design/src/views/system/task-instance/modules/task-instance-detail-drawer.vue
new file mode 100644
index 0000000..b8be701
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance/modules/task-instance-detail-drawer.vue
@@ -0,0 +1,45 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="浠诲姟瀹炰緥璇︽儏"
+    size="72%"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElScrollbar class="h-[calc(100vh-120px)]">
+      <div class="flex min-h-full flex-col gap-4 pr-2">
+        <ElDescriptions :column="4" border>
+          <ElDescriptionsItem label="浠诲姟鍙�">{{ detail.taskNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟鍗曞彿">{{ detail.bizNo || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="涓氬姟绫诲瀷">{{ detail.bizType || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="妯℃澘缂栫爜">{{ detail.templateCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鏉ユ簮缂栫爜">{{ detail.sourceCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐩爣缂栫爜">{{ detail.targetCode || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="褰撳墠鑺傜偣">{{ detail.currentNodeName || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鑺傜偣杩涘害">{{ detail.progressRate ?? '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴灉淇℃伅" :span="2">{{ detail.resultMessage || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="寮�濮嬫椂闂�">{{ detail.startTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="缁撴潫鏃堕棿">{{ detail.endTimeText || '--' }}</ElDescriptionsItem>
+          <ElDescriptionsItem label="鐘舵��">
+            <ElTag :type="detail.statusType || 'info'" effect="light">{{ detail.statusText || '--' }}</ElTag>
+          </ElDescriptionsItem>
+          <ElDescriptionsItem label="澶囨敞" :span="4">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        </ElDescriptions>
+      </div>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineOptions({ name: 'TaskInstanceDetailDrawer' })
+
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/task-instance/taskInstancePage.helpers.js b/rsf-design/src/views/system/task-instance/taskInstancePage.helpers.js
new file mode 100644
index 0000000..3388ece
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance/taskInstancePage.helpers.js
@@ -0,0 +1,204 @@
+const STATUS_META = {
+  1: { text: '姝e父', type: 'success' },
+  0: { text: '鍐荤粨', type: 'info' }
+}
+
+export const TASK_INSTANCE_REPORT_TITLE = '浠诲姟瀹炰緥鎶ヨ〃'
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return null
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : null
+}
+
+function normalizeDateTime(value) {
+  return normalizeText(value) || '--'
+}
+
+export function createTaskInstanceSearchState() {
+  return {
+    condition: '',
+    timeStart: '',
+    timeEnd: '',
+    taskNo: '',
+    bizNo: '',
+    bizType: '',
+    templateId: '',
+    templateCode: '',
+    templateVersion: '',
+    sourceInfo: '',
+    targetInfo: '',
+    sourceCode: '',
+    targetCode: '',
+    plannedPath: '',
+    actualPath: '',
+    priority: '',
+    timeoutAt: '',
+    currentNodeCode: '',
+    currentNodeName: '',
+    totalNodes: '',
+    completedNodes: '',
+    progressRate: '',
+    estimatedDurationMinutes: '',
+    actualDurationMinutes: '',
+    startTime: '',
+    endTime: '',
+    resultCode: '',
+    resultMessage: '',
+    resultData: '',
+    retryTimes: '',
+    lastRetryTime: '',
+    extParams: '',
+    remark: '',
+    memo: '',
+    status: ''
+  }
+}
+
+export function getTaskInstancePaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function buildTaskInstanceSearchParams(params = {}) {
+  const result = {}
+
+  ;[
+    'condition',
+    'taskNo',
+    'bizNo',
+    'bizType',
+    'templateCode',
+    'sourceInfo',
+    'targetInfo',
+    'sourceCode',
+    'targetCode',
+    'plannedPath',
+    'actualPath',
+    'timeoutAt',
+    'currentNodeCode',
+    'currentNodeName',
+    'startTime',
+    'endTime',
+    'resultCode',
+    'resultMessage',
+    'resultData',
+    'lastRetryTime',
+    'extParams',
+    'remark',
+    'memo'
+  ].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  ;['timeStart', 'timeEnd'].forEach((key) => {
+    if (params[key]) {
+      result[key] = params[key]
+    }
+  })
+
+  ;[
+    'templateId',
+    'templateVersion',
+    'priority',
+    'totalNodes',
+    'completedNodes',
+    'progressRate',
+    'estimatedDurationMinutes',
+    'actualDurationMinutes',
+    'retryTimes',
+    'status'
+  ].forEach((key) => {
+    const value = normalizeNumber(params[key])
+    if (value !== null) {
+      result[key] = value
+    }
+  })
+
+  return {
+    condition: '',
+    ...result
+  }
+}
+
+export function buildTaskInstancePageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTaskInstanceSearchParams(params)
+  }
+}
+
+export function normalizeTaskInstanceRow(record = {}) {
+  const statusMeta = STATUS_META[Number(record.status)] || STATUS_META[0]
+
+  return {
+    ...record,
+    id: record.id ?? '--',
+    taskNo: normalizeText(record.taskNo) || '--',
+    bizNo: normalizeText(record.bizNo) || '--',
+    bizType: normalizeText(record.bizType) || '--',
+    templateCode: normalizeText(record.templateCode) || '--',
+    sourceCode: normalizeText(record.sourceCode) || '--',
+    targetCode: normalizeText(record.targetCode) || '--',
+    currentNodeName: normalizeText(record.currentNodeName) || '--',
+    progressRate: record.progressRate ?? '--',
+    resultMessage: normalizeText(record.resultMessage) || '--',
+    startTimeText: normalizeDateTime(record['startTime$'] || record.startTimeText || record.startTime),
+    endTimeText: normalizeDateTime(record['endTime$'] || record.endTimeText || record.endTime),
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    memo: normalizeText(record.memo) || '--',
+    remark: normalizeText(record.remark) || '--'
+  }
+}
+
+export function getTaskInstanceReportColumns() {
+  return [
+    { prop: 'taskNo', label: '浠诲姟鍙�' },
+    { prop: 'bizNo', label: '涓氬姟鍗曞彿' },
+    { prop: 'bizType', label: '涓氬姟绫诲瀷' },
+    { prop: 'templateCode', label: '妯℃澘缂栫爜' },
+    { prop: 'sourceCode', label: '鏉ユ簮缂栫爜' },
+    { prop: 'targetCode', label: '鐩爣缂栫爜' },
+    { prop: 'currentNodeName', label: '褰撳墠鑺傜偣' },
+    { prop: 'progressRate', label: '鑺傜偣杩涘害' },
+    { prop: 'resultMessage', label: '缁撴灉淇℃伅' },
+    { prop: 'startTimeText', label: '寮�濮嬫椂闂�' },
+    { prop: 'endTimeText', label: '缁撴潫鏃堕棿' },
+    { prop: 'statusText', label: '鐘舵��' },
+    { prop: 'memo', label: '澶囨敞' }
+  ]
+}
+
+export function buildTaskInstancePrintRows(records = []) {
+  return (Array.isArray(records) ? records : []).map((record) => {
+    const row = normalizeTaskInstanceRow(record)
+    return {
+      taskNo: row.taskNo,
+      bizNo: row.bizNo,
+      bizType: row.bizType,
+      templateCode: row.templateCode,
+      sourceCode: row.sourceCode,
+      targetCode: row.targetCode,
+      currentNodeName: row.currentNodeName,
+      progressRate: row.progressRate,
+      resultMessage: row.resultMessage,
+      startTimeText: row.startTimeText,
+      endTimeText: row.endTimeText,
+      statusText: row.statusText,
+      memo: row.memo
+    }
+  })
+}
diff --git a/rsf-design/src/views/system/task-instance/taskInstanceTable.columns.js b/rsf-design/src/views/system/task-instance/taskInstanceTable.columns.js
new file mode 100644
index 0000000..8bfac20
--- /dev/null
+++ b/rsf-design/src/views/system/task-instance/taskInstanceTable.columns.js
@@ -0,0 +1,97 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTaskInstanceTableColumns({ handleView } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'taskNo',
+      label: '浠诲姟鍙�',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'bizNo',
+      label: '涓氬姟鍗曞彿',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'templateCode',
+      label: '妯℃澘缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'currentNodeName',
+      label: '褰撳墠鑺傜偣',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'progressRate',
+      label: '鑺傜偣杩涘害',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'resultMessage',
+      label: '缁撴灉淇℃伅',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'startTimeText',
+      label: '寮�濮嬫椂闂�',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'endTimeText',
+      label: '缁撴潫鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      align: 'center',
+      formatter: (row) =>
+        h(
+          ElTag,
+          {
+            type: row?.statusType || 'info',
+            effect: 'light'
+          },
+          () => row?.statusText || '--'
+        )
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 92,
+      align: 'center',
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:eye-line',
+          onClick: () => handleView?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/tenant/index.vue b/rsf-design/src/views/system/tenant/index.vue
new file mode 100644
index 0000000..2bbfe52
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/index.vue
@@ -0,0 +1,283 @@
+<template>
+  <div class="tenant-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="false"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <ElButton v-auth="'add'" @click="showInitDialog" v-ripple>鍒濆鍖栫鎴�</ElButton>
+            <ElButton
+              v-auth="'delete'"
+              type="danger"
+              :disabled="selectedRows.length === 0"
+              @click="handleBatchDelete"
+              v-ripple
+            >
+              鎵归噺鍒犻櫎
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+
+      <TenantInitDialog
+        v-model:visible="initDialogVisible"
+        :tenant-data="currentInitTenantData"
+        @submit="handleInitSubmit"
+      />
+
+      <TenantEditDialog
+        v-model:visible="editDialogVisible"
+        :tenant-data="currentEditTenantData"
+        @submit="handleEditSubmit"
+      />
+
+      <TenantDetailDrawer
+        v-model:visible="detailDrawerVisible"
+        :loading="detailLoading"
+        :detail-data="detailData"
+      />
+    </ElCard>
+  </div>
+</template>
+
+<script setup>
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useAuth } from '@/hooks/core/useAuth'
+  import { useTable } from '@/hooks/core/useTable'
+  import {
+    fetchDeleteTenant,
+    fetchGetTenantDetail,
+    fetchInitTenant,
+    fetchTenantPage,
+    fetchUpdateTenant
+  } from '@/api/system-manage'
+  import TenantDetailDrawer from './modules/tenant-detail-drawer.vue'
+  import TenantEditDialog from './modules/tenant-edit-dialog.vue'
+  import TenantInitDialog from './modules/tenant-init-dialog.vue'
+  import { createTenantTableColumns } from './tenantTable.columns'
+  import {
+    buildTenantEditDialogModel,
+    buildTenantInitDialogModel,
+    buildTenantInitPayload,
+    buildTenantPageQueryParams,
+    buildTenantSearchParams,
+    buildTenantUpdatePayload,
+    createTenantSearchState,
+    getTenantPaginationKey,
+    getTenantStatusOptions,
+    normalizeTenantListRow
+  } from './tenantPage.helpers'
+
+  defineOptions({ name: 'Tenant' })
+
+  const { hasAuth } = useAuth()
+  const searchForm = ref(createTenantSearchState())
+  const selectedRows = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const initDialogVisible = ref(false)
+  const editDialogVisible = ref(false)
+  const currentInitTenantData = ref(buildTenantInitDialogModel())
+  const currentEditTenantData = ref(buildTenantEditDialogModel())
+  let handleDeleteAction = null
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴峰悕绉版垨鏍囪瘑'
+      }
+    },
+    {
+      label: '绉熸埛鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴峰悕绉�'
+      }
+    },
+    {
+      label: '绉熸埛鏍囪瘑',
+      key: 'flag',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴锋爣璇�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: getTenantStatusOptions()
+      }
+    }
+  ])
+
+  async function openDetail(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      detailData.value = normalizeTenantListRow(await fetchGetTenantDetail(row.id))
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇绉熸埛璇︽儏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function openEditDialog(row) {
+    try {
+      currentEditTenantData.value = buildTenantEditDialogModel(await fetchGetTenantDetail(row.id))
+      editDialogVisible.value = true
+    } catch (error) {
+      ElMessage.error(error?.message || '鑾峰彇绉熸埛璇︽儏澶辫触')
+    }
+  }
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    getData,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    refreshCreate,
+    refreshUpdate,
+    refreshRemove
+  } = useTable({
+    core: {
+      apiFn: fetchTenantPage,
+      apiParams: buildTenantPageQueryParams(searchForm.value),
+      paginationKey: getTenantPaginationKey(),
+      columnsFactory: () =>
+        createTenantTableColumns({
+          handleView: openDetail,
+          handleEdit: openEditDialog,
+          handleDelete: hasAuth('delete') ? (row) => handleDeleteAction?.(row) : null
+        })
+    },
+    transform: {
+      dataTransformer: (records) => {
+        if (!Array.isArray(records)) {
+          return []
+        }
+        return records.map((item) => normalizeTenantListRow(item))
+      }
+    }
+  })
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function showInitDialog() {
+    currentInitTenantData.value = buildTenantInitDialogModel()
+    initDialogVisible.value = true
+  }
+
+  async function handleInitSubmit(formData) {
+    try {
+      await fetchInitTenant(buildTenantInitPayload(formData))
+      ElMessage.success('绉熸埛鍒濆鍖栨垚鍔�')
+      initDialogVisible.value = false
+      await refreshCreate?.()
+    } catch (error) {
+      ElMessage.error(error?.message || '绉熸埛鍒濆鍖栧け璐�')
+    }
+  }
+
+  async function handleEditSubmit(formData) {
+    try {
+      await fetchUpdateTenant(buildTenantUpdatePayload(formData))
+      ElMessage.success('淇敼鎴愬姛')
+      editDialogVisible.value = false
+      await refreshUpdate?.()
+    } catch (error) {
+      ElMessage.error(error?.message || '淇敼澶辫触')
+    }
+  }
+
+  async function handleDelete(record) {
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佸垹闄ょ鎴枫��${record?.name || record?.id}銆嶅悧锛焋, '鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteTenant(record.id)
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      await refreshRemove?.()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鍒犻櫎澶辫触')
+      }
+    }
+  }
+  handleDeleteAction = handleDelete
+
+  async function handleBatchDelete() {
+    if (!selectedRows.value.length) return
+    const ids = selectedRows.value
+      .map((item) => item.id)
+      .filter((id) => id !== void 0 && id !== null)
+    if (!ids.length) return
+
+    try {
+      await ElMessageBox.confirm(`纭畾瑕佹壒閲忓垹闄ら�変腑鐨� ${ids.length} 涓鎴峰悧锛焋, '鎵归噺鍒犻櫎纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchDeleteTenant(ids.join(','))
+      ElMessage.success('鎵归噺鍒犻櫎鎴愬姛')
+      selectedRows.value = []
+      await refreshRemove?.()
+    } catch (error) {
+      if (error !== 'cancel') {
+        ElMessage.error(error?.message || '鎵归噺鍒犻櫎澶辫触')
+      }
+    }
+  }
+
+  function handleSearch(params) {
+    replaceSearchParams(buildTenantSearchParams(params))
+    getData()
+  }
+
+  function handleReset() {
+    Object.assign(searchForm.value, createTenantSearchState())
+    resetSearchParams()
+  }
+</script>
diff --git a/rsf-design/src/views/system/tenant/modules/tenant-detail-drawer.vue b/rsf-design/src/views/system/tenant/modules/tenant-detail-drawer.vue
new file mode 100644
index 0000000..c8f0247
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/modules/tenant-detail-drawer.vue
@@ -0,0 +1,39 @@
+<template>
+  <ElDrawer
+    :model-value="visible"
+    title="绉熸埛璇︽儏"
+    size="560px"
+    @update:model-value="handleVisibleChange"
+  >
+    <ElSkeleton :loading="loading" animated :rows="10">
+      <ElDescriptions :column="1" border>
+        <ElDescriptionsItem label="绉熸埛鍚嶇О">{{ displayData.name || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="绉熸埛鏍囪瘑">{{ displayData.flag || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="绠$悊鍛�">{{ displayData.rootLabel || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">
+          <ElTag :type="displayData.statusType" effect="light">{{ displayData.statusText || '--' }}</ElTag>
+        </ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ displayData.updateTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ displayData.createTimeText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ displayData.memo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </ElSkeleton>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { normalizeTenantListRow } from '../tenantPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    loading: { type: Boolean, default: false },
+    detailData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+  const displayData = computed(() => normalizeTenantListRow(props.detailData))
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/system/tenant/modules/tenant-edit-dialog.vue b/rsf-design/src/views/system/tenant/modules/tenant-edit-dialog.vue
new file mode 100644
index 0000000..048c2f7
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/modules/tenant-edit-dialog.vue
@@ -0,0 +1,149 @@
+<template>
+  <ElDialog
+    title="缂栬緫绉熸埛"
+    :model-value="visible"
+    width="760px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="100px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTenantEditDialogModel,
+    createTenantEditFormState,
+    getTenantFlagPattern,
+    getTenantStatusOptions
+  } from '../tenantPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    tenantData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTenantEditFormState())
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ョ鎴峰悕绉�', trigger: 'blur' }],
+    flag: [
+      { required: true, message: '璇疯緭鍏ョ鎴锋爣璇�', trigger: 'blur' },
+      {
+        pattern: getTenantFlagPattern(),
+        message: '绉熸埛鏍囪瘑浠呮敮鎸� 3-20 浣嶈嫳鏂囧瓧姣�',
+        trigger: 'blur'
+      }
+    ]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '绉熸埛鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴峰悕绉�'
+      }
+    },
+    {
+      label: '绉熸埛鏍囪瘑',
+      key: 'flag',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴锋爣璇�'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        options: getTenantStatusOptions(),
+        placeholder: '璇烽�夋嫨鐘舵��'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createTenantEditFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildTenantEditDialogModel(props.tenantData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.tenantData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/tenant/modules/tenant-init-dialog.vue b/rsf-design/src/views/system/tenant/modules/tenant-init-dialog.vue
new file mode 100644
index 0000000..8909609
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/modules/tenant-init-dialog.vue
@@ -0,0 +1,219 @@
+<template>
+  <ElDialog
+    title="鍒濆鍖栫鎴�"
+    :model-value="visible"
+    width="860px"
+    align-center
+    @update:model-value="handleCancel"
+    @closed="handleClosed"
+  >
+    <div class="mb-5 rounded-xl border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] px-4 py-3">
+      <p class="text-sm text-[var(--art-gray-700)]">鍒涘缓绉熸埛鏃朵細鍚屾椂鍒濆鍖栫鎴风鐞嗗憳璐﹀彿銆�</p>
+    </div>
+
+    <ArtForm
+      ref="formRef"
+      v-model="form"
+      :items="formItems"
+      :rules="rules"
+      :span="12"
+      :gutter="20"
+      label-width="110px"
+      :show-reset="false"
+      :show-submit="false"
+    />
+
+    <template #footer>
+      <span class="dialog-footer">
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾鍒濆鍖�</ElButton>
+      </span>
+    </template>
+  </ElDialog>
+</template>
+
+<script setup>
+  import ArtForm from '@/components/core/forms/art-form/index.vue'
+  import {
+    buildTenantInitDialogModel,
+    createTenantInitFormState,
+    getTenantFlagPattern,
+    getTenantPasswordPattern,
+    getTenantUsernamePattern
+  } from '../tenantPage.helpers'
+
+  const props = defineProps({
+    visible: { type: Boolean, default: false },
+    tenantData: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible', 'submit'])
+  const formRef = ref()
+  const form = reactive(createTenantInitFormState())
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: '璇疯緭鍏ョ鎴峰悕绉�', trigger: 'blur' }],
+    flag: [
+      { required: true, message: '璇疯緭鍏ョ鎴锋爣璇�', trigger: 'blur' },
+      {
+        pattern: getTenantFlagPattern(),
+        message: '绉熸埛鏍囪瘑浠呮敮鎸� 3-20 浣嶈嫳鏂囧瓧姣�',
+        trigger: 'blur'
+      }
+    ],
+    username: [
+      { required: true, message: '璇疯緭鍏ョ鐞嗗憳璐﹀彿', trigger: 'blur' },
+      {
+        pattern: getTenantUsernamePattern(),
+        message: '绠$悊鍛樿处鍙蜂粎鏀寔 3-20 浣嶅瓧姣嶆垨鏁板瓧',
+        trigger: 'blur'
+      }
+    ],
+    email: [
+      {
+        type: 'email',
+        message: '璇疯緭鍏ユ纭殑閭鍦板潃',
+        trigger: 'blur'
+      }
+    ],
+    password: [
+      { required: true, message: '璇疯緭鍏ュ垵濮嬪寲瀵嗙爜', trigger: 'blur' },
+      {
+        pattern: getTenantPasswordPattern(),
+        message: '瀵嗙爜闇�涓� 6-13 浣嶄笖鍚屾椂鍖呭惈瀛楁瘝鍜屾暟瀛�',
+        trigger: 'blur'
+      }
+    ],
+    confirmPassword: [
+      { required: true, message: '璇峰啀娆¤緭鍏ュ垵濮嬪寲瀵嗙爜', trigger: 'blur' },
+      {
+        validator: (_rule, value, callback) => {
+          if (value !== form.password) {
+            callback(new Error('涓ゆ杈撳叆鐨勫瘑鐮佷笉涓�鑷�'))
+            return
+          }
+          callback()
+        },
+        trigger: 'blur'
+      }
+    ]
+  }))
+
+  const formItems = computed(() => [
+    {
+      label: '绉熸埛鍚嶇О',
+      key: 'name',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鎴峰悕绉�'
+      }
+    },
+    {
+      label: '绉熸埛鏍囪瘑',
+      key: 'flag',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏� 3-20 浣嶈嫳鏂囨爣璇�'
+      }
+    },
+    {
+      label: '绠$悊鍛樿处鍙�',
+      key: 'username',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鐞嗗憳璐﹀彿'
+      }
+    },
+    {
+      label: '绠$悊鍛橀偖绠�',
+      key: 'email',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ鐞嗗憳閭'
+      }
+    },
+    {
+      label: '鍒濆鍖栧瘑鐮�',
+      key: 'password',
+      type: 'input',
+      props: {
+        type: 'password',
+        showPassword: true,
+        placeholder: '璇疯緭鍏ュ垵濮嬪寲瀵嗙爜'
+      }
+    },
+    {
+      label: '纭瀵嗙爜',
+      key: 'confirmPassword',
+      type: 'input',
+      props: {
+        type: 'password',
+        showPassword: true,
+        placeholder: '璇峰啀娆¤緭鍏ュ垵濮嬪寲瀵嗙爜'
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      span: 24,
+      props: {
+        type: 'textarea',
+        rows: 3,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  function resetForm() {
+    Object.assign(form, createTenantInitFormState())
+    formRef.value?.clearValidate?.()
+  }
+
+  function loadFormData() {
+    Object.assign(form, buildTenantInitDialogModel(props.tenantData))
+  }
+
+  async function handleSubmit() {
+    if (!formRef.value) return
+    try {
+      await formRef.value.validate()
+      emit('submit', { ...form })
+    } catch {
+      return
+    }
+  }
+
+  function handleCancel() {
+    emit('update:visible', false)
+  }
+
+  function handleClosed() {
+    resetForm()
+  }
+
+  watch(
+    () => props.visible,
+    (visible) => {
+      if (visible) {
+        loadFormData()
+        nextTick(() => formRef.value?.clearValidate?.())
+      }
+    },
+    { immediate: true }
+  )
+
+  watch(
+    () => props.tenantData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+</script>
diff --git a/rsf-design/src/views/system/tenant/tenantPage.helpers.js b/rsf-design/src/views/system/tenant/tenantPage.helpers.js
new file mode 100644
index 0000000..8f57ea0
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/tenantPage.helpers.js
@@ -0,0 +1,148 @@
+const STATUS_OPTIONS = [
+  { label: '姝e父', value: 1 },
+  { label: '绂佺敤', value: 0 }
+]
+
+const TENANT_FLAG_PATTERN = /^[A-Za-z]{3,20}$/
+const TENANT_USERNAME_PATTERN = /^[A-Za-z0-9]{3,20}$/
+const TENANT_PASSWORD_PATTERN = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d.]{6,13}$/
+
+export function createTenantSearchState() {
+  return {
+    condition: '',
+    name: '',
+    flag: '',
+    status: ''
+  }
+}
+
+export function createTenantInitFormState() {
+  return {
+    name: '',
+    flag: '',
+    username: '',
+    email: '',
+    password: '',
+    confirmPassword: '',
+    memo: ''
+  }
+}
+
+export function createTenantEditFormState() {
+  return {
+    id: null,
+    name: '',
+    flag: '',
+    status: 1,
+    memo: ''
+  }
+}
+
+export function getTenantPaginationKey() {
+  return {
+    current: 'current',
+    size: 'pageSize'
+  }
+}
+
+export function getTenantStatusOptions() {
+  return STATUS_OPTIONS
+}
+
+export function getTenantStatusMeta(status) {
+  return Number(status) === 1
+    ? { text: '姝e父', type: 'success', bool: true }
+    : { text: '绂佺敤', type: 'danger', bool: false }
+}
+
+export function getTenantFlagPattern() {
+  return TENANT_FLAG_PATTERN
+}
+
+export function getTenantUsernamePattern() {
+  return TENANT_USERNAME_PATTERN
+}
+
+export function getTenantPasswordPattern() {
+  return TENANT_PASSWORD_PATTERN
+}
+
+export function buildTenantSearchParams(params = {}) {
+  return {
+    condition: String(params.condition || '').trim(),
+    name: String(params.name || '').trim(),
+    flag: String(params.flag || '').trim(),
+    ...(params.status !== '' && params.status !== null && params.status !== undefined
+      ? { status: Number(params.status) }
+      : {})
+  }
+}
+
+export function buildTenantPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildTenantSearchParams(params)
+  }
+}
+
+export function buildTenantInitDialogModel(record = {}) {
+  return {
+    ...createTenantInitFormState(),
+    name: record.name || '',
+    flag: record.flag || '',
+    username: record.username || '',
+    email: record.email || '',
+    password: '',
+    confirmPassword: '',
+    memo: record.memo || ''
+  }
+}
+
+export function buildTenantEditDialogModel(record = {}) {
+  return {
+    ...createTenantEditFormState(),
+    ...(record.id ? { id: Number(record.id) } : {}),
+    name: record.name || '',
+    flag: record.flag || '',
+    status: record.status !== undefined && record.status !== null ? Number(record.status) : 1,
+    memo: record.memo || ''
+  }
+}
+
+export function buildTenantInitPayload(formData = {}) {
+  return {
+    name: String(formData.name || '').trim(),
+    flag: String(formData.flag || '').trim(),
+    username: String(formData.username || '').trim(),
+    ...(String(formData.email || '').trim() ? { email: String(formData.email || '').trim() } : {}),
+    password: String(formData.password || '').trim(),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function buildTenantUpdatePayload(formData = {}) {
+  return {
+    ...(formData.id ? { id: Number(formData.id) } : {}),
+    name: String(formData.name || '').trim(),
+    flag: String(formData.flag || '').trim(),
+    status: Number(formData.status ?? 1),
+    memo: String(formData.memo || '').trim()
+  }
+}
+
+export function normalizeTenantListRow(record = {}) {
+  const statusMeta = getTenantStatusMeta(record.status)
+  return {
+    ...record,
+    name: record.name || '',
+    flag: record.flag || '',
+    memo: record.memo || '',
+    rootLabel: record['root$'] || record.root || '',
+    statusText: record['status$'] || statusMeta.text,
+    statusType: statusMeta.type,
+    statusBool: record.statusBool ?? statusMeta.bool,
+    updateTimeText: record['updateTime$'] || record.updateTime || '',
+    createTimeText: record['createTime$'] || record.createTime || ''
+  }
+}
diff --git a/rsf-design/src/views/system/tenant/tenantTable.columns.js b/rsf-design/src/views/system/tenant/tenantTable.columns.js
new file mode 100644
index 0000000..2c1fcd1
--- /dev/null
+++ b/rsf-design/src/views/system/tenant/tenantTable.columns.js
@@ -0,0 +1,80 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createTenantTableColumns({ handleView, handleEdit, handleDelete }) {
+  return [
+    {
+      prop: 'name',
+      label: '绉熸埛鍚嶇О',
+      minWidth: 180,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'flag',
+      label: '绉熸埛鏍囪瘑',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'rootLabel',
+      label: '绠$悊鍛�',
+      minWidth: 120,
+      formatter: (row) => row.rootLabel || '-'
+    },
+    {
+      prop: 'status',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) => h(ElTag, { type: row.statusType, effect: 'light' }, () => row.statusText || '-')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.updateTimeText || '-'
+    },
+    {
+      prop: 'createTimeText',
+      label: '鍒涘缓鏃堕棿',
+      minWidth: 180,
+      formatter: (row) => row.createTimeText || '-'
+    },
+    {
+      prop: 'memo',
+      label: '澶囨敞',
+      minWidth: 180,
+      showOverflowTooltip: true,
+      formatter: (row) => row.memo || '-'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: handleDelete ? 160 : 120,
+      align: 'right',
+      formatter: (row) => {
+        const buttons = [
+          h(ArtButtonTable, {
+            type: 'view',
+            onClick: () => handleView(row)
+          }),
+          h(ArtButtonTable, {
+            type: 'edit',
+            onClick: () => handleEdit(row)
+          })
+        ]
+
+        if (handleDelete) {
+          buttons.push(
+            h(ArtButtonTable, {
+              type: 'delete',
+              onClick: () => handleDelete(row)
+            })
+          )
+        }
+
+        return h('div', { class: 'flex justify-end' }, buttons)
+      }
+    }
+  ]
+}
diff --git a/rsf-design/src/views/system/user/index.vue b/rsf-design/src/views/system/user/index.vue
index 5adc86b..8800cf1 100644
--- a/rsf-design/src/views/system/user/index.vue
+++ b/rsf-design/src/views/system/user/index.vue
@@ -47,6 +47,7 @@
 
 <script setup>
   import request from '@/utils/http'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
   import {
     fetchDeleteUser,
     fetchGetDeptTree,
@@ -247,7 +248,19 @@
 
   const loadLookups = async () => {
     try {
-      const [roles, depts] = await Promise.all([fetchGetRoleOptions({}), fetchGetDeptTree({})])
+      const lookupPayload = await guardRequestWithMessage(
+        Promise.all([fetchGetRoleOptions({}), fetchGetDeptTree({})]),
+        null,
+        {
+          timeoutMessage: '鐢ㄦ埛椤靛瓧鍏稿姞杞借秴鏃讹紝宸插仠姝㈢瓑寰�'
+        }
+      )
+      if (!lookupPayload) {
+        roleOptions.value = []
+        deptTreeOptions.value = []
+        return
+      }
+      const [roles, depts] = lookupPayload
       roleOptions.value = normalizeRoleOptions(roles)
       deptTreeOptions.value = normalizeDeptTreeOptions(depts)
     } catch (error) {
diff --git a/rsf-design/src/views/work/check-out-bound/checkOutBoundPage.helpers.js b/rsf-design/src/views/work/check-out-bound/checkOutBoundPage.helpers.js
new file mode 100644
index 0000000..7865046
--- /dev/null
+++ b/rsf-design/src/views/work/check-out-bound/checkOutBoundPage.helpers.js
@@ -0,0 +1,178 @@
+const CHECK_OUT_BOUND_REPORT_TITLE = '鐩樼偣鍑哄簱浠诲姟鐢熸垚'
+
+function normalizeText(value) {
+  return typeof value === 'string' ? value.trim() : value
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function filterParams(params = {}, ignoredKeys = []) {
+  return Object.fromEntries(
+    Object.entries(params)
+      .filter(([key, value]) => {
+        if (ignoredKeys.includes(key)) return false
+        if (value === undefined || value === null) return false
+        if (typeof value === 'string' && value.trim() === '') return false
+        return true
+      })
+      .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])
+  )
+}
+
+function getCheckOutBoundStatusMeta(status) {
+  const normalized = Number(status)
+  if (normalized === 1) {
+    return { text: '姝e父', type: 'success' }
+  }
+  if (normalized === 0) {
+    return { text: '鍐荤粨', type: 'danger' }
+  }
+  return { text: '--', type: 'info' }
+}
+
+export function createCheckOutBoundSearchState() {
+  return {
+    condition: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    trackCode: ''
+  }
+}
+
+export function buildCheckOutBoundSearchParams(params = {}) {
+  return filterParams(params, ['current', 'pageSize', 'size'])
+}
+
+export function buildCheckOutBoundPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...filterParams(params, ['current', 'pageSize', 'size'])
+  }
+}
+
+function normalizeTaskItemRow(row = {}) {
+  const {
+    statusText,
+    statusTagType,
+    createTimeText,
+    updateTimeText,
+    actionList,
+    operation,
+    ...rest
+  } = row
+  return {
+    ...rest
+  }
+}
+
+export function buildCheckOutBoundTaskPayload({ siteNo = '', items = [], memo = '' } = {}) {
+  const payload = {
+    siteNo: normalizeText(siteNo),
+    items: Array.isArray(items) ? items.map((item) => normalizeTaskItemRow(item)) : []
+  }
+
+  const normalizedMemo = normalizeText(memo)
+  if (normalizedMemo) {
+    payload.memo = normalizedMemo
+  }
+
+  return payload
+}
+
+export function normalizeCheckOutBoundRow(row = {}) {
+  const statusMeta = getCheckOutBoundStatusMeta(row.status ?? row.status$)
+  return {
+    ...row,
+    locCode: normalizeText(row.locCode || row.locCode$ || row.locId || '--') || '--',
+    matnrCode: normalizeText(row.matnrCode || row.matnrCode$ || '--') || '--',
+    maktx: normalizeText(row.maktx || row.maktx$ || '--') || '--',
+    batch: normalizeText(row.batch || row.batch$ || '--') || '--',
+    splrBatch: normalizeText(row.splrBatch || row.splrBatch$ || '--') || '--',
+    unit: normalizeText(row.unit || row.unit$ || '--') || '--',
+    trackCode: normalizeText(row.trackCode || row.trackCode$ || '--') || '--',
+    siteNo: normalizeText(row.siteNo || row.siteNo$ || '--') || '--',
+    memo: normalizeText(row.memo || row.memo$ || '--') || '--',
+    anfme: normalizeNumber(row.anfme),
+    workQty: normalizeNumber(row.workQty),
+    qty: normalizeNumber(row.qty),
+    statusText: row.statusText || row.status$ || statusMeta.text,
+    statusTagType: row.statusTagType || statusMeta.type,
+    createTimeText: row.createTimeText || row.createTime$ || row.createTime || '--',
+    updateTimeText: row.updateTimeText || row.updateTime$ || row.updateTime || '--'
+  }
+}
+
+export function resolveCheckOutBoundSiteOptions(source = []) {
+  const records = Array.isArray(source)
+    ? source
+    : Array.isArray(source?.records)
+      ? source.records
+      : Array.isArray(source?.list)
+        ? source.list
+        : Array.isArray(source?.data)
+          ? source.data
+          : Array.isArray(source?.data?.records)
+            ? source.data.records
+            : Array.isArray(source?.data?.list)
+              ? source.data.list
+              : Array.isArray(source?.data?.data)
+                ? source.data.data
+                : []
+  const seen = new Set()
+
+  return records
+    .map((item) => {
+      if (typeof item === 'string' || typeof item === 'number') {
+        const value = String(item).trim()
+        return value ? { value, label: value } : null
+      }
+
+      const value = normalizeText(item?.siteNo ?? item?.site ?? item?.code ?? item?.value ?? item?.id)
+      if (!value) return null
+
+      return {
+        value,
+        label: normalizeText(item?.name ?? item?.siteName ?? item?.label ?? item?.code ?? value)
+      }
+    })
+    .filter(Boolean)
+    .filter((item) => {
+      if (seen.has(item.value)) return false
+      seen.add(item.value)
+      return true
+    })
+}
+
+export function getCheckOutBoundActionList() {
+  return [
+    {
+      key: 'view',
+      label: '鏌ョ湅鏄庣粏',
+      icon: 'ri:eye-line'
+    }
+  ]
+}
+
+export function buildCheckOutBoundReportMeta(rows = []) {
+  return {
+    reportTitle: CHECK_OUT_BOUND_REPORT_TITLE,
+    reportDate: new Date().toLocaleDateString('zh-CN'),
+    printedAt: new Date().toLocaleString('zh-CN', { hour12: false }),
+    count: Array.isArray(rows) ? rows.length : 0
+  }
+}
+
+export function buildCheckOutBoundPrintRows(records = []) {
+  return Array.isArray(records) ? records.map((item) => normalizeCheckOutBoundRow(item)) : []
+}
+
+export { CHECK_OUT_BOUND_REPORT_TITLE, getCheckOutBoundStatusMeta }
diff --git a/rsf-design/src/views/work/check-out-bound/checkOutBoundTable.columns.js b/rsf-design/src/views/work/check-out-bound/checkOutBoundTable.columns.js
new file mode 100644
index 0000000..f83c917
--- /dev/null
+++ b/rsf-design/src/views/work/check-out-bound/checkOutBoundTable.columns.js
@@ -0,0 +1,94 @@
+import { h } from 'vue'
+import { ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import { getCheckOutBoundActionList } from './checkOutBoundPage.helpers'
+
+export function createCheckOutBoundTableColumns({ handleActionClick } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 140,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'batch',
+      label: '搴撳瓨鎵规',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'anfme',
+      label: '鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '瀹屾垚鏁伴噺',
+      width: 100,
+      align: 'right'
+    },
+    {
+      prop: 'trackCode',
+      label: '杩借釜鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(ElTag, { type: row.statusTagType || 'info', effect: 'light' }, () => row.statusText || '--')
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 110,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: getCheckOutBoundActionList(row),
+          onClick: (item) => handleActionClick?.(item, row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/src/views/work/check-out-bound/index.vue b/rsf-design/src/views/work/check-out-bound/index.vue
new file mode 100644
index 0000000..4e46fe1
--- /dev/null
+++ b/rsf-design/src/views/work/check-out-bound/index.vue
@@ -0,0 +1,310 @@
+<template>
+  <div class="check-out-bound-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card mb-4">
+      <div class="flex flex-wrap items-center gap-3">
+        <div class="text-sm text-[var(--art-text-gray-600)]">
+          宸查�夋嫨 <span class="font-medium text-[var(--art-text-gray-800)]">{{ selectedRows.length }}</span> 鏉″簱浣嶇墿鏂�
+        </div>
+        <ElSelect
+          v-model="siteNo"
+          class="min-w-56"
+          filterable
+          clearable
+          placeholder="璇烽�夋嫨绔欑偣"
+          :loading="siteLoading"
+          :disabled="siteLoading"
+        >
+          <ElOption
+            v-for="item in siteOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </ElSelect>
+        <ElButton
+          type="primary"
+          :loading="generateLoading"
+          :disabled="!canGenerateTask"
+          @click="handleGenerateTask"
+        >
+          鐢熸垚鐩樼偣鍑哄簱浠诲姟
+        </ElButton>
+        <ElButton :disabled="selectedRows.length === 0" @click="clearSelection">娓呯┖閫夋嫨</ElButton>
+      </div>
+    </ElCard>
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData" />
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <CheckOutBoundDetailDrawer
+      v-model:visible="detailDrawerVisible"
+      :loading="detailLoading"
+      :detail="detailData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref, watch } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    buildCheckOutBoundPageQueryParams,
+    buildCheckOutBoundTaskPayload,
+    createCheckOutBoundSearchState,
+    normalizeCheckOutBoundRow,
+    resolveCheckOutBoundSiteOptions
+  } from './checkOutBoundPage.helpers'
+  import {
+    fetchCheckOutBoundPage,
+    fetchCheckOutBoundSites,
+    fetchGenerateCheckOutBoundTask,
+    fetchGetCheckOutBoundDetail
+  } from '@/api/check-out-bound'
+  import CheckOutBoundDetailDrawer from './modules/check-out-bound-detail-drawer.vue'
+  import { createCheckOutBoundTableColumns } from './checkOutBoundTable.columns'
+
+  defineOptions({ name: 'CheckOutBound' })
+
+  const searchForm = ref(createCheckOutBoundSearchState())
+  const selectedRows = ref([])
+  const siteOptions = ref([])
+  const siteLoading = ref(false)
+  const siteNo = ref('')
+  const memo = ref('')
+  const detailDrawerVisible = ref(false)
+  const detailLoading = ref(false)
+  const detailData = ref({})
+  const generateLoading = ref(false)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�/鐗╂枡缂栫爜/鐗╂枡鍚嶇О/鎵规'
+      }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'locCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '搴撳瓨鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱瀛樻壒娆�'
+      }
+    },
+    {
+      label: '杩借釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ拷韪爜'
+      }
+    }
+  ])
+
+  const {
+    columns,
+    columnChecks,
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange: handleTableSizeChange,
+    handleCurrentChange: handleTableCurrentChange,
+    refreshData
+  } = useTable({
+    core: {
+      apiFn: fetchCheckOutBoundPage,
+      apiParams: buildCheckOutBoundPageQueryParams(searchForm.value),
+      columnsFactory: () =>
+        createCheckOutBoundTableColumns({
+          handleActionClick
+        })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((item) => normalizeCheckOutBoundRow(item)) : [])
+    }
+  })
+
+  const canGenerateTask = computed(() => selectedRows.value.length > 0 && Boolean(siteNo.value) && !generateLoading.value)
+
+  watch(
+    data,
+    () => {
+      selectedRows.value = []
+    },
+    { deep: false }
+  )
+
+  onMounted(loadSiteOptions)
+
+  async function loadSiteOptions() {
+    siteLoading.value = true
+    try {
+      const response = await guardRequestWithMessage(fetchCheckOutBoundSites(), [], {
+        timeoutMessage: '绔欑偣閫夐」鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      siteOptions.value = resolveCheckOutBoundSiteOptions(response)
+      if (siteOptions.value.length > 0 && !siteOptions.value.some((item) => item.value === siteNo.value)) {
+        siteNo.value = ''
+      }
+    } catch (error) {
+      siteOptions.value = []
+      ElMessage.error(error?.message || '鑾峰彇绔欑偣閫夐」澶辫触')
+    } finally {
+      siteLoading.value = false
+    }
+  }
+
+  async function openDetailDrawer(row) {
+    detailDrawerVisible.value = true
+    detailLoading.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetCheckOutBoundDetail(row.id), {}, {
+        timeoutMessage: '鐩樼偣鍑哄簱鏄庣粏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeCheckOutBoundRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇鐩樼偣鍑哄簱鏄庣粏澶辫触')
+    } finally {
+      detailLoading.value = false
+    }
+  }
+
+  async function handleActionClick(action, row) {
+    if (action?.disabled) return
+    if (action.key === 'view') {
+      await openDetailDrawer(row)
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function clearSelection() {
+    selectedRows.value = []
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    selectedRows.value = []
+    replaceSearchParams(buildCheckOutBoundPageQueryParams(searchForm.value))
+    refreshData()
+  }
+
+  function handleReset() {
+    searchForm.value = createCheckOutBoundSearchState()
+    selectedRows.value = []
+    siteNo.value = ''
+    memo.value = ''
+    resetSearchParams()
+  }
+
+  function handleSizeChange(size) {
+    selectedRows.value = []
+    handleTableSizeChange(size)
+  }
+
+  function handleCurrentChange(current) {
+    selectedRows.value = []
+    handleTableCurrentChange(current)
+  }
+
+  async function handleGenerateTask() {
+    if (!siteNo.value) {
+      ElMessage.warning('璇烽�夋嫨绔欑偣')
+      return
+    }
+    if (selectedRows.value.length === 0) {
+      ElMessage.warning('璇烽�夋嫨搴撲綅鐗╂枡')
+      return
+    }
+
+    try {
+      await ElMessageBox.confirm(
+        `纭畾鍩轰簬褰撳墠鎵�閫� ${selectedRows.value.length} 鏉″簱浣嶇墿鏂欑敓鎴愮洏鐐瑰嚭搴撲换鍔″悧锛焋,
+        '鐢熸垚纭',
+        {
+          confirmButtonText: '纭畾',
+          cancelButtonText: '鍙栨秷',
+          type: 'warning'
+        }
+      )
+
+      generateLoading.value = true
+      const payload = buildCheckOutBoundTaskPayload({
+        siteNo: siteNo.value,
+        memo: memo.value,
+        items: selectedRows.value
+      })
+      await fetchGenerateCheckOutBoundTask(payload)
+      ElMessage.success('浠诲姟鐢熸垚鎴愬姛')
+      selectedRows.value = []
+      refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') return
+      ElMessage.error(error?.message || '浠诲姟鐢熸垚澶辫触')
+    } finally {
+      generateLoading.value = false
+    }
+  }
+</script>
diff --git a/rsf-design/src/views/work/check-out-bound/modules/check-out-bound-detail-drawer.vue b/rsf-design/src/views/work/check-out-bound/modules/check-out-bound-detail-drawer.vue
new file mode 100644
index 0000000..0a01550
--- /dev/null
+++ b/rsf-design/src/views/work/check-out-bound/modules/check-out-bound-detail-drawer.vue
@@ -0,0 +1,65 @@
+<template>
+  <ElDrawer
+    v-model="visibleProxy"
+    title="鐩樼偣鍑哄簱鏄庣粏"
+    size="760px"
+    destroy-on-close
+    append-to-body
+  >
+    <ElScrollbar class="h-full">
+      <ElSkeleton :loading="loading" animated :rows="10">
+        <div class="space-y-4">
+          <ElDescriptions :column="2" border>
+            <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="搴撳瓨鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="瀹屾垚鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="杩借釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="绔欑偣">{{ detail.siteNo || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鐘舵��">
+              <ElTag :type="detail.statusTagType || 'info'" effect="light">
+                {{ detail.statusText || '--' }}
+              </ElTag>
+            </ElDescriptionsItem>
+            <ElDescriptionsItem label="澶囨敞" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鍒涘缓鏃堕棿">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
+            <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+          </ElDescriptions>
+        </div>
+      </ElSkeleton>
+    </ElScrollbar>
+  </ElDrawer>
+</template>
+
+<script setup>
+  import { computed } from 'vue'
+
+  defineOptions({ name: 'CheckOutBoundDetailDrawer' })
+
+  const props = defineProps({
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  const visibleProxy = computed({
+    get: () => props.visible,
+    set: (value) => emit('update:visible', value)
+  })
+</script>
diff --git a/rsf-design/src/views/work/out-bound/index.vue b/rsf-design/src/views/work/out-bound/index.vue
new file mode 100644
index 0000000..9b92b07
--- /dev/null
+++ b/rsf-design/src/views/work/out-bound/index.vue
@@ -0,0 +1,368 @@
+<template>
+  <div class="out-bound-page art-full-height">
+    <ArtSearchBar
+      v-model="searchForm"
+      :items="searchItems"
+      :showExpand="true"
+      @search="handleSearch"
+      @reset="handleReset"
+    />
+
+    <ElCard class="art-table-card">
+      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
+        <template #left>
+          <ElSpace wrap>
+            <span class="text-sm text-[var(--art-text-secondary)]">宸查�夊簱瀛� {{ selectedBrowseRows.length }} 鏉�</span>
+            <ElButton type="primary" :disabled="selectedBrowseRows.length === 0" @click="handleCollectSelectedRows">
+              鍔犲叆浠诲姟绡�
+            </ElButton>
+          </ElSpace>
+        </template>
+      </ArtTableHeader>
+
+      <ArtTable
+        :loading="loading"
+        :data="data"
+        :columns="columns"
+        :pagination="pagination"
+        @selection-change="handleSelectionChange"
+        @pagination:size-change="handleSizeChange"
+        @pagination:current-change="handleCurrentChange"
+      />
+    </ElCard>
+
+    <ElCard class="art-table-card">
+      <template #header>
+        <div class="flex flex-wrap items-center justify-between gap-4">
+          <div>
+            <div class="text-base font-medium text-[var(--art-text-primary)]">浠诲姟绡�</div>
+            <div class="text-xs text-[var(--art-text-secondary)]">
+              {{ basketSummaryText }}
+            </div>
+          </div>
+
+          <ElSpace wrap>
+            <ElSelect
+              v-model="taskForm.siteNo"
+              filterable
+              clearable
+              placeholder="璇烽�夋嫨鍑哄簱绔欑偣"
+              style="min-width: 220px"
+            >
+              <ElOption v-for="option in siteOptions" :key="option.value" :label="option.label" :value="option.value" />
+            </ElSelect>
+            <ElInput v-model="taskForm.memo" clearable placeholder="璇疯緭鍏ヤ换鍔″娉�" style="min-width: 220px" />
+            <ElButton type="primary" :disabled="basketRows.length === 0" :loading="generating" @click="handleGenerateTask">
+              鐢熸垚浠诲姟
+            </ElButton>
+            <ElButton :disabled="basketRows.length === 0" @click="handleClearBasket">娓呯┖浠诲姟绡�</ElButton>
+          </ElSpace>
+        </div>
+      </template>
+
+      <ArtTable :loading="false" :data="basketRows" :columns="basketColumns" :pagination="basketPagination" />
+    </ElCard>
+
+    <OutBoundDetailDrawer v-model:visible="detailDrawerVisible" :detail="detailData" />
+  </div>
+</template>
+
+<script setup>
+  import { computed, onMounted, ref } from 'vue'
+  import { ElMessage, ElMessageBox } from 'element-plus'
+  import { useTable } from '@/hooks/core/useTable'
+  import { useTableColumns } from '@/hooks/core/useTableColumns'
+  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
+  import {
+    fetchGenerateOutboundTask,
+    fetchGetOutboundInventoryDetail,
+    fetchOutboundInventoryPage,
+    fetchOutboundSiteList,
+    normalizeOutboundTaskPayload
+  } from '@/api/out-bound'
+  import OutBoundDetailDrawer from './modules/out-bound-detail-drawer.vue'
+  import { createOutBoundInventoryTableColumns, createOutBoundBasketTableColumns } from './outBoundTable.columns'
+  import {
+    buildOutBoundGenerateTaskPayload,
+    buildOutBoundPageQueryParams,
+    createOutBoundSearchState,
+    createOutBoundTaskState,
+    getOutBoundValidationMessage,
+    mergeOutBoundBasketRows,
+    normalizeOutBoundBasketRows,
+    normalizeOutBoundRow,
+    normalizeOutBoundSiteOptions
+  } from './outBoundPage.helpers'
+
+  defineOptions({ name: 'OutBound' })
+
+  const searchForm = ref(createOutBoundSearchState())
+  const taskForm = ref(createOutBoundTaskState())
+  const selectedBrowseRows = ref([])
+  const basketRows = ref([])
+  const siteOptions = ref([])
+  const detailDrawerVisible = ref(false)
+  const detailData = ref({})
+  const generating = ref(false)
+
+  const searchItems = computed(() => [
+    {
+      label: '鍏抽敭瀛�',
+      key: 'condition',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣�/鐗╂枡/鎵规鍏抽敭瀛�'
+      }
+    },
+    {
+      label: '搴撲綅缂栫爜',
+      key: 'locCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡缂栫爜',
+      key: 'matnrCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�'
+      }
+    },
+    {
+      label: '鐗╂枡鍚嶇О',
+      key: 'maktx',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�'
+      }
+    },
+    {
+      label: '鎵规',
+      key: 'batch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ユ壒娆�'
+      }
+    },
+    {
+      label: '渚涘簲鍟嗘壒娆�',
+      key: 'splrBatch',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鎵规'
+      }
+    },
+    {
+      label: '杩借釜鐮�',
+      key: 'trackCode',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ヨ拷韪爜'
+      }
+    },
+    {
+      label: '鐘舵��',
+      key: 'status',
+      type: 'select',
+      props: {
+        clearable: true,
+        options: [
+          { label: '姝e父', value: 1 },
+          { label: '鍐荤粨', value: 0 }
+        ]
+      }
+    },
+    {
+      label: '澶囨敞',
+      key: 'memo',
+      type: 'input',
+      props: {
+        clearable: true,
+        placeholder: '璇疯緭鍏ュ娉�'
+      }
+    }
+  ])
+
+  const basketPagination = computed(() => ({
+    current: 1,
+    size: Math.max(basketRows.value.length, 1),
+    total: basketRows.value.length
+  }))
+
+  const basketSummaryText = computed(() => {
+    const totalQty = basketRows.value.reduce((sum, row) => sum + Number(row.outQty || 0), 0)
+    return `宸查�� ${basketRows.value.length} 鏉★紝鍚堣鍑哄簱鏁伴噺 ${totalQty}`
+  })
+
+  function buildBasketRowKey(row = {}) {
+    return row.id ?? `${row.locId || ''}-${row.matnrCode || ''}-${row.batch || ''}-${row.trackCode || ''}`
+  }
+
+  async function openDetailDrawer(row) {
+    detailDrawerVisible.value = true
+    try {
+      const detail = await guardRequestWithMessage(fetchGetOutboundInventoryDetail(row.id), {}, {
+        timeoutMessage: '搴撳瓨鏄庣粏璇︽儏鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+      })
+      detailData.value = normalizeOutBoundRow(detail)
+    } catch (error) {
+      detailDrawerVisible.value = false
+      detailData.value = {}
+      ElMessage.error(error?.message || '鑾峰彇搴撳瓨鏄庣粏璇︽儏澶辫触')
+    }
+  }
+
+  function handleSelectionChange(rows) {
+    selectedBrowseRows.value = Array.isArray(rows) ? rows : []
+  }
+
+  function handleCollectSelectedRows(rows = selectedBrowseRows.value) {
+    if (!Array.isArray(rows) || rows.length === 0) {
+      ElMessage.warning('璇峰厛閫夋嫨搴撳瓨鏄庣粏')
+      return
+    }
+    basketRows.value = mergeOutBoundBasketRows(basketRows.value, rows)
+    selectedBrowseRows.value = []
+    ElMessage.success(`宸插姞鍏� ${rows.length} 鏉″簱瀛樻槑缁哷)
+  }
+
+  function handleAddToBasket(rows) {
+    basketRows.value = mergeOutBoundBasketRows(basketRows.value, rows)
+  }
+
+  function handleRemoveBasketRow(row) {
+    const key = buildBasketRowKey(row)
+    basketRows.value = basketRows.value.filter((item) => buildBasketRowKey(item) !== key)
+  }
+
+  function handleOutQtyChange(row, value) {
+    const key = buildBasketRowKey(row)
+    basketRows.value = basketRows.value.map((item) => {
+      if (buildBasketRowKey(item) !== key) {
+        return item
+      }
+      const numericValue = Number(value ?? 0)
+      return {
+        ...item,
+        outQty: Number.isFinite(numericValue) ? numericValue : 0
+      }
+    })
+  }
+
+  function handleClearBasket() {
+    basketRows.value = []
+    taskForm.value = createOutBoundTaskState()
+  }
+
+  async function handleGenerateTask() {
+    const message = getOutBoundValidationMessage({
+      siteNo: taskForm.value.siteNo,
+      items: basketRows.value
+    })
+    if (message) {
+      ElMessage.warning(message)
+      return
+    }
+
+    try {
+      generating.value = true
+      await ElMessageBox.confirm('纭畾鏍规嵁褰撳墠浠诲姟绡敓鎴愬嚭搴撲换鍔″悧锛�', '鐢熸垚纭', {
+        confirmButtonText: '纭畾',
+        cancelButtonText: '鍙栨秷',
+        type: 'warning'
+      })
+      await fetchGenerateOutboundTask(
+        normalizeOutboundTaskPayload(
+          buildOutBoundGenerateTaskPayload({
+            siteNo: taskForm.value.siteNo,
+            memo: taskForm.value.memo,
+            items: normalizeOutBoundBasketRows(basketRows.value)
+          })
+        )
+      )
+      ElMessage.success('浠诲姟鐢熸垚鎴愬姛')
+      basketRows.value = []
+      taskForm.value = createOutBoundTaskState()
+      await refreshData()
+    } catch (error) {
+      if (error === 'cancel' || error?.message === 'cancel') {
+        return
+      }
+      ElMessage.error(error?.message || '鐢熸垚浠诲姟澶辫触')
+    } finally {
+      generating.value = false
+    }
+  }
+
+  const { columns, columnChecks } = useTableColumns(() =>
+    createOutBoundInventoryTableColumns({
+      handleViewDetail: openDetailDrawer,
+      handleAddToBasket
+    })
+  )
+
+  const basketColumns = computed(() =>
+    createOutBoundBasketTableColumns({
+      handleRemoveBasketRow,
+      handleOutQtyChange
+    })
+  )
+
+  const {
+    data,
+    loading,
+    pagination,
+    replaceSearchParams,
+    resetSearchParams,
+    handleSizeChange,
+    handleCurrentChange,
+    refreshData,
+    getData
+  } = useTable({
+    core: {
+      apiFn: fetchOutboundInventoryPage,
+      apiParams: buildOutBoundPageQueryParams(searchForm.value),
+      columnsFactory: () => createOutBoundInventoryTableColumns({
+        handleViewDetail: openDetailDrawer,
+        handleAddToBasket
+      })
+    },
+    transform: {
+      dataTransformer: (records) => (Array.isArray(records) ? records.map((record) => normalizeOutBoundRow(record)) : [])
+    }
+  })
+
+  async function loadSiteOptions() {
+    const records = await guardRequestWithMessage(fetchOutboundSiteList(), [], {
+      timeoutMessage: '鍑哄簱绔欑偣鍔犺浇瓒呮椂锛屽凡鍋滄绛夊緟'
+    })
+    siteOptions.value = normalizeOutBoundSiteOptions(records)
+  }
+
+  function handleSearch(params) {
+    searchForm.value = {
+      ...searchForm.value,
+      ...params
+    }
+    replaceSearchParams(buildOutBoundPageQueryParams(searchForm.value))
+    getData()
+  }
+
+  function handleReset() {
+    searchForm.value = createOutBoundSearchState()
+    resetSearchParams()
+  }
+
+  onMounted(async () => {
+    await loadSiteOptions()
+  })
+</script>
diff --git a/rsf-design/src/views/work/out-bound/modules/out-bound-detail-drawer.vue b/rsf-design/src/views/work/out-bound/modules/out-bound-detail-drawer.vue
new file mode 100644
index 0000000..7f14ecf
--- /dev/null
+++ b/rsf-design/src/views/work/out-bound/modules/out-bound-detail-drawer.vue
@@ -0,0 +1,41 @@
+<template>
+  <ElDrawer :model-value="visible" title="搴撳瓨鏄庣粏璇︽儏" size="72%" @update:model-value="handleVisibleChange">
+    <div class="flex h-full flex-col gap-4">
+      <ElDescriptions :column="3" border>
+        <ElDescriptionsItem label="搴撲綅缂栫爜">{{ detail.locCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡缂栫爜">{{ detail.matnrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐗╂枡鍚嶇О">{{ detail.maktx || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵规">{{ detail.batch || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟嗘壒娆�">{{ detail.splrBatch || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="杩借釜鐮�">{{ detail.trackCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍗曚綅">{{ detail.unit || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍙敤鏁伴噺">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鎵ц鏁伴噺">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鍑哄簱鏁伴噺">{{ detail.outQty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="瀹屾垚鏁伴噺">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鐘舵��">{{ detail.statusText || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="绔欑偣鍙�">{{ detail.siteNo || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+
+      <ElDescriptions :column="2" border>
+        <ElDescriptionsItem label="渚涘簲鍟嗙紪鐮�">{{ detail.splrCode || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="渚涘簲鍟嗗悕绉�">{{ detail.splrName || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="澶囨敞">{{ detail.memo || '--' }}</ElDescriptionsItem>
+        <ElDescriptionsItem label="鏇存柊鏃堕棿">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
+      </ElDescriptions>
+    </div>
+  </ElDrawer>
+</template>
+
+<script setup>
+  defineProps({
+    visible: { type: Boolean, default: false },
+    detail: { type: Object, default: () => ({}) }
+  })
+
+  const emit = defineEmits(['update:visible'])
+
+  function handleVisibleChange(visible) {
+    emit('update:visible', visible)
+  }
+</script>
diff --git a/rsf-design/src/views/work/out-bound/outBoundPage.helpers.js b/rsf-design/src/views/work/out-bound/outBoundPage.helpers.js
new file mode 100644
index 0000000..0c91f8e
--- /dev/null
+++ b/rsf-design/src/views/work/out-bound/outBoundPage.helpers.js
@@ -0,0 +1,239 @@
+const OUT_BOUND_STATUS_META = {
+  0: { label: '鍐荤粨', tagType: 'danger' },
+  1: { label: '姝e父', tagType: 'success' }
+}
+
+function normalizeText(value) {
+  return String(value ?? '').trim()
+}
+
+function normalizeOptionalText(value) {
+  const text = normalizeText(value)
+  return text === '-' ? '' : text
+}
+
+function normalizeNumber(value) {
+  if (value === '' || value === null || value === undefined) {
+    return 0
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : 0
+}
+
+function getStatusMeta(status, statusText) {
+  const fallback = OUT_BOUND_STATUS_META[Number(status)] || {
+    label: statusText || '-',
+    tagType: 'info'
+  }
+  return {
+    label: statusText || fallback.label,
+    tagType: fallback.tagType
+  }
+}
+
+function toNumberOrOriginal(value) {
+  if (value === '' || value === null || value === undefined) {
+    return value
+  }
+  const numericValue = Number(value)
+  return Number.isFinite(numericValue) ? numericValue : value
+}
+
+function normalizeItemOutQty(row = {}) {
+  const outQty = row.outQty !== undefined && row.outQty !== null && row.outQty !== ''
+    ? normalizeNumber(row.outQty)
+    : normalizeNumber(row.anfme)
+  return outQty < 0 ? 0 : outQty
+}
+
+function compactDefinedFields(record = {}) {
+  return Object.fromEntries(
+    Object.entries(record).filter(([, value]) => value !== undefined && value !== null && value !== '')
+  )
+}
+
+export function createOutBoundSearchState() {
+  return {
+    condition: '',
+    locCode: '',
+    matnrCode: '',
+    maktx: '',
+    batch: '',
+    splrBatch: '',
+    trackCode: '',
+    status: '',
+    memo: ''
+  }
+}
+
+export function createOutBoundTaskState() {
+  return {
+    siteNo: '',
+    memo: ''
+  }
+}
+
+export function buildOutBoundSearchParams(params = {}) {
+  const result = {}
+  ;['condition', 'locCode', 'matnrCode', 'maktx', 'batch', 'splrBatch', 'trackCode', 'memo'].forEach((key) => {
+    const value = normalizeText(params[key])
+    if (value) {
+      result[key] = value
+    }
+  })
+
+  if (params.status !== '' && params.status !== undefined && params.status !== null) {
+    result.status = Number(params.status)
+  }
+
+  return result
+}
+
+export function buildOutBoundPageQueryParams(params = {}) {
+  return {
+    current: params.current || 1,
+    pageSize: params.pageSize || params.size || 20,
+    ...buildOutBoundSearchParams(params)
+  }
+}
+
+export function normalizeOutBoundRow(record = {}) {
+  const statusMeta = getStatusMeta(record.status, record['status$'])
+  return {
+    ...record,
+    locCode: normalizeText(record.locCode) || '-',
+    matnrCode: normalizeText(record.matnrCode) || '-',
+    maktx: normalizeText(record.maktx) || '-',
+    trackCode: normalizeText(record.trackCode) || '-',
+    batch: normalizeText(record.batch) || '-',
+    splrBatch: normalizeText(record.splrBatch) || '-',
+    unit: normalizeText(record.unit) || '-',
+    anfme: normalizeNumber(record.anfme),
+    workQty: normalizeNumber(record.workQty),
+    qty: normalizeNumber(record.qty),
+    outQty: normalizeNumber(record.outQty),
+    statusText: statusMeta.label,
+    statusTagType: statusMeta.tagType,
+    createTimeText: normalizeText(record['createTime$'] || record.createTime) || '-',
+    updateTimeText: normalizeText(record['updateTime$'] || record.updateTime) || '-',
+    splrCode: normalizeText(record.splrCode) || '-',
+    splrName: normalizeText(record.splrName) || '-',
+    siteNo: normalizeText(record.siteNo) || '-',
+    memo: normalizeText(record.memo) || '-'
+  }
+}
+
+export function normalizeOutBoundBasketRows(rows = []) {
+  if (!Array.isArray(rows)) {
+    return []
+  }
+
+  const seen = new Set()
+  return rows
+    .map((row) => ({
+      ...normalizeOutBoundRow(row),
+      outQty: normalizeItemOutQty(row)
+    }))
+    .filter((row) => {
+      const key = row.id ?? `${row.locId || ''}-${row.matnrCode || ''}-${row.batch || ''}`
+      if (seen.has(key)) {
+        return false
+      }
+      seen.add(key)
+      return true
+    })
+}
+
+export function mergeOutBoundBasketRows(currentRows = [], incomingRows = []) {
+  const basket = new Map()
+
+  normalizeOutBoundBasketRows(currentRows).forEach((row) => {
+    const key = row.id ?? `${row.locId || ''}-${row.matnrCode || ''}-${row.batch || ''}`
+    basket.set(key, row)
+  })
+
+  normalizeOutBoundBasketRows(incomingRows).forEach((row) => {
+    const key = row.id ?? `${row.locId || ''}-${row.matnrCode || ''}-${row.batch || ''}`
+    if (!basket.has(key)) {
+      basket.set(key, row)
+      return
+    }
+    basket.set(key, {
+      ...basket.get(key),
+      ...row,
+      outQty: basket.get(key).outQty > 0 ? basket.get(key).outQty : row.outQty
+    })
+  })
+
+  return Array.from(basket.values())
+}
+
+export function buildOutBoundGenerateTaskPayload({ siteNo, memo = '', items = [] } = {}) {
+  return {
+    siteNo: normalizeOptionalText(siteNo),
+    memo: normalizeText(memo),
+    items: Array.isArray(items)
+      ? items.map((row) =>
+          compactDefinedFields({
+            ...row,
+            id: toNumberOrOriginal(row.id),
+            locId: toNumberOrOriginal(row.locId),
+            orderId: toNumberOrOriginal(row.orderId),
+            orderItemId: toNumberOrOriginal(row.orderItemId),
+            wkType: toNumberOrOriginal(row.wkType),
+            matnrId: toNumberOrOriginal(row.matnrId),
+            channel: toNumberOrOriginal(row.channel),
+            status: toNumberOrOriginal(row.status),
+            outQty: normalizeItemOutQty(row),
+            anfme: normalizeNumber(row.anfme),
+            workQty: normalizeNumber(row.workQty),
+            qty: normalizeNumber(row.qty),
+            siteNo: normalizeOptionalText(row.siteNo),
+            memo: normalizeOptionalText(row.memo)
+          })
+        )
+      : []
+  }
+}
+
+export function normalizeOutBoundSiteOptions(records = []) {
+  if (!Array.isArray(records)) {
+    return []
+  }
+
+  const seen = new Set()
+  return records
+    .map((record) => normalizeText(record.site))
+    .filter((site) => {
+      if (!site || seen.has(site)) {
+        return false
+      }
+      seen.add(site)
+      return true
+    })
+    .map((site) => ({
+      label: site,
+      value: site
+    }))
+}
+
+export function getOutBoundValidationMessage({ siteNo, items = [] } = {}) {
+  if (!normalizeText(siteNo)) {
+    return '璇烽�夋嫨鍑哄簱绔欑偣'
+  }
+  if (!Array.isArray(items) || items.length === 0) {
+    return '璇峰厛閫夋嫨搴撳瓨鏄庣粏'
+  }
+
+  const invalidRow = items.find((row) => normalizeItemOutQty(row) <= 0)
+  if (invalidRow) {
+    return `搴撳瓨鏄庣粏 ${invalidRow.locCode || invalidRow.matnrCode || invalidRow.id || ''} 鐨勫嚭搴撴暟閲忓繀椤诲ぇ浜� 0`
+  }
+
+  const overflowRow = items.find((row) => normalizeItemOutQty(row) + normalizeNumber(row.workQty) > normalizeNumber(row.anfme))
+  if (overflowRow) {
+    return `搴撳瓨鏄庣粏 ${overflowRow.locCode || overflowRow.matnrCode || overflowRow.id || ''} 鐨勫嚭搴撴暟閲忎笉鑳借秴杩囧彲鐢ㄦ暟閲廯
+  }
+
+  return ''
+}
diff --git a/rsf-design/src/views/work/out-bound/outBoundTable.columns.js b/rsf-design/src/views/work/out-bound/outBoundTable.columns.js
new file mode 100644
index 0000000..171d9d6
--- /dev/null
+++ b/rsf-design/src/views/work/out-bound/outBoundTable.columns.js
@@ -0,0 +1,203 @@
+import { h } from 'vue'
+import { ElInputNumber, ElTag } from 'element-plus'
+import ArtButtonMore from '@/components/core/forms/art-button-more/index.vue'
+import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
+
+export function createOutBoundInventoryTableColumns({ handleViewDetail, handleAddToBasket } = {}) {
+  return [
+    { type: 'selection', width: 48, align: 'center' },
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrBatch || '--'
+    },
+    {
+      prop: 'trackCode',
+      label: '杩借釜鐮�',
+      minWidth: 160,
+      showOverflowTooltip: true,
+      formatter: (row) => row.trackCode || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鍙敤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'qty',
+      label: '瀹屾垚鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'statusText',
+      label: '鐘舵��',
+      width: 100,
+      formatter: (row) =>
+        h(
+          ElTag,
+          { type: row.statusTagType || 'info', effect: 'light' },
+          () => row.statusText || '--'
+        )
+    },
+    {
+      prop: 'updateTimeText',
+      label: '鏇存柊鏃堕棿',
+      minWidth: 170,
+      showOverflowTooltip: true,
+      formatter: (row) => row.updateTimeText || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 170,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonMore, {
+          list: [
+            { key: 'view', label: '鏌ョ湅璇︽儏', icon: 'ri:eye-line' },
+            { key: 'add', label: '鍔犲叆浠诲姟', icon: 'ri:add-line' }
+          ],
+          onClick: (item) => {
+            if (item.key === 'view') handleViewDetail?.(row)
+            if (item.key === 'add') handleAddToBasket?.([row])
+          }
+        })
+    }
+  ]
+}
+
+export function createOutBoundBasketTableColumns({ handleRemoveBasketRow, handleOutQtyChange } = {}) {
+  return [
+    { type: 'globalIndex', label: '搴忓彿', width: 72, align: 'center' },
+    {
+      prop: 'locCode',
+      label: '搴撲綅缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.locCode || '--'
+    },
+    {
+      prop: 'matnrCode',
+      label: '鐗╂枡缂栫爜',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.matnrCode || '--'
+    },
+    {
+      prop: 'maktx',
+      label: '鐗╂枡鍚嶇О',
+      minWidth: 220,
+      showOverflowTooltip: true,
+      formatter: (row) => row.maktx || '--'
+    },
+    {
+      prop: 'batch',
+      label: '鎵规',
+      minWidth: 130,
+      showOverflowTooltip: true,
+      formatter: (row) => row.batch || '--'
+    },
+    {
+      prop: 'splrBatch',
+      label: '渚涘簲鍟嗘壒娆�',
+      minWidth: 150,
+      showOverflowTooltip: true,
+      formatter: (row) => row.splrBatch || '--'
+    },
+    {
+      prop: 'anfme',
+      label: '鍙敤鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'workQty',
+      label: '鎵ц鏁伴噺',
+      width: 110,
+      align: 'right'
+    },
+    {
+      prop: 'outQty',
+      label: '鍑哄簱鏁伴噺',
+      minWidth: 140,
+      align: 'right',
+      formatter: (row) =>
+        h(ElInputNumber, {
+          modelValue: Number(row.outQty ?? row.anfme ?? 0),
+          min: 0,
+          precision: 3,
+          controlsPosition: 'right',
+          style: 'width: 100%',
+          'onUpdate:modelValue': (value) => handleOutQtyChange?.(row, value)
+        })
+    },
+    {
+      prop: 'unit',
+      label: '鍗曚綅',
+      width: 90
+    },
+    {
+      prop: 'siteNo',
+      label: '绔欑偣鍙�',
+      minWidth: 140,
+      showOverflowTooltip: true,
+      formatter: (row) => row.siteNo || '--'
+    },
+    {
+      prop: 'operation',
+      label: '鎿嶄綔',
+      width: 90,
+      fixed: 'right',
+      formatter: (row) =>
+        h(ArtButtonTable, {
+          icon: 'ri:delete-bin-5-line',
+          title: '绉婚櫎',
+          onClick: () => handleRemoveBasketRow?.(row)
+        })
+    }
+  ]
+}
diff --git a/rsf-design/tests/auth-contract.test.mjs b/rsf-design/tests/auth-contract.test.mjs
deleted file mode 100644
index 6dd0d4e..0000000
--- a/rsf-design/tests/auth-contract.test.mjs
+++ /dev/null
@@ -1,123 +0,0 @@
-import assert from 'node:assert/strict'
-import { register } from 'node:module'
-import test from 'node:test'
-
-register(
-  'data:text/javascript,export function resolve(specifier, context, nextResolve){ if(specifier===\'@/utils/http\'){ return { shortCircuit:true, url:\'data:text/javascript,export default { post(options){ globalThis.__authHttpCalls.push({ method:"post", options }); return Promise.resolve({ accessToken:"abc", refreshToken:"ref", user:{ id:1 } }) }, get(options){ globalThis.__authHttpCalls.push({ method:"get", options }); return Promise.resolve({ userId:1, roles:[{ code:"R_ADMIN", name:"绠$悊鍛�" }], authorities:[{ authority:"system:menu:save" },{ authority:"system:menu:update" }] }) } }\' } } return nextResolve(specifier, context) }',
-  import.meta.url
-)
-
-globalThis.__authHttpCalls = []
-
-const {
-  buildLoginPayload,
-  fetchGetUserInfo,
-  fetchLogin,
-  normalizeLoginResponse,
-  normalizeUserInfo
-} = await import('../src/api/auth.js')
-
-test('buildLoginPayload matches the rsf-server login contract', () => {
-  assert.deepEqual(buildLoginPayload({ username: 'demo', password: '123456' }), {
-    username: 'demo',
-    password: '123456'
-  })
-})
-
-test('normalizeLoginResponse extracts the real token fields', () => {
-  assert.deepEqual(
-    normalizeLoginResponse({
-      code: 200,
-      data: { accessToken: 'abc', user: { id: 1 } }
-    }),
-    { accessToken: 'abc', refreshToken: '', user: { id: 1 } }
-  )
-})
-
-test('normalizeLoginResponse also handles an inner data object', () => {
-  assert.deepEqual(
-    normalizeLoginResponse({
-      accessToken: 'abc',
-      refreshToken: 'ref',
-      user: { id: 1 }
-    }),
-    { accessToken: 'abc', refreshToken: 'ref', user: { id: 1 } }
-  )
-})
-
-test('normalizeLoginResponse accepts the old backend accessToken shape', () => {
-  const result = normalizeLoginResponse({
-    code: 200,
-    data: { accessToken: 'token-1', refreshToken: '', user: { username: 'admin' } }
-  })
-
-  assert.equal(result.accessToken, 'token-1')
-})
-
-test('normalizeLoginResponse keeps missing accessToken empty', () => {
-  const result = normalizeLoginResponse({
-    code: 200,
-    data: { refreshToken: 'ref', user: { username: 'admin' } }
-  })
-
-  assert.equal(result.accessToken, '')
-})
-
-test('normalizeUserInfo returns roles and buttons arrays safely', () => {
-  assert.deepEqual(normalizeUserInfo({ userId: 1, roles: ['R_ADMIN'], buttons: ['add'] }), {
-    userId: 1,
-    roles: ['R_ADMIN'],
-    buttons: ['add']
-  })
-})
-
-test('normalizeUserInfo derives role codes and button aliases from rsf-server auth payloads', () => {
-  assert.deepEqual(
-    normalizeUserInfo({
-      userId: 1,
-      roles: [{ code: 'R_SUPER', name: '瓒呯骇绠$悊鍛�' }],
-      authorities: [{ authority: 'system:menu:save' }, { authority: 'system:menu:update' }]
-    }),
-    {
-      userId: 1,
-      roles: ['R_SUPER'],
-      authorities: [{ authority: 'system:menu:save' }, { authority: 'system:menu:update' }],
-      buttons: ['system:menu:save', 'system:menu:update']
-    }
-  )
-})
-
-test('fetchLogin keeps supporting legacy userName input at the boundary', async () => {
-  const result = await fetchLogin({ userName: 'demo', password: '123456' })
-
-  assert.deepEqual(globalThis.__authHttpCalls.at(-1), {
-    method: 'post',
-    options: {
-      url: '/login',
-      params: { username: 'demo', password: '123456' }
-    }
-  })
-  assert.deepEqual(result, {
-    token: 'abc',
-    accessToken: 'abc',
-    refreshToken: 'ref',
-    user: { id: 1 }
-  })
-})
-
-test('fetchGetUserInfo normalizes the returned user payload', async () => {
-  const result = await fetchGetUserInfo()
-
-  assert.deepEqual(globalThis.__authHttpCalls.at(-1), {
-    method: 'get',
-    options: {
-      url: '/auth/user'
-    }
-  })
-  assert.deepEqual(result, {
-    userId: 1,
-    roles: ['R_ADMIN'],
-    authorities: [{ authority: 'system:menu:save' }, { authority: 'system:menu:update' }],
-    buttons: ['system:menu:save', 'system:menu:update']
-  })
-})
diff --git a/rsf-design/tests/backend-api-base.test.mjs b/rsf-design/tests/backend-api-base.test.mjs
deleted file mode 100644
index ded0f78..0000000
--- a/rsf-design/tests/backend-api-base.test.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFile } from 'node:fs/promises'
-import path from 'node:path'
-import test from 'node:test'
-
-const projectRoot = path.resolve(import.meta.dirname, '..')
-
-async function readProjectFile(relativePath) {
-  return readFile(path.join(projectRoot, relativePath), 'utf8')
-}
-
-test('development env routes API requests through the rsf-server context path', async () => {
-  const envContent = await readProjectFile('.env.development')
-
-  assert.match(envContent, /VITE_API_URL\s*=\s*\/rsf-server\b/)
-  assert.match(envContent, /VITE_API_PROXY_URL\s*=\s*http:\/\/127\.0\.0\.1:8085\b/)
-})
-
-test('production env keeps the rsf-server context path as the API base', async () => {
-  const envContent = await readProjectFile('.env.production')
-
-  assert.match(envContent, /VITE_API_URL\s*=\s*\/rsf-server\b/)
-})
-
-test('vite dev server proxies the rsf-server context path to the backend target', async () => {
-  const viteConfig = await readProjectFile('vite.config.js')
-
-  assert.match(viteConfig, /'\/rsf-server'\s*:\s*\{/)
-  assert.match(viteConfig, /target:\s*VITE_API_PROXY_URL/)
-})
diff --git a/rsf-design/tests/backend-menu-adapter.test.mjs b/rsf-design/tests/backend-menu-adapter.test.mjs
deleted file mode 100644
index 76d6c49..0000000
--- a/rsf-design/tests/backend-menu-adapter.test.mjs
+++ /dev/null
@@ -1,208 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import test from 'node:test'
-import { RoutesAlias } from '../src/router/routesAlias.js'
-
-test('adapts a backend menu tree using route for path and name for title', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 10,
-      name: 'menu.system',
-      parentId: 0,
-      path: '1',
-      route: '/system',
-      type: 0,
-      children: [
-        {
-          id: 11,
-          name: 'menu.userLogin',
-          parentId: 10,
-          path: '1,10',
-          route: 'user-login',
-          component: 'userLogin',
-          type: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].path, 'system')
-  assert.equal(result[0].meta.title, '绯荤粺璁剧疆')
-  assert.equal(result[0].children[0].path, 'user-login')
-  assert.equal(result[0].children[0].meta.title, '鐧诲綍鏃ュ織')
-  assert.equal(result[0].children[0].component, '/system/user-login')
-})
-
-test('keeps backend leaves by deriving component paths from the route hierarchy when no alias exists', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 1,
-      name: 'menu.system',
-      parentId: 0,
-      path: '1',
-      route: '/system',
-      type: 0,
-      children: [
-        {
-          id: 2,
-          name: 'menu.userLogin',
-          parentId: 1,
-          path: '1,2',
-          route: 'user-login',
-          component: 'userLogin',
-          type: 0
-        },
-        {
-          id: 3,
-          name: 'menu.host',
-          parentId: 1,
-          path: '1,3',
-          route: 'host',
-          component: 'host',
-          type: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].children.length, 2)
-  assert.equal(result[0].children[0].path, 'user-login')
-  assert.equal(result[0].children[0].meta.title, '鐧诲綍鏃ュ織')
-  assert.equal(result[0].children[0].component, '/system/user-login')
-  assert.equal(result[0].children[1].path, 'host')
-  assert.equal(result[0].children[1].meta.title, '鏈烘瀯绠$悊')
-  assert.equal(result[0].children[1].component, '/system/host')
-})
-
-test('filters non-menu nodes while keeping plain string titles unchanged', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 20,
-      name: '绯荤粺璁剧疆',
-      parentId: 0,
-      path: '20',
-      route: '/system',
-      type: 0,
-      children: [
-        {
-          id: 21,
-          name: 'menu.user',
-          parentId: 20,
-          path: '20,21',
-          route: 'user',
-          component: 'user',
-          type: 0
-        },
-        {
-          id: 22,
-          name: 'system:user:add',
-          parentId: 20,
-          path: '20,22',
-          route: 'user/add',
-          component: 'user',
-          type: 1
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].meta.title, '绯荤粺璁剧疆')
-  assert.equal(result[0].children.length, 1)
-  assert.equal(result[0].children[0].id, '21')
-  assert.equal(result[0].children[0].component, '/system/user')
-})
-
-test('preserves absolute backend child routes instead of nesting them under the parent path', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 5318,
-      name: 'AI绠$悊涓績',
-      parentId: 0,
-      route: '/AI',
-      type: 0,
-      children: [
-        {
-          id: 422,
-          name: 'menu.aiParam',
-          parentId: 5318,
-          route: '/system/aiParam',
-          component: 'aiParam',
-          type: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].path, 'AI')
-  assert.equal(result[0].children[0].path, '/system/aiParam')
-  assert.equal(result[0].children[0].meta.title, 'AI 鍙傛暟')
-  assert.equal(result[0].children[0].component, '/system/aiParam')
-})
-
-test('keeps directory menus as layout nodes when they only group child routes', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 5318,
-      name: 'AI绠$悊涓績',
-      parentId: 0,
-      route: '/AI',
-      type: 0,
-      children: [
-        {
-          id: 422,
-          name: 'menu.aiParam',
-          parentId: 5318,
-          route: '/system/aiParam',
-          component: 'aiParam',
-          type: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].component, RoutesAlias.Layout)
-})
-
-test('maps legacy backend icon names into renderable Iconify icons', async () => {
-  const { adaptBackendMenuTree } = await import('../src/router/adapters/backendMenuAdapter.js')
-  const result = adaptBackendMenuTree([
-    {
-      id: 1,
-      name: 'AI绠$悊涓績',
-      route: '/ai',
-      icon: 'SmartToy',
-      type: 0,
-      children: [
-        {
-          id: 2,
-          name: 'menu.userLogin',
-          route: 'user-login',
-          component: 'userLogin',
-          icon: 'Token',
-          type: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(result[0].meta.icon, 'ri:robot-2-line')
-  assert.equal(result[0].children[0].meta.icon, 'ri:key-2-line')
-})
-
-test('phase 1 resource mappings resolve to existing views and translatable menu labels', async () => {
-  const { PHASE_1_COMPONENTS } = await import('../src/router/adapters/backendMenuAdapter.js')
-
-  Object.entries(PHASE_1_COMPONENTS).forEach(([resourceKey, viewPath]) => {
-    const normalizedPath = viewPath.replace(/^\//, '')
-    const componentWithIndex = new URL(`../src/views/${normalizedPath}/index.vue`, import.meta.url)
-    const singleFileComponent = new URL(`../src/views/${normalizedPath}.vue`, import.meta.url)
-    const componentExists = fs.existsSync(componentWithIndex) || fs.existsSync(singleFileComponent)
-
-    assert.equal(componentExists, true, `${resourceKey} should resolve to an existing view`)
-  })
-})
diff --git a/rsf-design/tests/basic-info-loc-page-contract.test.mjs b/rsf-design/tests/basic-info-loc-page-contract.test.mjs
deleted file mode 100644
index a7f5dee..0000000
--- a/rsf-design/tests/basic-info-loc-page-contract.test.mjs
+++ /dev/null
@@ -1,266 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFileSync } from 'node:fs'
-import test from 'node:test'
-
-const pageModuleUrl = new URL('../src/views/basic-info/loc/index.vue', import.meta.url)
-const helpersModuleUrl = new URL('../src/views/basic-info/loc/locPage.helpers.js', import.meta.url)
-const columnsModuleUrl = new URL('../src/views/basic-info/loc/locTable.columns.js', import.meta.url)
-const apiModuleUrl = new URL('../src/api/loc.js', import.meta.url)
-const backendMenuAdapterUrl = new URL('../src/router/adapters/backendMenuAdapter.js', import.meta.url)
-const staticRoutesUrl = new URL('../src/router/routes/staticRoutes.js', import.meta.url)
-
-test('loc api exposes the dedicated basic-info backend contract', async () => {
-  const apiSource = readFileSync(apiModuleUrl, 'utf8')
-
-  assert.match(apiSource, /fetchLocPage/)
-  assert.match(apiSource, /fetchGetLocDetail/)
-  assert.match(apiSource, /fetchGetLocMany/)
-  assert.match(apiSource, /fetchSaveLoc/)
-  assert.match(apiSource, /fetchUpdateLoc/)
-  assert.match(apiSource, /fetchDeleteLoc/)
-  assert.match(apiSource, /fetchLocQuery/)
-  assert.match(apiSource, /fetchLocTypeList/)
-  assert.match(apiSource, /fetchWarehouseList/)
-  assert.match(apiSource, /fetchWarehouseAreasList/)
-  assert.match(apiSource, /fetchExportLocReport/)
-  assert.match(apiSource, /url:\s*'\/loc\/page'/)
-  assert.match(apiSource, /url:\s*'\/loc\/save'/)
-  assert.match(apiSource, /url:\s*'\/loc\/update'/)
-  assert.match(apiSource, /url:\s*`\/loc\/remove\/\$\{normalizeIds\(ids\)\}`/)
-  assert.match(apiSource, /url:\s*'\/locType\/list'/)
-})
-
-test('loc helpers keep page, save and detail contracts stable', async () => {
-  const helpers = await import(helpersModuleUrl)
-
-  assert.deepEqual(helpers.createLocSearchState(), {
-    condition: '',
-    warehouseId: '',
-    areaId: '',
-    code: '',
-    useStatus: '',
-    row: '',
-    col: '',
-    lev: '',
-    channel: '',
-    status: '',
-    barcode: '',
-    memo: ''
-  })
-
-  assert.deepEqual(helpers.getLocPaginationKey(), {
-    current: 'current',
-    size: 'pageSize'
-  })
-
-  assert.deepEqual(
-    helpers.buildLocPageQueryParams({
-      current: 2,
-      pageSize: 30,
-      condition: '  A鍖�  ',
-      warehouseId: '8',
-      areaId: '9',
-      code: ' LOC-01 ',
-      useStatus: ' O ',
-      row: '1',
-      col: '2',
-      lev: '3',
-      channel: '4',
-      status: '1',
-      barcode: ' BOX-01 ',
-      memo: '  memo  '
-    }),
-    {
-      current: 2,
-      pageSize: 30,
-      condition: 'A鍖�',
-      warehouseId: 8,
-      areaId: 9,
-      code: 'LOC-01',
-      useStatus: 'O',
-      row: 1,
-      col: 2,
-      lev: 3,
-      channel: 4,
-      status: 1,
-      barcode: 'BOX-01',
-      memo: 'memo'
-    }
-  )
-
-  assert.deepEqual(
-    helpers.buildLocSavePayload({
-      id: '8',
-      version: '3',
-      warehouseId: '5',
-      areaId: '6',
-      code: ' LOC-01 ',
-      typeIds: ['1', '2'],
-      flagLogic: 1,
-      fucAtrrs: ' 鍔熻兘 ',
-      barcode: ' BOX-01 ',
-      unit: ' 绠� ',
-      length: '1.2',
-      height: '2.3',
-      width: '3.4',
-      row: '1',
-      col: '2',
-      lev: '3',
-      channel: '4',
-      maxParts: '10',
-      maxPack: '20',
-      useStatus: 'F',
-      flagLabelMange: 0,
-      locAttrs: ' 灞炴�� ',
-      status: '',
-      memo: ' 澶囨敞 '
-    }),
-    {
-      id: 8,
-      version: 3,
-      warehouseId: 5,
-      areaId: 6,
-      code: 'LOC-01',
-      typeIds: [1, 2],
-      flagLogic: 1,
-      fucAtrrs: '鍔熻兘',
-      barcode: 'BOX-01',
-      unit: '绠�',
-      length: 1.2,
-      height: 2.3,
-      width: 3.4,
-      row: 1,
-      col: 2,
-      lev: 3,
-      channel: 4,
-      maxParts: 10,
-      maxPack: 20,
-      useStatus: 'F',
-      flagLabelMange: 0,
-      locAttrs: '灞炴��',
-      status: 1,
-      memo: '澶囨敞'
-    }
-  )
-
-  const detail = helpers.normalizeLocDetailRecord({
-    id: 1,
-    warehouseId: 4,
-    warehouseId$: '涓讳粨',
-    areaId: 8,
-    areaId$: 'A鍖�',
-    type: '1,2',
-    typeIds$: '楂樺簱浣�,涓簱浣�',
-    useStatus: 'O',
-    status: 1,
-    flagLogic: 1,
-    flagLabelMange: 0,
-    code: ' LOC-01 ',
-    barcode: ' BOX-01 ',
-    unit: ' 绠� ',
-    row: 1,
-    col: 2,
-    lev: 3,
-    channel: 4,
-    length: 1.2,
-    height: 2.3,
-    width: 3.4,
-    maxParts: 10,
-    maxPack: 20,
-    memo: ' memo ',
-    createBy$: 'ROOT',
-    updateBy$: 'ROOT',
-    createTime$: '2026-03-30 10:00:00',
-    updateTime$: '2026-03-30 10:10:00'
-  })
-
-  assert.equal(detail.warehouseName, '涓讳粨')
-  assert.equal(detail.areaName, 'A鍖�')
-  assert.equal(detail.typeIdsText, '楂樺簱浣�,涓簱浣�')
-  assert.equal(detail.useStatusText, '绌哄簱')
-  assert.equal(detail.statusText, '姝e父')
-  assert.equal(detail.flagLogicText, '鏄�')
-  assert.equal(detail.flagLabelMangeText, '鍚�')
-  assert.equal(detail.memo, 'memo')
-
-  assert.deepEqual(helpers.buildLocPrintRows([{ id: 2, code: 'LOC-02', useStatus: 'F', status: 0 }]), [
-    {
-      id: 2,
-      code: 'LOC-02',
-      useStatus: 'F',
-      status: 0,
-      warehouseName: '',
-      areaName: '',
-      typeIds: [],
-      typeIdsText: '',
-      barcode: '',
-      unit: '',
-      fucAtrrs: '',
-      locAttrs: '',
-      memo: '',
-      flagLogicText: '--',
-      flagLabelMangeText: '--',
-      useStatusText: '鍦ㄥ簱',
-      useStatusType: 'primary',
-      statusText: '鍐荤粨',
-      statusType: 'danger',
-      statusBool: false,
-      row: void 0,
-      col: void 0,
-      lev: void 0,
-      channel: void 0,
-      length: void 0,
-      height: void 0,
-      width: void 0,
-      maxParts: void 0,
-      maxPack: void 0,
-      createByText: '',
-      createTimeText: '',
-      updateByText: '',
-      updateTimeText: ''
-    }
-  ])
-
-  assert.deepEqual(helpers.buildLocReportMeta({ count: 12 }), {
-    reportTitle: '搴撲綅鎶ヨ〃',
-    reportDate: undefined,
-    printedAt: undefined,
-    operator: undefined,
-    count: 12,
-    reportStyle: {
-      titleAlign: 'center',
-      titleLevel: 'strong',
-      orientation: 'portrait',
-      density: 'compact',
-      showSequence: true
-    }
-  })
-})
-
-test('loc page skeleton uses ArtDesignPro list page structure', async () => {
-  const pageSource = readFileSync(pageModuleUrl, 'utf8')
-  const columnsSource = readFileSync(columnsModuleUrl, 'utf8')
-
-  assert.match(pageSource, /ArtSearchBar/)
-  assert.match(pageSource, /ArtTableHeader/)
-  assert.match(pageSource, /ListExportPrint/)
-  assert.match(pageSource, /LocDialog/)
-  assert.match(pageSource, /LocDetailDrawer/)
-  assert.match(pageSource, /useCrudPage/)
-  assert.match(pageSource, /usePrintExportPage/)
-  assert.match(columnsSource, /label:\s*'搴撲綅鍙�'/)
-  assert.match(columnsSource, /label:\s*'浠撳簱'/)
-  assert.match(columnsSource, /label:\s*'搴撳尯'/)
-  assert.match(columnsSource, /label:\s*'搴撲綅绫诲瀷'/)
-  assert.match(columnsSource, /label:\s*'浣跨敤鐘舵��'/)
-})
-
-test('backend menu adapter and static routes expose the loc page', async () => {
-  const backendMenuAdapterSource = readFileSync(backendMenuAdapterUrl, 'utf8')
-  const staticRoutesSource = readFileSync(staticRoutesUrl, 'utf8')
-
-  assert.match(backendMenuAdapterSource, /loc:\s*'\/basic-info\/loc'/)
-  assert.match(staticRoutesSource, /path:\s*'loc'/)
-  assert.match(staticRoutesSource, /title:\s*'menu\.loc'/)
-  assert.match(staticRoutesSource, /basic-info\/loc\/index\.vue/)
-})
diff --git a/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs b/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs
deleted file mode 100644
index 280c44e..0000000
--- a/rsf-design/tests/basic-info-warehouse-areas-page-contract.test.mjs
+++ /dev/null
@@ -1,177 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFile } from 'node:fs/promises'
-import { fileURLToPath } from 'node:url'
-import test from 'node:test'
-
-const apiPath = new URL('../src/api/warehouse-areas.js', import.meta.url)
-const helpersPath = new URL('../src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js', import.meta.url)
-const columnsPath = new URL('../src/views/basic-info/warehouse-areas/warehouseAreasTable.columns.js', import.meta.url)
-const pagePath = new URL('../src/views/basic-info/warehouse-areas/index.vue', import.meta.url)
-const backendMenuAdapterPath = new URL('../src/router/adapters/backendMenuAdapter.js', import.meta.url)
-const staticRoutesPath = new URL('../src/router/routes/staticRoutes.js', import.meta.url)
-
-test('warehouse areas api uses real backend endpoints', async () => {
-  const apiSource = await readFile(fileURLToPath(apiPath), 'utf8')
-
-  assert.match(apiSource, /fetchWarehouseAreasPage/)
-  assert.match(apiSource, /fetchWarehouseAreasDetail/)
-  assert.match(apiSource, /fetchWarehouseAreasMany/)
-  assert.match(apiSource, /fetchSaveWarehouseAreas/)
-  assert.match(apiSource, /fetchUpdateWarehouseAreas/)
-  assert.match(apiSource, /fetchDeleteWarehouseAreas/)
-  assert.match(apiSource, /fetchWarehouseAreasQuery/)
-  assert.match(apiSource, /fetchExportWarehouseAreasReport/)
-  assert.match(apiSource, /url: '\/warehouseAreas\/page'/)
-  assert.match(apiSource, /url: '\/warehouseAreas\/list'/)
-  assert.match(apiSource, /url: `\/warehouseAreas\/many\/\$\{normalizeIds\(ids\)\}`/)
-  assert.match(apiSource, /url: '\/warehouseAreas\/save'/)
-  assert.match(apiSource, /url: '\/warehouseAreas\/update'/)
-  assert.match(apiSource, /url: `\/warehouseAreas\/remove\/\$\{normalizeIds\(ids\)\}`/)
-  assert.match(apiSource, /url: '\/warehouseAreas\/query'/)
-  assert.match(apiSource, /warehouseAreas\/export/)
-})
-
-test('warehouse areas helpers keep page, save and detail contracts stable', async () => {
-  const helpers = await import('../src/views/basic-info/warehouse-areas/warehouseAreasPage.helpers.js')
-
-  assert.deepEqual(helpers.createWarehouseAreasSearchState(), {
-    condition: '',
-    warehouseId: '',
-    code: '',
-    name: '',
-    type: '',
-    shipperId: '',
-    supplierId: '',
-    status: '',
-    memo: ''
-  })
-
-  assert.deepEqual(helpers.getWarehouseAreasPaginationKey(), {
-    current: 'current',
-    size: 'pageSize'
-  })
-
-  assert.deepEqual(
-    helpers.buildWarehouseAreasPageQueryParams({
-      current: 2,
-      pageSize: 30,
-      condition: '  搴撳尯A  ',
-      warehouseId: 8,
-      code: '  A01 ',
-      name: '  涓�妤煎簱鍖� ',
-      type: '  normal ',
-      shipperId: 5,
-      supplierId: 7,
-      status: 1,
-      memo: '  memo  '
-    }),
-    {
-      current: 2,
-      pageSize: 30,
-      condition: '搴撳尯A',
-      warehouseId: 8,
-      code: 'A01',
-      name: '涓�妤煎簱鍖�',
-      type: 'normal',
-      shipperId: 5,
-      supplierId: 7,
-      status: 1,
-      memo: 'memo'
-    }
-  )
-
-  assert.deepEqual(
-    helpers.buildWarehouseAreasSavePayload({
-      id: '9',
-      warehouseId: '3',
-      code: ' A01 ',
-      name: ' 涓�妤煎簱鍖� ',
-      type: ' normal ',
-      shipperId: '11',
-      supplierId: '12',
-      flagMinus: 1,
-      flagLabelMange: 0,
-      flagMix: 1,
-      status: '',
-      sort: '2',
-      memo: ' memo '
-    }),
-    {
-      id: 9,
-      warehouseId: 3,
-      code: 'A01',
-      name: '涓�妤煎簱鍖�',
-      type: 'normal',
-      shipperId: 11,
-      supplierId: 12,
-      flagMinus: 1,
-      flagLabelMange: 0,
-      flagMix: 1,
-      status: 1,
-      sort: 2,
-      memo: 'memo'
-    }
-  )
-
-  const detail = helpers.normalizeWarehouseAreasDetailRecord({
-    id: 1,
-    warehouseId: 4,
-    warehouseId$: '涓讳粨',
-    type: 'A',
-    type$: '甯告俯',
-    name: ' 涓�妤煎簱鍖� ',
-    code: ' A01 ',
-    shipperId$: '璐т富A',
-    supplierId$: '渚涘簲鍟咮',
-    flagMinus: 1,
-    flagLabelMange: 0,
-    flagMix: 1,
-    status: 1,
-    sort: 3,
-    memo: ' memo ',
-    createBy$: 'root',
-    createTime$: '2026-03-30 10:00:00',
-    updateBy$: 'root',
-    updateTime$: '2026-03-30 10:10:00'
-  })
-
-  assert.equal(detail.statusText, '姝e父')
-  assert.equal(detail.flagMixText, '鏄�')
-  assert.equal(detail.warehouseName, '涓讳粨')
-  assert.equal(detail.typeText, '甯告俯')
-  assert.equal(detail.shipperName, '璐т富A')
-  assert.equal(detail.supplierName, '渚涘簲鍟咮')
-  assert.equal(detail.memo, 'memo')
-})
-
-test('warehouse areas columns expose detail action slot and status tag', async () => {
-  const columnsSource = await readFile(fileURLToPath(columnsPath), 'utf8')
-
-  assert.match(columnsSource, /createWarehouseAreasTableColumns/)
-  assert.match(columnsSource, /ArtButtonMore|ArtButtonTable/)
-  assert.match(columnsSource, /label: '鎿嶄綔'/)
-  assert.match(columnsSource, /useSlot: true|formatter:/)
-  assert.match(columnsSource, /label: '鐘舵��'/)
-})
-
-test('warehouse areas page uses real query, tree-like references and detail drawer structure', async () => {
-  const pageSource = await readFile(fileURLToPath(pagePath), 'utf8')
-
-  assert.match(pageSource, /fetchWarehouseAreasPage/)
-  assert.match(pageSource, /fetchWarehouseAreasDetail/)
-  assert.match(pageSource, /fetchSaveWarehouseAreas/)
-  assert.match(pageSource, /fetchUpdateWarehouseAreas/)
-  assert.match(pageSource, /fetchDeleteWarehouseAreas/)
-  assert.match(pageSource, /WarehouseAreasDetailDrawer/)
-  assert.match(pageSource, /ArtSearchBar/)
-  assert.match(pageSource, /ArtTable/)
-})
-
-test('backend menu adapter releases warehouse areas route and static route is registered', async () => {
-  const backendMenuAdapterSource = await readFile(fileURLToPath(backendMenuAdapterPath), 'utf8')
-  const staticRoutesSource = await readFile(fileURLToPath(staticRoutesPath), 'utf8')
-
-  assert.match(backendMenuAdapterSource, /warehouseAreas:\s*'\/basic-info\/warehouse-areas'/)
-  assert.match(staticRoutesSource, /path: 'warehouse-areas'/)
-  assert.match(staticRoutesSource, /title:\s*'menu\.warehouseAreas'/)
-})
diff --git a/rsf-design/tests/basic-info-wh-mat-page-contract.test.mjs b/rsf-design/tests/basic-info-wh-mat-page-contract.test.mjs
deleted file mode 100644
index 77ce88b..0000000
--- a/rsf-design/tests/basic-info-wh-mat-page-contract.test.mjs
+++ /dev/null
@@ -1,79 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-test('builds matnr page params with trimmed filters and group ids', async () => {
-  const { buildMatnrPageQueryParams } = await import(
-    '../src/views/basic-info/wh-mat/whMatPage.helpers.js'
-  )
-
-  assert.deepEqual(
-    buildMatnrPageQueryParams({
-      current: 2,
-      pageSize: 30,
-      condition: '  鍗婃垚鍝�  ',
-      code: '  RM001  ',
-      name: '  鐗╂枡A  ',
-      spec: '',
-      groupId: 19
-    }),
-    {
-      current: 2,
-      pageSize: 30,
-      condition: '鍗婃垚鍝�',
-      code: 'RM001',
-      name: '鐗╂枡A',
-      groupId: '19'
-    }
-  )
-})
-
-test('normalizes matnr group trees for el-tree rendering', async () => {
-  const { normalizeMatnrGroupTreeRows } = await import(
-    '../src/views/basic-info/wh-mat/whMatPage.helpers.js'
-  )
-
-  const tree = normalizeMatnrGroupTreeRows([
-    {
-      id: 1,
-      parentId: 0,
-      name: '鍗婃垚鍝�',
-      code: 'RM',
-      status: 1,
-      children: [
-        {
-          id: 2,
-          parentId: 1,
-          name: '鍗婃垚鍝丄',
-          code: 'RM-A',
-          status: 0
-        }
-      ]
-    }
-  ])
-
-  assert.equal(tree[0].displayLabel, '鍗婃垚鍝� 路 RM')
-  assert.equal(tree[0].children[0].statusText, '鍐荤粨')
-})
-
-test('normalizes matnr detail fields for detail drawer display', async () => {
-  const { normalizeMatnrDetail } = await import('../src/views/basic-info/wh-mat/whMatPage.helpers.js')
-
-  const detail = normalizeMatnrDetail({
-    id: 8,
-    code: 'RM001',
-    name: '鍗婃垚鍝�',
-    groupId$: '鍘熸潗鏂�',
-    status: 1,
-    stockLeval$: ' A',
-    flagLabelMange$: ' 鏄�',
-    extendFields: {
-      batch: 'B001'
-    }
-  })
-
-  assert.equal(detail.code, 'RM001')
-  assert.equal(detail.groupName, '鍘熸潗鏂�')
-  assert.equal(detail.statusText, '姝e父')
-  assert.equal(detail.extendFields.batch, 'B001')
-})
-
diff --git a/rsf-design/tests/clean-dev-helpers.test.mjs b/rsf-design/tests/clean-dev-helpers.test.mjs
deleted file mode 100644
index 1c536f7..0000000
--- a/rsf-design/tests/clean-dev-helpers.test.mjs
+++ /dev/null
@@ -1,112 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-import {
-  CLEAN_DEV_TARGETS,
-  buildMinimalRouteModuleFiles,
-  createMinimalChangeLogContent,
-  createMinimalFastEnterContent,
-  pruneLocaleMessages,
-  rewriteMenuApiContent
-} from '../scripts/clean-dev.helpers.mjs'
-
-test('clean:dev targets align with current JS workspace structure', () => {
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/template'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/widgets'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/examples'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/article'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/safeguard'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/change'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/dashboard/analysis'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/views/dashboard/ecommerce'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/components/core/charts/art-map-chart'))
-  assert.ok(CLEAN_DEV_TARGETS.includes('src/components/business/comment-widget'))
-})
-
-test('minimal route module files keep only dashboard/system/result/exception', () => {
-  const routeFiles = buildMinimalRouteModuleFiles()
-
-  assert.match(routeFiles.dashboard, /path: 'console'/)
-  assert.doesNotMatch(routeFiles.dashboard, /analysis/)
-  assert.doesNotMatch(routeFiles.dashboard, /ecommerce/)
-  assert.doesNotMatch(routeFiles.system, /nested/i)
-  assert.match(routeFiles.index, /dashboardRoutes/)
-  assert.match(routeFiles.index, /systemRoutes/)
-  assert.match(routeFiles.index, /resultRoutes/)
-  assert.match(routeFiles.index, /exceptionRoutes/)
-  assert.doesNotMatch(
-    routeFiles.index,
-    /templateRoutes|widgetsRoutes|examplesRoutes|articleRoutes|helpRoutes/
-  )
-})
-
-test('locale pruning removes demo menu entries and extra dashboard/system branches', () => {
-  const locale = {
-    menus: {
-      dashboard: {
-        title: 'Dashboard',
-        console: 'Console',
-        analysis: 'Analysis',
-        ecommerce: 'Ecommerce'
-      },
-      widgets: { title: 'Widgets' },
-      template: { title: 'Template' },
-      article: { title: 'Article' },
-      examples: { title: 'Examples' },
-      safeguard: { title: 'Safeguard' },
-      plan: { title: 'Plan' },
-      help: { title: 'Help' },
-      system: {
-        title: 'System',
-        user: 'User',
-        role: 'Role',
-        userCenter: 'User Center',
-        menu: 'Menu',
-        nested: 'Nested',
-        menu1: 'Menu1',
-        menu2: 'Menu2',
-        menu21: 'Menu21',
-        menu3: 'Menu3',
-        menu31: 'Menu31',
-        menu32: 'Menu32',
-        menu321: 'Menu321'
-      }
-    }
-  }
-
-  const result = pruneLocaleMessages(locale)
-
-  assert.deepEqual(Object.keys(result.menus).sort(), ['dashboard', 'system'])
-  assert.deepEqual(Object.keys(result.menus.dashboard).sort(), ['console', 'title'])
-  assert.deepEqual(Object.keys(result.menus.system).sort(), [
-    'menu',
-    'role',
-    'title',
-    'user',
-    'userCenter'
-  ])
-})
-
-test('minimal fast enter content removes demo quick entries', () => {
-  const content = createMinimalFastEnterContent()
-
-  assert.match(content, /routeName: 'Console'/)
-  assert.match(content, /routeName: 'Login'/)
-  assert.match(content, /routeName: 'Register'/)
-  assert.match(content, /routeName: 'ForgetPassword'/)
-  assert.match(content, /routeName: 'UserCenter'/)
-  assert.doesNotMatch(content, /Analysis|Fireworks|Chat|ChangeLog|Pricing|ArticleComment/)
-})
-
-test('menu api rewrite switches to simple endpoint', () => {
-  const content = "url: '/api/v3/system/menus'"
-
-  assert.equal(rewriteMenuApiContent(content), "url: '/api/v3/system/menus/simple'")
-})
-
-test('minimal change log content clears upgrade history', () => {
-  assert.equal(
-    createMinimalChangeLogContent().trim(),
-    "import { ref } from 'vue'\n\nexport const upgradeLogList = ref([])"
-  )
-})
diff --git a/rsf-design/tests/iconify-local-minimal.test.mjs b/rsf-design/tests/iconify-local-minimal.test.mjs
deleted file mode 100644
index b3925d5..0000000
--- a/rsf-design/tests/iconify-local-minimal.test.mjs
+++ /dev/null
@@ -1,112 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import path from 'node:path'
-import test from 'node:test'
-import { fileURLToPath, pathToFileURL } from 'node:url'
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const projectRoot = path.resolve(__dirname, '..')
-const srcRoot = path.join(projectRoot, 'src')
-const iconRegistryModule = pathToFileURL(path.join(srcRoot, 'plugins', 'iconify.js')).href
-
-function collectSourceFiles(dir) {
-  const entries = fs.readdirSync(dir, { withFileTypes: true })
-  return entries.flatMap((entry) => {
-    const fullPath = path.join(dir, entry.name)
-
-    if (entry.isDirectory()) {
-      return collectSourceFiles(fullPath)
-    }
-
-    return /\.(vue|js)$/.test(entry.name) ? [fullPath] : []
-  })
-}
-
-function collectUsedIconsByPrefix() {
-  const iconPattern = /["']([a-z0-9-]+):([a-z0-9-]+)["']/g
-  const knownPrefixes = new Set([
-    'fluent',
-    'icon-park-outline',
-    'iconamoon',
-    'ix',
-    'line-md',
-    'ri',
-    'svg-spinners',
-    'system-uicons',
-    'vaadin'
-  ])
-  const usedIconsByPrefix = new Map()
-
-  for (const filePath of collectSourceFiles(srcRoot)) {
-    const content = fs.readFileSync(filePath, 'utf8')
-
-    for (const [, prefix, name] of content.matchAll(iconPattern)) {
-      if (!knownPrefixes.has(prefix)) {
-        continue
-      }
-
-      const names = usedIconsByPrefix.get(prefix) || new Set()
-      names.add(name)
-      usedIconsByPrefix.set(prefix, names)
-    }
-  }
-
-  return usedIconsByPrefix
-}
-
-function collectRequiredEntries(collection, usedNames) {
-  const requiredIcons = new Set()
-  const requiredAliases = new Set()
-  const aliases = collection.aliases || {}
-
-  function addDependencies(name) {
-    if (collection.icons[name]) {
-      requiredIcons.add(name)
-      return
-    }
-
-    const alias = aliases[name]
-    assert.ok(alias, `Icon "${collection.prefix}:${name}" is missing from local collection`)
-
-    if (requiredAliases.has(name)) {
-      return
-    }
-
-    requiredAliases.add(name)
-    addDependencies(alias.parent)
-  }
-
-  usedNames.forEach((name) => addDependencies(name))
-
-  return {
-    icons: [...requiredIcons].sort(),
-    aliases: [...requiredAliases].sort()
-  }
-}
-
-test('local Iconify collections only contain icons that are actually used by the app', async () => {
-  const { LOCAL_ICON_COLLECTIONS } = await import(iconRegistryModule)
-  const usedIconsByPrefix = collectUsedIconsByPrefix()
-
-  for (const [prefix, usedNames] of usedIconsByPrefix.entries()) {
-    const collection = LOCAL_ICON_COLLECTIONS[prefix]
-
-    assert.ok(collection, `Missing local collection for prefix "${prefix}"`)
-
-    const requiredEntries = collectRequiredEntries(collection, usedNames)
-    const actualIcons = Object.keys(collection.icons || {}).sort()
-    const actualAliases = Object.keys(collection.aliases || {}).sort()
-
-    assert.deepEqual(
-      actualIcons,
-      requiredEntries.icons,
-      `Local collection "${prefix}" includes unused icon entries`
-    )
-
-    assert.deepEqual(
-      actualAliases,
-      requiredEntries.aliases,
-      `Local collection "${prefix}" includes unused alias entries`
-    )
-  }
-})
diff --git a/rsf-design/tests/iconify-local-prefixes.test.mjs b/rsf-design/tests/iconify-local-prefixes.test.mjs
deleted file mode 100644
index 7041182..0000000
--- a/rsf-design/tests/iconify-local-prefixes.test.mjs
+++ /dev/null
@@ -1,65 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-import fs from 'node:fs'
-import path from 'node:path'
-import { fileURLToPath, pathToFileURL } from 'node:url'
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const projectRoot = path.resolve(__dirname, '..')
-const srcRoot = path.join(projectRoot, 'src')
-const iconRegistryModule = pathToFileURL(path.join(srcRoot, 'plugins', 'iconify.js')).href
-
-function collectSourceFiles(dir) {
-  const entries = fs.readdirSync(dir, { withFileTypes: true })
-  return entries.flatMap((entry) => {
-    const fullPath = path.join(dir, entry.name)
-
-    if (entry.isDirectory()) {
-      return collectSourceFiles(fullPath)
-    }
-
-    return /\.(vue|js)$/.test(entry.name) ? [fullPath] : []
-  })
-}
-
-function collectIconPrefixes() {
-  const iconPattern = /["']([a-z0-9-]+):([a-z0-9-]+)["']/g
-  const knownPrefixes = new Set([
-    'fluent',
-    'icon-park-outline',
-    'iconamoon',
-    'ix',
-    'line-md',
-    'ri',
-    'svg-spinners',
-    'system-uicons',
-    'vaadin'
-  ])
-  const prefixes = new Set()
-
-  for (const filePath of collectSourceFiles(srcRoot)) {
-    const content = fs.readFileSync(filePath, 'utf8')
-
-    for (const [, prefix] of content.matchAll(iconPattern)) {
-      if (!knownPrefixes.has(prefix)) {
-        continue
-      }
-
-      prefixes.add(prefix)
-    }
-  }
-
-  return [...prefixes].sort()
-}
-
-test('all used Iconify prefixes are mapped to local collections', async () => {
-  const { LOCAL_ICON_COLLECTIONS } = await import(iconRegistryModule)
-  const actualPrefixes = collectIconPrefixes()
-  const registeredPrefixes = Object.keys(LOCAL_ICON_COLLECTIONS).sort()
-
-  assert.deepEqual(
-    registeredPrefixes,
-    actualPrefixes,
-    `Missing local Iconify collections for: ${actualPrefixes.filter((prefix) => !registeredPrefixes.includes(prefix)).join(', ')}`
-  )
-})
diff --git a/rsf-design/tests/list-export-print-contract.test.mjs b/rsf-design/tests/list-export-print-contract.test.mjs
deleted file mode 100644
index d7a2f50..0000000
--- a/rsf-design/tests/list-export-print-contract.test.mjs
+++ /dev/null
@@ -1,103 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-import {
-  buildListExportPayload,
-  buildPrintPageQuery,
-  toExportColumns
-} from '../src/components/biz/list-export-print/list-export-print.helpers.js'
-
-test('selected rows keep export payload on ids only instead of flat query params', () => {
-  const payload = buildListExportPayload({
-    reportTitle: '瑙掕壊绠$悊鎶ヨ〃',
-    selectedRows: [{ id: 7 }, { id: 9 }],
-    queryParams: { current: 1, pageSize: 20, name: '绠$悊鍛�', code: 'R_ADMIN' },
-    columns: [{ prop: 'name', label: '瑙掕壊鍚嶇О' }]
-  })
-
-  assert.deepEqual(payload.ids, [7, 9])
-  assert.deepEqual(payload.columns, [{ source: 'name', label: '瑙掕壊鍚嶇О' }])
-  assert.equal(payload.meta.reportTitle, '瑙掕壊绠$悊鎶ヨ〃')
-  assert.deepEqual(
-    Object.keys(payload).filter((key) => ['current', 'pageSize', 'name', 'code', 'queryParams'].includes(key)),
-    []
-  )
-})
-
-test('export columns use ListExportService source/label contract only', () => {
-  assert.deepEqual(
-    toExportColumns([
-      { prop: 'name', label: '瑙掕壊鍚嶇О' },
-      { prop: 'operation', label: '鎿嶄綔' },
-      { prop: 'selection', label: '鍕鹃��' }
-    ]),
-    [{ source: 'name', label: '瑙掕壊鍚嶇О' }]
-  )
-})
-
-test('export payload keeps no-filter searches legal as flat params', () => {
-  const payload = buildListExportPayload({
-    reportTitle: '瑙掕壊绠$悊鎶ヨ〃',
-    selectedRows: [],
-    queryParams: { current: 1, pageSize: 20 },
-    columns: [{ prop: 'name', label: '瑙掕壊鍚嶇О' }]
-  })
-
-  assert.equal(payload.current, 1)
-  assert.equal(payload.pageSize, 20)
-  assert.equal(payload.meta.reportTitle, '瑙掕壊绠$悊鎶ヨ〃')
-  assert.deepEqual(payload.ids, [])
-  assert.equal(Object.prototype.hasOwnProperty.call(payload, 'queryParams'), false)
-})
-
-test('export payload preserves report style meta and column align without top-level reportStyle', () => {
-  const payload = buildListExportPayload({
-    reportTitle: '瑙掕壊绠$悊鎶ヨ〃',
-    meta: {
-      reportStyle: {
-        orientation: 'landscape',
-        density: 'comfortable'
-      }
-    },
-    columns: [{ prop: 'name', label: '瑙掕壊鍚嶇О', align: 'right' }]
-  })
-
-  assert.deepEqual(payload.meta.reportStyle, {
-    orientation: 'landscape',
-    density: 'comfortable'
-  })
-  assert.equal(payload.columns[0].align, 'right')
-  assert.equal(Object.prototype.hasOwnProperty.call(payload, 'reportStyle'), false)
-})
-
-test('print query expands to the full result set instead of the current page size', () => {
-  assert.deepEqual(
-    buildPrintPageQuery({
-      queryParams: { current: 3, pageSize: 20, orderBy: 'createTime desc', name: '绠$悊鍛�' },
-      total: 86,
-      maxResults: 1000
-    }),
-    {
-      current: 1,
-      pageSize: 86,
-      orderBy: 'createTime desc',
-      name: '绠$悊鍛�'
-    }
-  )
-})
-
-test('print query caps pageSize at maxResults when total is larger', () => {
-  assert.deepEqual(
-    buildPrintPageQuery({
-      queryParams: { current: 5, pageSize: 20, orderBy: 'createTime desc', code: 'R_ADMIN' },
-      total: 1500,
-      maxResults: 1000
-    }),
-    {
-      current: 1,
-      pageSize: 1000,
-      orderBy: 'createTime desc',
-      code: 'R_ADMIN'
-    }
-  )
-})
diff --git a/rsf-design/tests/list-print-preview-style-contract.test.mjs b/rsf-design/tests/list-print-preview-style-contract.test.mjs
deleted file mode 100644
index 760c0e1..0000000
--- a/rsf-design/tests/list-print-preview-style-contract.test.mjs
+++ /dev/null
@@ -1,202 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFileSync } from 'node:fs'
-import test from 'node:test'
-
-import {
-  buildPreviewColumns,
-  buildReportStyleMeta
-} from '../src/components/biz/list-export-print/list-export-print.helpers.js'
-import { buildPrintDocumentHtml } from '../src/components/biz/list-export-print/list-print-document.js'
-
-const previewDialogSource = readFileSync(
-  new URL('../src/components/biz/list-export-print/list-print-preview-dialog.vue', import.meta.url),
-  'utf8'
-)
-const printDocumentSource = readFileSync(
-  new URL('../src/components/biz/list-export-print/list-print-document.js', import.meta.url),
-  'utf8'
-)
-
-const scriptSetupIndex = previewDialogSource.indexOf('<script setup>')
-const templateBlock =
-  scriptSetupIndex >= 0
-    ? previewDialogSource.slice(previewDialogSource.indexOf('<template>'), scriptSetupIndex)
-    : previewDialogSource
-
-test('report style meta defaults to the shared print preview contract', () => {
-  assert.deepEqual(buildReportStyleMeta({ reportTitle: '瑙掕壊鎶ヨ〃' }).reportStyle, {
-    titleAlign: 'center',
-    titleLevel: 'strong',
-    orientation: 'portrait',
-    density: 'compact',
-    showSequence: true,
-    showBorder: true
-  })
-})
-
-test('preview columns do not inject a sequence column when disabled', () => {
-  assert.deepEqual(
-    buildPreviewColumns({
-      columns: [{ source: 'name', label: '瑙掕壊鍚嶇О' }],
-      reportStyle: { showSequence: false }
-    }),
-    [{ source: 'name', label: '瑙掕壊鍚嶇О' }]
-  )
-})
-
-test('preview dialog template uses passed columns for sequence rendering and colspan', () => {
-  assert.equal(templateBlock.includes('>搴忓彿<'), false)
-  assert.match(templateBlock, /<div\s+:class="titleClass">\s*\{\{\s*meta\.reportTitle\s*\?\?\s*'--'\s*\}\}\s*<\/div>/)
-  assert.match(templateBlock, /<div\s+v-for="item in metaItems"[\s\S]*?:key="item\.key"[\s\S]*?>/)
-  assert.match(templateBlock, /\{\{\s*item\.label\s*\}\}/)
-  assert.match(templateBlock, /\{\{\s*item\.value\s*\?\?\s*'--'\s*\}\}/)
-  assert.match(templateBlock, /<th[\s\S]*?v-for="column in columns"[\s\S]*?>[\s\S]*?\{\{\s*column\.label\s*\}\}[\s\S]*?<\/th>/)
-  assert.match(
-    templateBlock,
-    /<td[\s\S]*?v-for="column in columns"[\s\S]*?>[\s\S]*?\{\{\s*column\.source === '__sequence__' \? index \+ 1 : row\?\.\[column\.source\] \?\? '--'\s*\}\}[\s\S]*?<\/td>/
-  )
-  assert.ok(templateBlock.includes("column.source === '__sequence__' ? index + 1"))
-  assert.ok(templateBlock.includes('Math.max(columns.length, 1)'))
-  assert.ok(previewDialogSource.includes('鎶ヨ〃鏃ユ湡'))
-  assert.ok(previewDialogSource.includes('鎵撳嵃浜�'))
-  assert.ok(previewDialogSource.includes('鎵撳嵃鏃堕棿'))
-  assert.ok(previewDialogSource.includes('璁板綍鏁�'))
-  assert.ok(templateBlock.includes('border-b'))
-  assert.ok(templateBlock.includes('print:hidden'))
-})
-
-test('preview dialog derives titleClass from reportStyle title alignment and level', () => {
-  assert.ok(previewDialogSource.includes('const titleClass = computed(() =>'))
-  assert.ok(previewDialogSource.includes('meta.reportTitle'))
-  assert.ok(previewDialogSource.includes('titleClass'))
-  assert.ok(previewDialogSource.includes('reportStyle.titleAlign'))
-  assert.ok(previewDialogSource.includes('reportStyle.titleLevel'))
-  assert.ok(previewDialogSource.includes("left: 'text-left'"))
-  assert.ok(previewDialogSource.includes("center: 'text-center'"))
-  assert.ok(previewDialogSource.includes("right: 'text-right'"))
-  assert.ok(previewDialogSource.includes("normal: 'text-[18px] font-medium'"))
-  assert.ok(previewDialogSource.includes("strong: 'text-[22px] font-semibold'"))
-  assert.ok(previewDialogSource.includes("prominent: 'text-[26px] font-bold'"))
-  assert.ok(previewDialogSource.includes("?? alignMap.center"))
-  assert.ok(previewDialogSource.includes("?? levelMap.strong"))
-})
-
-test('preview dialog keeps report content compact and prioritizes showing all columns', () => {
-  assert.ok(previewDialogSource.includes("text-[18px] font-medium"))
-  assert.ok(previewDialogSource.includes("text-[22px] font-semibold"))
-  assert.ok(previewDialogSource.includes("text-[26px] font-bold"))
-  assert.ok(previewDialogSource.includes('grid grid-cols-4 gap-2 text-[11px]'))
-  assert.ok(previewDialogSource.includes('table-auto'))
-  assert.ok(previewDialogSource.includes('text-[11px] leading-tight'))
-  assert.ok(previewDialogSource.includes('px-1.5 py-1.5'))
-  assert.ok(previewDialogSource.includes('break-all'))
-})
-
-test('preview dialog prints through a standalone report document instead of window.print on the app shell', () => {
-  assert.equal(previewDialogSource.includes('window.print()'), false)
-  assert.ok(previewDialogSource.includes("import { printReportDocument } from './list-print-document.js'"))
-  assert.ok(previewDialogSource.includes('printReportDocument({'))
-})
-
-test('standalone print document preserves report title, meta row, sequence column, and print page styles', () => {
-  const html = buildPrintDocumentHtml({
-    title: '瑙掕壊绠$悊鎶ヨ〃',
-    meta: {
-      reportTitle: '瑙掕壊绠$悊鎶ヨ〃',
-      reportDate: '2026/3/29',
-      operator: 'root',
-      printedAt: '2026/3/29 17:31:00',
-      count: 2,
-      reportStyle: {
-        titleAlign: 'center',
-        titleLevel: 'strong',
-        orientation: 'portrait'
-      }
-    },
-    columns: [
-      { source: '__sequence__', label: '搴忓彿', align: 'center' },
-      { source: 'name', label: '瑙掕壊鍚嶇О' },
-      { source: 'code', label: '瑙掕壊缂栫爜' }
-    ],
-    rows: [
-      { id: 1, name: 'WMS绯荤粺绠$悊鍛�', code: 'admin' },
-      { id: 2, name: 'ERP璐㈠姟绠$悊鍛�', code: 'erpcw' }
-    ]
-  })
-
-  assert.ok(html.includes('<title>瑙掕壊绠$悊鎶ヨ〃</title>'))
-  assert.ok(html.includes('鎶ヨ〃鏃ユ湡'))
-  assert.ok(html.includes('鎵撳嵃浜�'))
-  assert.ok(html.includes('鎵撳嵃鏃堕棿'))
-  assert.ok(html.includes('璁板綍鏁�'))
-  assert.ok(html.includes('搴忓彿'))
-  assert.ok(html.includes('WMS绯荤粺绠$悊鍛�'))
-  assert.ok(html.includes('ERP璐㈠姟绠$悊鍛�'))
-  assert.ok(html.includes('@page'))
-  assert.ok(html.includes('size: A4 portrait;'))
-  const pageRule = html.match(/@page\s*\{[\s\S]*?\}/)?.[0] ?? ''
-  assert.equal(pageRule.includes('margin:'), false)
-  assert.ok(html.includes('print-color-adjust: exact;'))
-  assert.ok(html.includes('window.print()'))
-  assert.ok(html.includes('window.close()'))
-  assert.ok(html.includes('font-size: 11px;'))
-  assert.ok(html.includes('font-size: 22px;'))
-  assert.ok(html.includes('table-layout: auto;'))
-})
-
-test('standalone print document opens a writable popup window for document injection', () => {
-  assert.equal(printDocumentSource.includes('noopener,noreferrer'), false)
-  assert.ok(printDocumentSource.includes("window.open('', '_blank')"))
-  assert.ok(printDocumentSource.includes('printWindow.document.write(buildPrintDocumentHtml(payload))'))
-})
-
-test('preview dialog caps rendered rows and keeps the table body scrollable for large datasets', () => {
-  assert.ok(previewDialogSource.includes("maxPreviewRows: { type: Number, default: 50 }"))
-  assert.ok(previewDialogSource.includes('const previewRows = computed(() =>'))
-  assert.ok(previewDialogSource.includes('props.rows.slice(0, props.maxPreviewRows)'))
-  assert.ok(previewDialogSource.includes('const hiddenRowCount = computed(() =>'))
-  assert.ok(previewDialogSource.includes('棰勮浠呭睍绀哄墠'))
-  assert.ok(previewDialogSource.includes('min-h-0 flex-1 overflow-auto'))
-  assert.ok(previewDialogSource.includes('v-for="(row, index) in previewRows"'))
-  assert.ok(previewDialogSource.includes('v-if="hiddenRowCount > 0"'))
-})
-
-test('preview dialog keeps the whole modal inside the viewport and lets only the table area scroll', () => {
-  assert.ok(previewDialogSource.includes('width="min(96vw, 1100px)"'))
-  assert.ok(previewDialogSource.includes('top="4vh"'))
-  assert.ok(previewDialogSource.includes('class="max-h-[88vh] overflow-hidden"'))
-  assert.ok(previewDialogSource.includes('flex max-h-[calc(88vh-160px)] min-h-0 flex-col'))
-  assert.ok(previewDialogSource.includes('mx-auto flex min-h-0 flex-1 flex-col overflow-hidden'))
-  assert.ok(previewDialogSource.includes(':class="paperClass"'))
-  assert.ok(previewDialogSource.includes(':style="paperStyle"'))
-  assert.ok(previewDialogSource.includes('mt-4 min-h-0 flex-1 overflow-auto'))
-})
-
-test('preview dialog lets the user switch between portrait and landscape before printing', () => {
-  assert.ok(previewDialogSource.includes("const currentOrientation = ref('portrait')"))
-  assert.ok(previewDialogSource.includes('watch('))
-  assert.ok(previewDialogSource.includes('v-model="currentOrientation"'))
-  assert.ok(previewDialogSource.includes('label="portrait"'))
-  assert.ok(previewDialogSource.includes('label="landscape"'))
-  assert.ok(previewDialogSource.includes('绔栫増'))
-  assert.ok(previewDialogSource.includes('妯増'))
-  assert.ok(previewDialogSource.includes('const effectiveMeta = computed(() =>'))
-  assert.ok(previewDialogSource.includes('orientation: currentOrientation.value'))
-  assert.ok(previewDialogSource.includes('meta: effectiveMeta.value'))
-  assert.ok(previewDialogSource.includes('const paperClass = computed(() =>'))
-  assert.ok(previewDialogSource.includes("currentOrientation.value === 'landscape'"))
-  assert.ok(previewDialogSource.includes("aspectRatio: currentOrientation.value === 'landscape' ? '297 / 210' : '210 / 297'"))
-})
-
-test('preview dialog lets the user toggle table borders and carries the setting into print meta', () => {
-  assert.ok(previewDialogSource.includes("const currentShowBorder = ref(true)"))
-  assert.ok(previewDialogSource.includes('v-model="currentShowBorder"'))
-  assert.ok(previewDialogSource.includes('杈规寮�'))
-  assert.ok(previewDialogSource.includes('杈规鍏�'))
-  assert.ok(previewDialogSource.includes('showBorder: currentShowBorder.value'))
-  assert.ok(previewDialogSource.includes('const tableWrapClass = computed(() =>'))
-  assert.ok(previewDialogSource.includes('const headerCellClass = computed(() =>'))
-  assert.ok(previewDialogSource.includes('const bodyCellClass = computed(() =>'))
-  assert.ok(printDocumentSource.includes('const showBorder = reportStyle.showBorder !== false'))
-  assert.ok(printDocumentSource.includes("const tableWrapClass = showBorder ? 'report-table-wrap' : 'report-table-wrap report-table-wrap-borderless'"))
-})
diff --git a/rsf-design/tests/manual-chunks.test.mjs b/rsf-design/tests/manual-chunks.test.mjs
deleted file mode 100644
index a4f2fd1..0000000
--- a/rsf-design/tests/manual-chunks.test.mjs
+++ /dev/null
@@ -1,37 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-import { createManualChunks } from '../build/manualChunks.js'
-
-test('createManualChunks groups heavy dependencies into stable vendor chunks', () => {
-  assert.equal(createManualChunks('/repo/node_modules/echarts/core.js'), 'vendor-echarts')
-  assert.equal(
-    createManualChunks('/repo/node_modules/@wangeditor/editor-for-vue/dist/index.js'),
-    'vendor-editor'
-  )
-  assert.equal(createManualChunks('/repo/node_modules/highlight.js/lib/index.js'), 'vendor-editor')
-  assert.equal(createManualChunks('/repo/node_modules/xlsx/xlsx.mjs'), 'vendor-xlsx')
-  assert.equal(createManualChunks('/repo/node_modules/xgplayer/dist/index.min.js'), 'vendor-media')
-  assert.equal(
-    createManualChunks('/repo/node_modules/element-plus/es/index.mjs'),
-    'vendor-element-plus'
-  )
-  assert.equal(
-    createManualChunks('/repo/node_modules/@element-plus/icons-vue/dist/index.mjs'),
-    'vendor-element-plus'
-  )
-  assert.equal(createManualChunks('/repo/node_modules/vue-router/dist/index.mjs'), 'vendor-vue')
-  assert.equal(createManualChunks('/repo/node_modules/pinia/dist/pinia.mjs'), 'vendor-vue')
-  assert.equal(createManualChunks('/repo/node_modules/@vueuse/core/index.mjs'), 'vendor-vue')
-  assert.equal(createManualChunks('/repo/node_modules/@iconify/vue/dist/index.mjs'), 'vendor-utils')
-  assert.equal(
-    createManualChunks('/repo/node_modules/file-saver/dist/FileSaver.min.js'),
-    'vendor-utils'
-  )
-  assert.equal(createManualChunks('/repo/node_modules/axios/index.js'), 'vendor-utils')
-})
-
-test('createManualChunks leaves application modules untouched', () => {
-  assert.equal(createManualChunks('/repo/src/views/dashboard/index.vue'), undefined)
-  assert.equal(createManualChunks('\u0000plugin-vue:export-helper'), undefined)
-})
diff --git a/rsf-design/tests/navigation-home-path.test.mjs b/rsf-design/tests/navigation-home-path.test.mjs
deleted file mode 100644
index ad5e873..0000000
--- a/rsf-design/tests/navigation-home-path.test.mjs
+++ /dev/null
@@ -1,65 +0,0 @@
-import test from 'node:test'
-import assert from 'node:assert/strict'
-
-test('getFirstMenuPath skips unreleased menu routes and selects the first implemented page', async () => {
-  const { getFirstMenuPath } = await import('../src/utils/navigation/route.js')
-
-  const menuList = [
-    {
-      path: '/system',
-      children: [
-        {
-          path: '/system/aiParam',
-          component: '/system/aiParam',
-          meta: { title: 'AI 鍙傛暟' }
-        }
-      ]
-    },
-    {
-      path: '/logs',
-      children: [
-        {
-          path: '/logs/system/userLogin',
-          component: '/system/user-login',
-          meta: { title: '鐧诲綍鏃ュ織' }
-        }
-      ]
-    }
-  ]
-
-  assert.equal(getFirstMenuPath(menuList), '/logs/system/userLogin')
-})
-
-test('getFirstMenuPath keeps traversing siblings when the first branch has no implemented leaf', async () => {
-  const { getFirstMenuPath } = await import('../src/utils/navigation/route.js')
-
-  const menuList = [
-    {
-      path: '/ai',
-      children: [
-        {
-          path: '/ai/param',
-          component: '/system/aiParam',
-          meta: { title: 'AI 鍙傛暟' }
-        }
-      ]
-    },
-    {
-      path: '/permissions',
-      children: [
-        {
-          path: '/permissions/system',
-          children: [
-            {
-              path: '/permissions/system/role',
-              component: '/system/role',
-              meta: { title: '瑙掕壊绠$悊' }
-            }
-          ]
-        }
-      ]
-    }
-  ]
-
-  assert.equal(getFirstMenuPath(menuList), '/permissions/system/role')
-})
diff --git a/rsf-design/tests/repo-hygiene.test.mjs b/rsf-design/tests/repo-hygiene.test.mjs
deleted file mode 100644
index c7ac397..0000000
--- a/rsf-design/tests/repo-hygiene.test.mjs
+++ /dev/null
@@ -1,37 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import path from 'node:path'
-import test from 'node:test'
-
-const projectRoot = path.resolve(import.meta.dirname, '..')
-
-const migrationArtifacts = [
-  'docs/superpowers/specs/2026-03-27-art-design-pro-js-migration-design.md',
-  'docs/superpowers/plans/2026-03-27-art-design-pro-js-migration-plan.md',
-  'scripts/migrate-to-js.mjs',
-  'scripts/restore-vue-template-imports.mjs',
-  'tests/js-migration-sanity.test.mjs',
-  'src/types'
-]
-
-test('migration-only artifacts have been removed from the working tree', () => {
-  for (const relativePath of migrationArtifacts) {
-    const fullPath = path.join(projectRoot, relativePath)
-    assert.equal(
-      fs.existsSync(fullPath),
-      false,
-      `Expected migration-only artifact to be removed: ${relativePath}`
-    )
-  }
-})
-
-test('package scripts no longer expose one-off migration entry points', () => {
-  const packageJsonPath = path.join(projectRoot, 'package.json')
-  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
-
-  assert.equal(
-    Reflect.has(packageJson.scripts || {}, 'migrate:js'),
-    false,
-    'Expected package.json scripts.migrate:js to be removed'
-  )
-})
diff --git a/rsf-design/tests/system-manage-contract.test.mjs b/rsf-design/tests/system-manage-contract.test.mjs
deleted file mode 100644
index f542a6b..0000000
--- a/rsf-design/tests/system-manage-contract.test.mjs
+++ /dev/null
@@ -1,147 +0,0 @@
-import assert from 'node:assert/strict'
-import { register } from 'node:module'
-import test from 'node:test'
-
-const menuTreeFixture = [
-  {
-    id: 1,
-    parentId: 0,
-    name: 'menu.user',
-    route: 'user',
-    type: 0,
-    component: 'user',
-    authority: 'system:user',
-    icon: 'People',
-    sort: 1,
-    status: 1,
-    memo: 'User menu',
-    children: [
-      {
-        id: 2,
-        parentId: 1,
-        name: 'Query User',
-        type: 1,
-        authority: 'system:user:list',
-        icon: 'Search',
-        sort: 1,
-        status: 0,
-        memo: 'Query action'
-      }
-    ]
-  }
-]
-
-register(
-  `data:text/javascript,export function resolve(specifier, context, nextResolve){ if(specifier===\'@/utils/http\'){ return { shortCircuit:true, url:\'data:text/javascript,export default { get(){ return Promise.resolve({}) }, post(config){ if(config?.url===\\'/menu/list\\'){ globalThis.__lastMenuListConfig = config; return Promise.resolve(${JSON.stringify(menuTreeFixture.flatMap((node) => [node, ...node.children]))}) } if(config?.url===\\'/menu/tree\\'){ globalThis.__lastMenuTreeConfig = config; return Promise.resolve(${JSON.stringify(menuTreeFixture)}) } return Promise.resolve(config) } }\' } } return nextResolve(specifier, context) }`,
-  import.meta.url
-)
-
-const {
-  buildUserListParams,
-  buildRoleListParams,
-  fetchDeleteMenu,
-  fetchGetMenuList,
-  fetchGetMenuTree,
-  fetchResetUserPassword,
-  fetchGetRoleScopeList,
-  fetchGetRoleScopeTree,
-  fetchSaveMenu,
-  fetchUpdateMenu
-} = await import('../src/api/system-manage.js')
-
-test('buildUserListParams matches the rsf-admin paging contract', () => {
-  assert.equal(typeof buildUserListParams, 'function')
-  assert.equal(typeof buildRoleListParams, 'function')
-
-  assert.deepEqual(
-    buildUserListParams({
-      current: 2,
-      pageSize: 20,
-      username: 'root',
-      email: 'root@example.com',
-      deptId: 3
-    }),
-    {
-      current: 2,
-      pageSize: 20,
-      username: 'root',
-      email: 'root@example.com',
-      deptId: 3
-    }
-  )
-})
-
-test('fetchResetUserPassword requires an admin update payload', () => {
-  assert.throws(() => fetchResetUserPassword(1), /object payload/i)
-  assert.throws(() => fetchResetUserPassword({ id: 1 }), /password/i)
-
-  return fetchResetUserPassword({ id: 1, newPassword: 'secret' }).then((config) => {
-    assert.equal(config.params.password, 'secret')
-  })
-})
-
-test('fetchGetMenuList folds button nodes into authList', async () => {
-  const menuList = await fetchGetMenuList()
-
-  assert.equal(menuList.length, 1)
-  assert.equal(menuList[0].id, '1')
-  assert.equal(menuList[0].parentId, '0')
-  assert.equal(menuList[0].route, 'user')
-  assert.equal(menuList[0].authority, 'system:user')
-  assert.equal(menuList[0].status, 1)
-  assert.equal(menuList[0].memo, 'User menu')
-  assert.equal(menuList[0].meta.title, 'menus.user')
-  assert.equal(menuList[0].component, 'user')
-  assert.equal(menuList[0].meta.sort, 1)
-  assert.equal(menuList[0].meta.isEnable, true)
-  assert.deepEqual(menuList[0].meta.authList, [
-    {
-      id: '2',
-      parentId: '1',
-      parentName: '',
-      name: 'Query User',
-      title: 'Query User',
-      route: '',
-      component: '',
-      authMark: 'system:user:list',
-      authority: 'system:user:list',
-      icon: 'Search',
-      sort: 1,
-      status: 0,
-      memo: 'Query action',
-      type: 1
-    }
-  ])
-  assert.equal(menuList[0].children.length, 0)
-  assert.deepEqual(globalThis.__lastMenuListConfig?.params, {})
-})
-
-test('menu tree helpers always send an explicit empty body for Spring Map request bodies', async () => {
-  await fetchGetMenuTree()
-  assert.deepEqual(globalThis.__lastMenuTreeConfig?.params, {})
-
-  await fetchGetRoleScopeTree('menu')
-  assert.equal(globalThis.__lastMenuTreeConfig?.url, '/menu/tree')
-  assert.deepEqual(globalThis.__lastMenuTreeConfig?.params, {})
-})
-
-test('menu CRUD helpers target the real rsf-server endpoints', async () => {
-  assert.equal(typeof fetchSaveMenu, 'function')
-  assert.equal(typeof fetchUpdateMenu, 'function')
-  assert.equal(typeof fetchDeleteMenu, 'function')
-
-  const saveConfig = await fetchSaveMenu({ name: '鑿滃崟' })
-  const updateConfig = await fetchUpdateMenu({ id: 1, name: '鑿滃崟' })
-  const deleteConfig = await fetchDeleteMenu('1,2')
-
-  assert.equal(saveConfig.url, '/menu/save')
-  assert.deepEqual(saveConfig.params, { name: '鑿滃崟' })
-  assert.equal(updateConfig.url, '/menu/update')
-  assert.deepEqual(updateConfig.params, { id: 1, name: '鑿滃崟' })
-  assert.equal(deleteConfig.url, '/menu/remove/1,2')
-})
-
-test('scope resolvers fail fast on invalid scope types', () => {
-  assert.throws(() => fetchGetRoleScopeList('invalid', 1), /Unsupported scope type/i)
-  assert.throws(() => fetchGetRoleScopeTree('invalid', {}), /Unsupported scope type/i)
-})
diff --git a/rsf-design/tests/system-menu-page-contract.test.mjs b/rsf-design/tests/system-menu-page-contract.test.mjs
deleted file mode 100644
index 4592ee2..0000000
--- a/rsf-design/tests/system-menu-page-contract.test.mjs
+++ /dev/null
@@ -1,56 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFileSync } from 'node:fs'
-import test from 'node:test'
-
-const menuPageSource = readFileSync(
-  new URL('../src/views/system/menu/index.vue', import.meta.url),
-  'utf8'
-)
-
-const menuDialogSource = readFileSync(
-  new URL('../src/views/system/menu/modules/menu-dialog.vue', import.meta.url),
-  'utf8'
-)
-
-const routerSource = readFileSync(new URL('../src/utils/router.js', import.meta.url), 'utf8')
-const zhLocale = JSON.parse(readFileSync(new URL('../src/locales/langs/zh.json', import.meta.url), 'utf8'))
-const enLocale = JSON.parse(readFileSync(new URL('../src/locales/langs/en.json', import.meta.url), 'utf8'))
-
-test('menu page submit and delete handlers call the real backend menu APIs', () => {
-  assert.match(menuPageSource, /fetchSaveMenu/)
-  assert.match(menuPageSource, /fetchUpdateMenu/)
-  assert.match(menuPageSource, /fetchDeleteMenu/)
-  assert.doesNotMatch(menuPageSource, /console\.log\('鎻愪氦鏁版嵁:'/)
-  assert.match(menuPageSource, /const handleAddAuth = \(row\) =>/)
-  assert.match(menuPageSource, /const handleDeleteMenu = async \(row\) =>/)
-  assert.match(menuPageSource, /const handleDeleteAuth = async \(row\) =>/)
-  assert.match(menuPageSource, /await fetchDeleteMenu\(row\.id\)/)
-})
-
-test('menu dialog accepts edit data and parent menu options from the page', () => {
-  assert.match(menuDialogSource, /editData:/)
-  assert.match(menuDialogSource, /menuTreeOptions:/)
-  assert.match(menuDialogSource, /key: 'parentId'/)
-  assert.match(menuDialogSource, /type: 'treeselect'/)
-})
-
-test('menu page renders Iconify preview and current-system translated menu names', () => {
-  assert.ok(
-    menuPageSource.indexOf("label: '鑿滃崟鍚嶇О'") < menuPageSource.indexOf("label: '鍥炬爣棰勮'"),
-    '鑿滃崟鍚嶇О鍒楀簲淇濇寔鍦ㄥ浘鏍囬瑙堝垪涔嬪墠锛岄伩鍏嶆爲褰㈠睍寮�绠ご璺戝埌鍥炬爣鍒�'
-  )
-  assert.match(menuPageSource, /label:\s*'鍥炬爣棰勮'/)
-  assert.match(menuPageSource, /ArtSvgIcon/)
-  assert.match(menuPageSource, /rounded-md/)
-  assert.match(menuPageSource, /row\.meta\?\.title\s*\|\|\s*row\.name/)
-  assert.match(routerSource, /startsWith\('menu\.'\)|startsWith\("menu\."\)/)
-  assert.match(routerSource, /const fallbackTitle =/)
-  assert.match(routerSource, /title\.startsWith\('menus\.'\)/)
-  assert.match(routerSource, /i18n\.global\.te\(fallbackTitle\)/)
-  assert.equal(zhLocale.menu?.system, '绯荤粺璁剧疆')
-  assert.equal(zhLocale.menu?.basicInfo, '鍩虹淇℃伅')
-  assert.equal(zhLocale.menu?.aiParam, 'AI 鍙傛暟')
-  assert.equal(enLocale.menu?.system, 'System')
-  assert.equal(enLocale.menu?.basicInfo, 'BasicInfo')
-  assert.equal(enLocale.menu?.aiParam, 'AI Params')
-})
diff --git a/rsf-design/tests/system-role-print-export-page.test.mjs b/rsf-design/tests/system-role-print-export-page.test.mjs
deleted file mode 100644
index 3b557c6..0000000
--- a/rsf-design/tests/system-role-print-export-page.test.mjs
+++ /dev/null
@@ -1,97 +0,0 @@
-import assert from 'node:assert/strict'
-import { readFileSync } from 'node:fs'
-import test from 'node:test'
-
-import * as roleHelpers from '../src/views/system/role/rolePage.helpers.js'
-
-test('role report columns are the fixed business columns, not table utility columns', () => {
-  assert.deepEqual(
-    roleHelpers.getRoleReportColumns().map((column) => column.source),
-    ['name', 'code', 'statusText', 'memo', 'createTimeText', 'updateTimeText']
-  )
-})
-
-test('report columns keep visible order inside the role allowlist', () => {
-  assert.deepEqual(
-    roleHelpers.resolveRoleReportColumns([
-      { prop: 'selection', label: '鍕鹃��' },
-      { prop: 'status', label: '鐘舵��' },
-      { prop: 'name', label: '瑙掕壊鍚嶇О' },
-      { prop: 'deptName', label: '閮ㄩ棬鍚嶇О' },
-      { prop: 'operation', label: '鎿嶄綔' },
-      { prop: 'memo', label: '澶囨敞' }
-    ]),
-    [
-      { source: 'statusText', label: '鐘舵��' },
-      { source: 'name', label: '瑙掕壊鍚嶇О' },
-      { source: 'memo', label: '澶囨敞' }
-    ]
-  )
-})
-
-test('role print rows expose formatted status text', () => {
-  const rows = roleHelpers.buildRolePrintRows([
-    { name: '绠$悊鍛�', status: 1 },
-    { name: '璁垮', status: 0 }
-  ])
-
-  assert.equal(rows[0].statusText, '姝e父')
-  assert.equal(rows[1].statusText, '绂佺敤')
-})
-
-test('role report meta applies shared report style and title', () => {
-  const meta = roleHelpers.buildRoleReportMeta({
-    previewMeta: {
-      reportDate: '2026-03-29',
-      printedAt: '2026-03-29 10:57:25',
-      operator: 'ROOT'
-    },
-    count: 2,
-    titleAlign: 'left',
-    titleLevel: 'prominent'
-  })
-
-  assert.equal(meta.reportTitle, '瑙掕壊绠$悊鎶ヨ〃')
-  assert.equal(meta.reportDate, '2026-03-29')
-  assert.equal(meta.operator, 'ROOT')
-  assert.deepEqual(meta.reportStyle, {
-    titleAlign: 'left',
-    titleLevel: 'prominent',
-    orientation: 'portrait',
-    density: 'compact',
-    showSequence: true
-  })
-})
-
-test('role page uses helper report defaults as single source of truth', () => {
-  const indexSource = readFileSync(new URL('../src/views/system/role/index.vue', import.meta.url), 'utf8')
-  const meta = roleHelpers.buildRoleReportMeta()
-
-  assert.equal(roleHelpers.ROLE_REPORT_TITLE, '瑙掕壊绠$悊鎶ヨ〃')
-  assert.deepEqual(roleHelpers.ROLE_REPORT_STYLE, {
-    titleAlign: 'center',
-    titleLevel: 'strong'
-  })
-  assert.equal(meta.reportTitle, roleHelpers.ROLE_REPORT_TITLE)
-  assert.deepEqual(meta.reportStyle, {
-    ...roleHelpers.ROLE_REPORT_STYLE,
-    orientation: 'portrait',
-    density: 'compact',
-    showSequence: true
-  })
-  assert.match(
-    indexSource,
-    /import\s*\{[\s\S]*\bROLE_REPORT_STYLE\b,[\s\S]*\bROLE_REPORT_TITLE\b[\s\S]*\}\s*from '\.\/rolePage\.helpers'/
-  )
-  assert.match(
-    indexSource,
-    /<ListExportPrint[\s\S]*:report-title="reportTitle"[\s\S]*:preview-meta="resolvedPreviewMeta"[\s\S]*\/>/
-  )
-  assert.match(indexSource, /const reportTitle = ROLE_REPORT_TITLE/)
-  assert.doesNotMatch(indexSource, /const reportTitle = '瑙掕壊绠$悊鎶ヨ〃'/)
-  assert.doesNotMatch(indexSource, /const ROLE_REPORT_STYLE = \{/)
-  assert.match(
-    indexSource,
-    /const resolvedPreviewMeta = computed\(\(\) =>\s*buildRoleReportMeta\(\{\s*previewMeta: previewMeta\.value,\s*count: previewRows\.value\.length,\s*titleAlign: ROLE_REPORT_STYLE\.titleAlign,\s*titleLevel: ROLE_REPORT_STYLE\.titleLevel\s*\}\)\s*\)/
-  )
-})
diff --git a/rsf-design/tests/system-role-scope-contract.test.mjs b/rsf-design/tests/system-role-scope-contract.test.mjs
deleted file mode 100644
index c44030d..0000000
--- a/rsf-design/tests/system-role-scope-contract.test.mjs
+++ /dev/null
@@ -1,223 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import test from 'node:test'
-
-import {
-  buildRoleDialogModel,
-  buildRolePageQueryParams,
-  buildRoleSavePayload,
-  buildRoleScopeSubmitPayload,
-  buildRoleSearchParams,
-  getRolePaginationKey,
-  getRoleScopeConfig,
-  normalizeRoleScopeTreeData,
-  normalizeRoleListRow,
-  createRoleSearchState
-} from '../src/views/system/role/rolePage.helpers.js'
-import { resolveBackendMenuTitle } from '../src/utils/backend-menu-title.js'
-
-const rolePermissionDialogSource = fs.readFileSync(
-  new URL('../src/views/system/role/modules/role-permission-dialog.vue', import.meta.url),
-  'utf8'
-)
-const roleEditDialogSource = fs.readFileSync(
-  new URL('../src/views/system/role/modules/role-edit-dialog.vue', import.meta.url),
-  'utf8'
-)
-const roleIndexSource = fs.readFileSync(
-  new URL('../src/views/system/role/index.vue', import.meta.url),
-  'utf8'
-)
-const roleTableColumnsSource = fs.readFileSync(
-  new URL('../src/views/system/role/roleTable.columns.js', import.meta.url),
-  'utf8'
-)
-
-test('buildRoleSearchParams keeps real role search fields', () => {
-  assert.deepEqual(
-    buildRoleSearchParams({
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 1,
-      condition: 'admin'
-    }),
-    {
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 1,
-      condition: 'admin'
-    }
-  )
-})
-
-test('buildRolePageQueryParams merges paging and search fields', () => {
-  assert.deepEqual(
-    buildRolePageQueryParams({
-      current: 3,
-      size: 20,
-      name: '绠$悊鍛�'
-    }),
-    {
-      current: 3,
-      pageSize: 20,
-      name: '绠$悊鍛�'
-    }
-  )
-})
-
-test('role page uses backend pageSize pagination key', () => {
-  assert.deepEqual(getRolePaginationKey(), {
-    current: 'current',
-    size: 'pageSize'
-  })
-})
-
-test('buildRoleDialogModel normalizes backend role data into the form model', () => {
-  assert.deepEqual(
-    buildRoleDialogModel({
-      id: 7,
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 0
-    }),
-    {
-      id: 7,
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 0
-    }
-  )
-})
-
-test('buildRoleSavePayload submits backend role fields', () => {
-  assert.deepEqual(
-    buildRoleSavePayload({
-      id: 7,
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 1
-    }),
-    {
-      id: 7,
-      name: '绠$悊鍛�',
-      code: 'R_ADMIN',
-      memo: '鏍稿績瑙掕壊',
-      status: 1
-    }
-  )
-})
-
-test('normalizeRoleListRow exposes table friendly fields', () => {
-  const normalized = normalizeRoleListRow({
-    id: 7,
-    name: '绠$悊鍛�',
-    code: 'R_ADMIN',
-    memo: '鏍稿績瑙掕壊',
-    status: 1,
-    createTime: '2025-03-28 10:00:00',
-    updateTime: '2025-03-28 11:00:00'
-  })
-
-  assert.equal(normalized.name, '绠$悊鍛�')
-  assert.equal(normalized.statusBool, true)
-  assert.equal(normalized.statusText, '姝e父')
-  assert.equal(normalized.statusType, 'success')
-  assert.equal(normalized.createTimeText, '2025-03-28 10:00:00')
-  assert.equal(normalized.updateTimeText, '2025-03-28 11:00:00')
-})
-
-test('buildRoleScopeSubmitPayload normalizes checked keys for backend scope update', () => {
-  assert.deepEqual(
-    buildRoleScopeSubmitPayload(7, ['1', 2], ['3']),
-    {
-      id: 7,
-      menuIds: {
-        checked: [1, 2],
-        halfChecked: [3]
-      }
-    }
-  )
-})
-
-test('getRoleScopeConfig resolves the four backend scope contracts', () => {
-  assert.deepEqual(getRoleScopeConfig('menu'), {
-    scopeType: 'menu',
-    title: '缃戦〉鏉冮檺',
-    listUrl: '/role/scope/list',
-    treeUrl: '/menu/tree'
-  })
-  assert.deepEqual(getRoleScopeConfig('warehouse'), {
-    scopeType: 'warehouse',
-    title: '浠撳簱鏉冮檺',
-    listUrl: '/roleWarehouse/scope/list',
-    treeUrl: '/menuWarehouse/tree'
-  })
-  assert.throws(() => getRoleScopeConfig('invalid'), /Unsupported scope type/i)
-})
-
-test('normalizeRoleScopeTreeData folds menu auth buttons into child nodes', () => {
-  const tree = normalizeRoleScopeTreeData('menu', [
-    {
-      id: 1,
-      name: 'menu.role',
-      type: 0,
-      children: [
-        {
-          id: 2,
-          name: 'Query Role',
-          type: 1,
-          authority: 'system:role:list'
-        }
-      ]
-    }
-  ])
-
-  assert.equal(tree[0].label, 'menus.role')
-  assert.equal(tree[0].children[0].isAuthButton, true)
-  assert.equal(tree[0].children[0].authMark, 'system:role:list')
-})
-
-test('resolveBackendMenuTitle translates legacy backend menu keys into readable labels', () => {
-  assert.equal(resolveBackendMenuTitle('menu.role'), '瑙掕壊绠$悊')
-  assert.equal(resolveBackendMenuTitle('menus.aiParam'), 'AI 鍙傛暟')
-  assert.equal(resolveBackendMenuTitle('AI绠$悊涓績'), 'AI绠$悊涓績')
-})
-
-test('role permission dialog only loads the active scope instead of all scopes together', () => {
-  assert.match(rolePermissionDialogSource, /ensureScopeLoaded\(activeScopeType\.value, \{ force: true \}\)/)
-  assert.doesNotMatch(rolePermissionDialogSource, /loadAllScopeData/)
-})
-
-test('role permission dialog keeps scope tree search and readable labels', () => {
-  assert.match(rolePermissionDialogSource, /placeholder="鎼滅储鏉冮檺鏍�"/)
-  assert.match(rolePermissionDialogSource, /reloadSelection: false/)
-  assert.match(rolePermissionDialogSource, /resolveBackendMenuTitle/)
-})
-
-test('role edit dialog keeps code optional to match the backend contract', () => {
-  assert.match(roleEditDialogSource, /name: \[\{ required: true, message: '璇疯緭鍏ヨ鑹插悕绉�'/)
-  assert.doesNotMatch(roleEditDialogSource, /code: \[\{ required: true, message: '璇疯緭鍏ヨ鑹茬紪鐮�'/)
-})
-
-test('role page uses semantic auth aliases so backend permissions render actions', () => {
-  assert.match(roleIndexSource, /v-auth=\"'add'\"/)
-  assert.match(roleIndexSource, /v-auth=\"'delete'\"/)
-  assert.match(roleIndexSource, /v-auth=\"'query'\"/)
-  assert.match(roleTableColumnsSource, /auth: 'edit'/)
-  assert.match(roleTableColumnsSource, /auth: 'delete'/)
-})
-
-test('createRoleSearchState exposes the role search form model', () => {
-  assert.deepEqual(createRoleSearchState(), {
-    name: '',
-    code: '',
-    memo: '',
-    status: void 0,
-    condition: ''
-  })
-})
diff --git a/rsf-design/tests/system-user-page-contract.test.mjs b/rsf-design/tests/system-user-page-contract.test.mjs
deleted file mode 100644
index 484333b..0000000
--- a/rsf-design/tests/system-user-page-contract.test.mjs
+++ /dev/null
@@ -1,217 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-import {
-  buildUserDialogModel,
-  buildUserPageQueryParams,
-  buildUserSavePayload,
-  buildUserSearchParams,
-  getUserPaginationKey,
-  getUserStatusMeta,
-  mergeUserDetailRecord,
-  normalizeDeptTreeOptions,
-  normalizeRoleOptions,
-  normalizeUserListRow
-} from '../src/views/system/user/userPage.helpers.js'
-
-test('buildUserSearchParams keeps real user page search fields', () => {
-  assert.deepEqual(
-    buildUserSearchParams({
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      phone: '13800000000',
-      email: 'root@example.com',
-      status: 1,
-      deptId: 0,
-      roleIds: [3, 8],
-      code: 'A001',
-      sex: 1,
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      condition: 'root'
-    }),
-    {
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      phone: '13800000000',
-      email: 'root@example.com',
-      status: 1,
-      deptId: 0,
-      roleIds: [3, 8],
-      code: 'A001',
-      sex: 1,
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      condition: 'root'
-    }
-  )
-})
-
-test('buildUserPageQueryParams merges paging and search fields', () => {
-  assert.deepEqual(
-    buildUserPageQueryParams({
-      current: 2,
-      size: 20,
-      username: 'root',
-      condition: 'manager'
-    }),
-    {
-      current: 2,
-      pageSize: 20,
-      username: 'root',
-      condition: 'manager'
-    }
-  )
-})
-
-test('user page uses backend pageSize pagination key', () => {
-  assert.deepEqual(getUserPaginationKey(), {
-    current: 'current',
-    size: 'pageSize'
-  })
-})
-
-test('buildUserDialogModel normalizes backend edit data into the form model', () => {
-  assert.deepEqual(
-    buildUserDialogModel({
-      id: 7,
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      deptId: 1,
-      roles: [{ id: 3 }, { roleId: 8 }],
-      sex: 1,
-      code: 'A001',
-      phone: '13800000000',
-      email: 'root@example.com',
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      memo: 'memo',
-      status: 0
-    }),
-    {
-      id: 7,
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      deptId: 1,
-      roleIds: [3, 8],
-      userRoleIds: [3, 8],
-      password: '',
-      confirmPassword: '',
-      sex: 1,
-      code: 'A001',
-      phone: '13800000000',
-      email: 'root@example.com',
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      memo: 'memo',
-      status: 0
-    }
-  )
-})
-
-test('buildUserSavePayload submits roleIds and password for the backend', () => {
-  assert.deepEqual(
-    buildUserSavePayload({
-      id: 7,
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      deptId: 1,
-      userRoleIds: [3, 8],
-      password: 'secret',
-      confirmPassword: 'secret',
-      sex: 1,
-      code: 'A001',
-      phone: '13800000000',
-      email: 'root@example.com',
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      memo: 'memo',
-      status: 1
-    }),
-    {
-      id: 7,
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      deptId: 1,
-      roleIds: [3, 8],
-      password: 'secret',
-      sex: 1,
-      code: 'A001',
-      phone: '13800000000',
-      email: 'root@example.com',
-      realName: 'Vincent',
-      idCard: '330421199511233211',
-      memo: 'memo',
-      status: 1
-    }
-  )
-})
-
-test('normalizeUserListRow exposes table friendly fields', () => {
-  const normalized = normalizeUserListRow({
-    username: 'root',
-    nickname: '绠$悊鍛�',
-    deptLabel: '鐮斿彂閮�',
-    deptId$: '鐮斿彂閮�',
-    roleNames: '瓒呯骇绠$悊鍛樸�丱PS',
-    roles: [{ name: '瓒呯骇绠$悊鍛�' }, { code: 'OPS' }],
-    status: 1,
-    createTime$: '2025-03-28 10:00:00',
-    updateTime: '2025-03-28 11:00:00'
-  })
-
-  assert.equal(normalized.username, 'root')
-  assert.equal(normalized.deptLabel, '鐮斿彂閮�')
-  assert.equal(normalized.roleNames, '瓒呯骇绠$悊鍛樸�丱PS')
-  assert.equal(normalized.statusBool, true)
-  assert.equal(normalized.statusText, '姝e父')
-  assert.equal(normalized.statusType, 'success')
-  assert.equal(normalized.createTimeText, '2025-03-28 10:00:00')
-  assert.equal(normalized.updateTimeText, '2025-03-28 11:00:00')
-})
-
-test('mergeUserDetailRecord keeps list row roles when detail omits them', () => {
-  assert.deepEqual(
-    mergeUserDetailRecord(
-      {
-        id: 7,
-        username: 'root',
-        nickname: '绠$悊鍛�'
-      },
-      {
-        id: 7,
-        deptLabel: '鐮斿彂閮�',
-        roleNames: '瓒呯骇绠$悊鍛�',
-        roles: [{ id: 3, name: '瓒呯骇绠$悊鍛�' }]
-      }
-    ),
-    {
-      id: 7,
-      username: 'root',
-      nickname: '绠$悊鍛�',
-      deptLabel: '鐮斿彂閮�',
-      roleNames: '瓒呯骇绠$悊鍛�',
-      roles: [{ id: 3, name: '瓒呯骇绠$悊鍛�' }]
-    }
-  )
-})
-
-test('normalizeDeptTreeOptions and normalizeRoleOptions adapt lookup data', () => {
-  assert.deepEqual(
-    normalizeDeptTreeOptions([{ id: 1, name: '鎬婚儴', children: [{ id: 2, name: '鐮斿彂閮�' }] }]),
-    [{ value: 1, label: '鎬婚儴', children: [{ value: 2, label: '鐮斿彂閮�', children: [] }] }]
-  )
-
-  assert.deepEqual(
-    normalizeRoleOptions([{ id: 3, name: '绠$悊鍛�' }, { roleId: 8, code: 'OPS' }]),
-    [
-      { value: 3, label: '绠$悊鍛�' },
-      { value: 8, label: 'OPS' }
-    ]
-  )
-})
-
-test('getUserStatusMeta maps enabled and disabled states', () => {
-  assert.deepEqual(getUserStatusMeta(1), { type: 'success', text: '姝e父', bool: true })
-  assert.deepEqual(getUserStatusMeta(0), { type: 'danger', text: '绂佺敤', bool: false })
-})
diff --git a/rsf-design/tests/user-login-page-contract.test.mjs b/rsf-design/tests/user-login-page-contract.test.mjs
deleted file mode 100644
index 4291b8a..0000000
--- a/rsf-design/tests/user-login-page-contract.test.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import assert from 'node:assert/strict'
-import test from 'node:test'
-
-const { createUserLoginApiParams, userLoginPaginationKey } = await import(
-  '../src/views/system/user-login/userLoginTable.config.js'
-)
-
-test('user login page uses the backend pageSize pagination contract', () => {
-  assert.deepEqual(userLoginPaginationKey, {
-    current: 'current',
-    size: 'pageSize'
-  })
-})
-
-test('user login page builds initial params with pageSize instead of size', () => {
-  assert.deepEqual(
-    createUserLoginApiParams({
-      token: 'abc',
-      ip: '127.0.0.1',
-      system: 'wms'
-    }),
-    {
-      current: 1,
-      pageSize: 20,
-      token: 'abc',
-      ip: '127.0.0.1',
-      system: 'wms'
-    }
-  )
-})
diff --git a/rsf-design/tests/work-tab-icon-contract.test.mjs b/rsf-design/tests/work-tab-icon-contract.test.mjs
deleted file mode 100644
index 4e1111c..0000000
--- a/rsf-design/tests/work-tab-icon-contract.test.mjs
+++ /dev/null
@@ -1,17 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import path from 'node:path'
-import test from 'node:test'
-import { fileURLToPath } from 'node:url'
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const projectRoot = path.resolve(__dirname, '..')
-const workTabSource = fs.readFileSync(
-  path.join(projectRoot, 'src/components/core/layouts/art-work-tab/index.vue'),
-  'utf8'
-)
-
-test('work tabs only render the leading icon when a tab actually has an icon', () => {
-  assert.match(workTabSource, /<ArtSvgIcon\s+v-if="item\.icon"/)
-  assert.doesNotMatch(workTabSource, /<ArtSvgIcon\s+v-show="item\.icon"/)
-})
diff --git a/rsf-design/tests/worktab-icon-normalization-contract.test.mjs b/rsf-design/tests/worktab-icon-normalization-contract.test.mjs
deleted file mode 100644
index 510c4b8..0000000
--- a/rsf-design/tests/worktab-icon-normalization-contract.test.mjs
+++ /dev/null
@@ -1,19 +0,0 @@
-import assert from 'node:assert/strict'
-import fs from 'node:fs'
-import path from 'node:path'
-import test from 'node:test'
-import { fileURLToPath } from 'node:url'
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const projectRoot = path.resolve(__dirname, '..')
-const worktabSource = fs.readFileSync(
-  path.join(projectRoot, 'src/store/modules/worktab.js'),
-  'utf8'
-)
-
-test('worktab store normalizes persisted legacy icon names before rendering tabs', () => {
-  assert.match(worktabSource, /import\s+\{\s*normalizeIcon\s*\}\s+from\s+'@\/router\/adapters\/backendMenuAdapter\.js'/)
-  assert.match(worktabSource, /icon:\s*normalizeIcon\(/)
-  assert.match(worktabSource, /const validTabs = opened\.value\.filter\(\(tab\) => isTabRouteValid\(tab\)\)\.map\(normalizeTabState\)/)
-  assert.match(worktabSource, /opened\.value\s*=\s*validTabs/)
-})
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
index 3846664..b910240 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasContainerController.java
@@ -1,18 +1,25 @@
 package com.vincent.rsf.server.manager.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vincent.rsf.framework.common.Cools;
 import com.vincent.rsf.framework.common.R;
-import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.common.annotation.OperationLog;
 import com.vincent.rsf.server.common.domain.BaseParam;
 import com.vincent.rsf.server.common.domain.KeyValVo;
 import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.service.ListExportHandler;
+import com.vincent.rsf.server.common.service.ListExportService;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.manager.entity.BasContainer;
 import com.vincent.rsf.server.manager.entity.BasStation;
+import com.vincent.rsf.server.manager.entity.WarehouseAreas;
 import com.vincent.rsf.server.manager.service.BasContainerService;
+import com.vincent.rsf.server.manager.service.WarehouseAreasService;
 import com.vincent.rsf.server.system.controller.BaseController;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -25,6 +32,12 @@
 
     @Autowired
     private BasContainerService basContainerService;
+
+    @Autowired
+    private WarehouseAreasService warehouseAreasService;
+
+    @Autowired
+    private ListExportService listExportService;
 
     @PreAuthorize("hasAuthority('manager:basContainer:list')")
     @PostMapping("/basContainer/page")
@@ -123,7 +136,124 @@
     @PreAuthorize("hasAuthority('manager:basContainer:list')")
     @PostMapping("/basContainer/export")
     public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
-        ExcelUtil.build(ExcelUtil.create(basContainerService.list(), BasContainer.class), response);
+        Map<Long, String> areaNameMap = buildAreaNameMap();
+        listExportService.export(
+                map,
+                exportMap -> buildParam(exportMap, BaseParam.class),
+                new ListExportHandler<BasContainer, BaseParam>() {
+                    @Override
+                    public List<BasContainer> listByIds(List<Long> ids) {
+                        List<BasContainer> records = basContainerService.listByIds(ids);
+                        records.forEach(BasContainer::sortAreas);
+                        return records;
+                    }
+
+                    @Override
+                    public List<BasContainer> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) {
+                        PageParam<BasContainer, BaseParam> pageParam = new PageParam<>(baseParam, BasContainer.class);
+                        QueryWrapper<BasContainer> queryWrapper = pageParam.buildWrapper(true);
+                        List<BasContainer> records = basContainerService.list(queryWrapper);
+                        records.forEach(BasContainer::sortAreas);
+                        return records;
+                    }
+
+                    @Override
+                    public void fillExportFields(List<BasContainer> records) {
+                        records.forEach(BasContainer::sortAreas);
+                    }
+
+                    @Override
+                    public Map<String, Object> toExportRow(BasContainer record, List<ExcelUtil.ExportColumn> columns) {
+                        return buildExportRow(record, columns, areaNameMap);
+                    }
+
+                    @Override
+                    public String defaultReportTitle() {
+                        return "瀹瑰櫒瑙勫垯鎶ヨ〃";
+                    }
+                },
+                response
+        );
+    }
+
+    private Map<Long, String> buildAreaNameMap() {
+        Map<Long, String> areaNameMap = new HashMap<>();
+        for (WarehouseAreas area : warehouseAreasService.list()) {
+            if (area != null && area.getId() != null) {
+                areaNameMap.put(area.getId(), area.getName());
+            }
+        }
+        return areaNameMap;
+    }
+
+    private Map<String, Object> buildExportRow(
+            BasContainer record,
+            List<ExcelUtil.ExportColumn> columns,
+            Map<Long, String> areaNameMap
+    ) {
+        BeanWrapper beanWrapper = new BeanWrapperImpl(record);
+        Map<String, Object> row = new LinkedHashMap<>();
+        for (ExcelUtil.ExportColumn column : columns) {
+            Object value = resolveExportValue(record, column.getSource(), beanWrapper, areaNameMap);
+            row.put(column.getSource(), value);
+        }
+        return row;
+    }
+
+    private Object resolveExportValue(
+            BasContainer record,
+            String source,
+            BeanWrapper beanWrapper,
+            Map<Long, String> areaNameMap
+    ) {
+        if ("containerTypeText".equals(source)) {
+            return record.getContainerType$();
+        }
+        if ("areasText".equals(source)) {
+            return buildAreasText(record, areaNameMap);
+        }
+        if ("status".equals(source)) {
+            return record.getStatusBool() == null ? "" : (record.getStatusBool() ? "姝e父" : "鍐荤粨");
+        }
+        if ("createByText".equals(source)) {
+            return record.getCreateBy$();
+        }
+        if ("createTimeText".equals(source)) {
+            return record.getCreateTime$();
+        }
+        if ("updateByText".equals(source)) {
+            return record.getUpdateBy$();
+        }
+        if ("updateTimeText".equals(source)) {
+            return record.getUpdateTime$();
+        }
+        if (beanWrapper.isReadableProperty(source)) {
+            return beanWrapper.getPropertyValue(source);
+        }
+        return null;
+    }
+
+    private String buildAreasText(BasContainer record, Map<Long, String> areaNameMap) {
+        if (record == null || Cools.isEmpty(record.getAreas())) {
+            return "";
+        }
+        List<String> labels = new ArrayList<>();
+        for (Map<String, Object> area : record.getAreas()) {
+            if (area == null) {
+                continue;
+            }
+            Object idValue = area.get("id");
+            if (idValue == null) {
+                continue;
+            }
+            Long areaId = Long.valueOf(String.valueOf(idValue));
+            String label = areaNameMap.get(areaId);
+            if (Cools.isEmpty(label)) {
+                label = String.valueOf(idValue);
+            }
+            labels.add(label);
+        }
+        return String.join("銆�", labels);
     }
 
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/StockStatisticController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/StockStatisticController.java
index 7e8b86a..67ea781 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/StockStatisticController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/StockStatisticController.java
@@ -41,8 +41,10 @@
         BaseParam baseParam = buildParam(map, BaseParam.class);
         PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class);
         QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true);
-        wrapper.select("id, loc_code, day_time, task_type, task_status, barcode, maktx, matnr_code, batch, SUM(anfme) AS anfme, unit, fields_index,  create_time, update_time");
-        wrapper.groupBy("matnr_code, day_time");
+        wrapper.select("MIN(id) AS id, day_time, task_type, task_status, " +
+                "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, " +
+                "SUM(anfme) AS anfme, MAX(unit) AS unit");
+        wrapper.groupBy("day_time, task_type, task_status, matnr_code");
         return R.ok().add(stockStatisticService.page(pageParam, wrapper));
     }
 
@@ -52,8 +54,10 @@
         BaseParam baseParam = buildParam(map, BaseParam.class);
         PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class);
         QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true);
-        wrapper.select("id, loc_code, day_time, task_type, task_status, barcode, maktx, matnr_code, batch, SUM(anfme) AS anfme, unit, fields_index,  create_time, update_time");
-        wrapper.groupBy("matnr_code, day_time");
+        wrapper.select("MIN(id) AS id, day_time, task_type, task_status, " +
+                "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, " +
+                "SUM(anfme) AS anfme, MAX(unit) AS unit");
+        wrapper.groupBy("day_time, task_type, task_status, matnr_code");
         return R.ok().add(stockStatisticService.page(pageParam, wrapper));
     }
 
@@ -63,8 +67,10 @@
         BaseParam baseParam = buildParam(map, BaseParam.class);
         PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class);
         QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true);
-        wrapper.select("id, loc_code, day_time, task_type, task_status, barcode, maktx, matnr_code, batch,  SUM(anfme) anfme, unit, fields_index, create_by, update_by,  create_time, update_time");
-        wrapper.groupBy("matnr_code, day_time, task_type, task_status");
+        wrapper.select("MIN(id) AS id, loc_code, day_time, task_type, task_status, barcode, " +
+                "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, SUM(anfme) AS anfme, " +
+                "MAX(unit) AS unit, create_by, update_by, create_time, update_time");
+        wrapper.groupBy("loc_code, day_time, task_type, task_status, barcode, matnr_code, create_by, update_by, create_time, update_time");
         return R.ok().add(stockStatisticService.page(pageParam, wrapper));
     }
 
@@ -74,8 +80,10 @@
         BaseParam baseParam = buildParam(map, BaseParam.class);
         PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class);
         QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true);
-        wrapper.select("id, loc_code, day_time, task_type, task_status, barcode, maktx, matnr_code, batch,  SUM(anfme) anfme, unit, fields_index, create_by, update_by,  create_time, update_time");
-        wrapper.groupBy("matnr_code, day_time, task_type, task_status");
+        wrapper.select("MIN(id) AS id, loc_code, day_time, task_type, task_status, barcode, " +
+                "MAX(maktx) AS maktx, matnr_code, MAX(batch) AS batch, SUM(anfme) AS anfme, " +
+                "MAX(unit) AS unit, create_by, update_by, create_time, update_time");
+        wrapper.groupBy("loc_code, day_time, task_type, task_status, barcode, matnr_code, create_by, update_by, create_time, update_time");
         return R.ok().add(stockStatisticService.page(pageParam, wrapper));
     }
 
@@ -85,7 +93,7 @@
         BaseParam baseParam = buildParam(map, BaseParam.class);
         PageParam<StockStatistic, BaseParam> pageParam = new PageParam<>(baseParam, StockStatistic.class);
         QueryWrapper<StockStatistic> wrapper = pageParam.buildWrapper(true);
-        wrapper.select("id, day_time, COUNT( barcode ) `count`, " +
+        wrapper.select("MIN(id) AS id, day_time, COUNT(barcode) AS `count`, " +
                 "SUM( anfme ) anfme," +
                 "COUNT(IF (task_type = 1, 0, NULL)) in_anfme_count, " +
                 "COUNT(IF ( task_type = 101, 0, NULL)) out_anfme_count, " +

--
Gitblit v1.9.1