From 558552aba0f99081a670dc29169989f186a7c7cd Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 09 七月 2026 17:21:34 +0800
Subject: [PATCH] 银川 1.仓储物流页面开发、联调

---
 src/views/wls/stocktaking/plan/components/index.ts                  |    0 
 src/views/stocktaking/plan/index.vue                                |    0 
 src/views/salesnotice/components/select.vue                         |    0 
 src/views/barcode/config/data.ts                                    |    0 
 src/views/barcode/components/printer-label.vue                      |    0 
 src/views/warehouse/area/modules/form.vue                           |    0 
 src/views/warehouse/modules/form.vue                                |    0 
 src/views/wls/stocktaking/task/index.vue                            |  201 
 src/api/wls/batch/index.ts                                          |   90 
 src/views/wls/returnissue/modules/detail-form.vue                   |  104 
 src/views/wls/returnvendor/modules/line-list.vue                    |  237 
 src/api/wls/returnsales/detail/index.ts                             |   55 
 src/views/returnvendor/modules/detail-list.vue                      |    0 
 src/views/returnsales/modules/detail-form.vue                       |    0 
 src/api/wls/productsales/line/index.ts                              |   56 
 src/views/packages/modules/package-line-list.vue                    |    0 
 src/api/wls/productissue/index.ts                                   |   87 
 src/views/miscissue/index.vue                                       |    0 
 src/api/wls/returnissue/line/index.ts                               |   58 
 src/views/wls/itemreceipt/modules/detail-list.vue                   |    0 
 src/views/arrivalnotice/components/select.vue                       |    0 
 src/views/wls/arrivalnotice/index.vue                               |  179 
 src/views/wls/productsales/modules/line-list.vue                    |  236 
 src/views/wls/warehouse/area/modules/form.vue                       |  149 
 src/views/wls/stocktaking/task/data.ts                              |  600 +
 src/views/stocktaking/plan/modules/form.vue                         |    0 
 src/views/wls/arrivalnotice/modules/line-form.vue                   |   94 
 src/views/warehouse/data.ts                                         |    0 
 src/api/wls/productreceipt/line/index.ts                            |   57 
 src/views/productissue/modules/form.vue                             |    0 
 src/views/wls/sn/data.ts                                            |    0 
 src/views/wls/returnvendor/modules/detail-list.vue                  |    0 
 src/api/wls/productsales/index.ts                                   |   96 
 src/views/wls/returnsales/modules/detail-list.vue                   |    0 
 src/views/productsales/modules/detail-form.vue                      |    0 
 src/views/stocktaking/task/index.vue                                |    0 
 src/views/wls/itemreceipt/modules/line-form.vue                     |  104 
 vite.config.ts                                                      |    2 
 src/views/erp/stock/warehouseMapping/modules/form.vue               |   78 
 src/views/itemreceipt/modules/form.vue                              |    0 
 src/views/wls/returnissue/modules/line-form.vue                     |   95 
 src/views/stocktaking/plan/components/select-dialog.vue             |    0 
 src/views/wls/transfer/modules/detail-form.vue                      |  103 
 src/views/packages/modules/form.vue                                 |    0 
 src/views/productissue/modules/line-list.vue                        |    0 
 src/views/arrivalnotice/data.ts                                     |    0 
 src/views/wls/outsourcereceipt/modules/detail-form.vue              |  103 
 src/views/returnvendor/modules/line-form.vue                        |    0 
 src/views/miscreceipt/index.vue                                     |    0 
 src/views/wls/batch/components/index.ts                             |    0 
 src/views/outsourceissue/index.vue                                  |    0 
 src/views/wls/arrivalnotice/components/line-select-dialog.vue       |  203 
 src/views/wls/salesnotice/data.ts                                   |  362 
 src/views/erp/purchase/order/components/index.ts                    |    4 
 src/views/stocktaking/plan/modules/param-form.vue                   |    0 
 src/views/barcode/components/detail.vue                             |    0 
 src/views/wls/miscreceipt/modules/line-list.vue                     |  140 
 src/views/wls/outsourcereceipt/modules/line-form.vue                |   98 
 src/views/wls/barcode/index.vue                                     |  234 
 src/views/wls/barcode/config/modules/form.vue                       |   89 
 src/views/warehouse/components/index.ts                             |    0 
 src/api/wls/returnissue/index.ts                                    |   78 
 src/views/erp/stock/warehouseMapping/data.ts                        |  143 
 src/views/productreceipt/modules/line-form.vue                      |    0 
 src/api/wls/returnsales/line/index.ts                               |   57 
 src/views/wls/salesnotice/index.vue                                 |  168 
 src/views/productreceipt/modules/detail-list.vue                    |    0 
 src/views/arrivalnotice/modules/form.vue                            |    0 
 src/views/wls/productsales/modules/detail-list.vue                  |    0 
 src/views/wls/productsales/modules/form.vue                         |  270 
 src/api/wls/transfer/index.ts                                       |   92 
 src/views/wls/stocktaking/plan/modules/param-list.vue               |  131 
 src/views/returnsales/index.vue                                     |    0 
 src/views/wls/sn/modules/form.vue                                   |   62 
 src/views/wls/productreceipt/modules/line-list.vue                  |  254 
 src/views/wls/miscreceipt/modules/form.vue                          |  200 
 src/views/wls/transfer/index.vue                                    |  214 
 src/views/outsourcereceipt/index.vue                                |    0 
 src/views/stocktaking/plan/components/index.ts                      |    0 
 src/views/materialstock/components/select.vue                       |    0 
 src/views/packages/modules/line-form.vue                            |    0 
 src/views/stocktaking/plan/components/select.vue                    |    0 
 src/views/wls/salesnotice/components/select.vue                     |  141 
 src/views/wls/transfer/modules/line-form.vue                        |  102 
 src/views/erp/stock/warehouseMapping/index.vue                      |  132 
 src/views/wls/materialstock/data.ts                                 |  306 
 src/api/wls/productsales/detail/index.ts                            |   61 
 src/views/warehouse/location/data.ts                                |    0 
 src/api/wls/productproduce/line/index.ts                            |   37 
 src/views/arrivalnotice/components/select-dialog.vue                |    0 
 src/api/wls/stocktaking/plan/param/index.ts                         |   49 
 src/views/wls/miscissue/modules/line-form.vue                       |   98 
 src/api/wls/outsourcereceipt/line/index.ts                          |   59 
 src/views/productissue/modules/detail-form.vue                      |    0 
 src/views/wls/salesnotice/modules/form.vue                          |  182 
 src/views/wls/warehouse/components/area-select.vue                  |    0 
 src/views/wls/warehouse/location/index.vue                          |  218 
 src/api/wls/miscissue/index.ts                                      |   70 
 src/views/barcode/components/index.ts                               |    0 
 src/api/wls/barcode/index.ts                                        |   70 
 src/api/wls/returnvendor/line/index.ts                              |   53 
 src/views/itemreceipt/modules/line-list.vue                         |    0 
 src/views/miscreceipt/modules/line-list.vue                         |    0 
 src/views/wls/productissue/modules/detail-form.vue                  |  104 
 src/views/wls/salesnotice/components/line-select.vue                |  153 
 src/views/warehouse/components/area-select.vue                      |    0 
 src/views/warehouse/components/location-select.vue                  |    0 
 src/views/productsales/modules/line-form.vue                        |    0 
 src/views/wls/productreceipt/index.vue                              |  201 
 src/api/erp/warehouseMapping/index.ts                               |   68 
 src/api/wls/outsourcereceipt/detail/index.ts                        |   61 
 src/views/returnsales/modules/form.vue                              |    0 
 src/views/barcode/components/barcode.vue                            |    0 
 src/views/sn/modules/detail.vue                                     |    0 
 src/views/transfer/modules/line-form.vue                            |    0 
 src/views/returnvendor/modules/line-list.vue                        |    0 
 src/views/wls/productreceipt/modules/form.vue                       |  243 
 src/views/returnvendor/modules/form.vue                             |    0 
 src/views/productsales/data.ts                                      |    0 
 src/views/wls/itemreceipt/modules/detail-form.vue                   |  104 
 src/views/returnissue/modules/line-list.vue                         |    0 
 src/views/returnsales/modules/line-form.vue                         |    0 
 src/views/wls/productsales/modules/line-form.vue                    |  104 
 src/views/wls/outsourceissue/modules/line-list.vue                  |  227 
 src/api/wls/productreceipt/index.ts                                 |   91 
 src/api/wls/warehouse/area/index.ts                                 |   65 
 src/views/wls/salesnotice/components/select-dialog.vue              |  320 
 src/views/wls/barcode/config/index.vue                              |  150 
 src/views/wls/warehouse/area/index.vue                              |  211 
 src/views/salesnotice/components/line-select-dialog.vue             |    0 
 src/api/wls/materialstock/index.ts                                  |   61 
 src/views/wls/sn/modules/detail.vue                                 |  100 
 src/views/returnissue/modules/detail-form.vue                       |    0 
 src/views/packages/index.vue                                        |    0 
 src/views/salesnotice/components/index.ts                           |    0 
 src/views/stocktaking/task/data.ts                                  |    0 
 src/views/wls/outsourcereceipt/modules/line-list.vue                |  236 
 src/views/wls/packages/modules/form.vue                             |  193 
 src/views/stocktaking/plan/modules/param-list.vue                   |    0 
 src/api/wls/salesnotice/index.ts                                    |   65 
 src/views/wls/outsourcereceipt/index.vue                            |  201 
 src/views/outsourceissue/modules/detail-list.vue                    |    0 
 src/views/returnvendor/index.vue                                    |    0 
 src/views/wls/transfer/modules/form.vue                             |  258 
 src/views/productreceipt/modules/line-list.vue                      |    0 
 src/views/returnissue/modules/form.vue                              |    0 
 src/views/wls/returnsales/modules/line-list.vue                     |  247 
 src/views/wls/warehouse/location/modules/form.vue                   |  184 
 src/views/miscissue/data.ts                                         |    0 
 src/views/wls/barcode/components/index.ts                           |    0 
 src/views/productreceipt/modules/form.vue                           |    0 
 src/views/wls/itemreceipt/modules/line-list.vue                     |  241 
 src/api/wls/miscreceipt/line/index.ts                               |   57 
 src/api/wls/outsourceissue/line/index.ts                            |   55 
 src/views/wls/returnvendor/modules/detail-form.vue                  |  104 
 src/views/wls/arrivalnotice/components/select.vue                   |  141 
 src/views/wls/materialstock/index.vue                               |  175 
 src/api/wms/approval/index.ts                                       |   69 
 src/views/wls/batch/components/select.vue                           |  149 
 src/views/warehouse/area/data.ts                                    |    0 
 src/views/outsourcereceipt/modules/line-form.vue                    |    0 
 src/api/wls/miscreceipt/index.ts                                    |   72 
 src/views/wls/salesnotice/components/line-select-dialog.vue         |  198 
 src/views/warehouse/components/select.vue                           |    0 
 src/views/wls/salesnotice/components/index.ts                       |    0 
 src/views/wls/outsourcereceipt/modules/form.vue                     |  227 
 src/views/wls/barcode/config/data.ts                                |  114 
 src/views/wls/transfer/data.ts                                      |  647 +
 src/views/arrivalnotice/components/line-select-dialog.vue           |    0 
 src/views/wls/packages/modules/line-form.vue                        |   92 
 src/views/wls/returnvendor/modules/form.vue                         |  236 
 src/api/wls/barcode/config/index.ts                                 |   49 
 src/views/wls/productsales/modules/detail-form.vue                  |  110 
 src/views/wls/barcode/components/printer-label.vue                  |   30 
 src/views/wls/batch/components/select-dialog.vue                    |    0 
 src/views/wls/packages/components/index.ts                          |    0 
 src/views/productissue/data.ts                                      |    0 
 src/views/packages/components/index.ts                              |    0 
 src/views/wls/stocktaking/plan/index.vue                            |  176 
 src/api/wls/returnvendor/index.ts                                   |   88 
 src/views/wls/stocktaking/task/modules/result-form.vue              |  106 
 src/views/wls/warehouse/components/select.vue                       |    0 
 src/views/wls/miscissue/index.vue                                   |  187 
 src/views/miscreceipt/modules/line-form.vue                         |    0 
 src/views/wls/productissue/modules/line-form.vue                    |   94 
 src/api/wls/transfer/detail/index.ts                                |   59 
 src/views/wls/packages/data.ts                                      |  641 +
 src/views/batch/components/index.ts                                 |    0 
 src/views/salesnotice/modules/form.vue                              |    0 
 src/views/warehouse/location/modules/form.vue                       |    0 
 src/views/itemreceipt/modules/line-form.vue                         |    0 
 src/views/wls/returnissue/modules/form.vue                          |  222 
 src/views/wls/outsourceissue/index.vue                              |  201 
 src/views/returnissue/index.vue                                     |    0 
 src/views/wls/stocktaking/task/modules/form.vue                     |  226 
 src/api/wls/arrivalnotice/line/index.ts                             |   55 
 src/views/packages/data.ts                                          |    0 
 src/views/wls/returnissue/index.vue                                 |  213 
 src/views/wls/warehouse/components/index.ts                         |    0 
 src/views/sn/modules/form.vue                                       |    0 
 src/api/wls/outsourcereceipt/index.ts                               |   80 
 src/views/wls/barcode/components/detail.vue                         |  169 
 src/views/productreceipt/data.ts                                    |    0 
 src/views/returnissue/data.ts                                       |    0 
 src/views/wls/approval/config/modules/process-config-form.vue       |  113 
 src/views/wls/productissue/modules/form.vue                         |  232 
 src/views/wls/stocktaking/plan/modules/form.vue                     |  126 
 src/views/wls/returnissue/modules/detail-list.vue                   |    0 
 src/views/wls/stocktaking/plan/components/condition-value-input.vue |  204 
 src/views/wls/itemreceipt/data.ts                                   |  527 
 src/views/itemreceipt/modules/detail-form.vue                       |    0 
 src/views/wls/outsourceissue/modules/form.vue                       |  236 
 src/views/transfer/data.ts                                          |    0 
 src/views/wls/returnvendor/data.ts                                  |  530 
 src/api/wls/stocktaking/task/index.ts                               |   86 
 src/views/transfer/modules/detail-form.vue                          |    0 
 src/views/outsourcereceipt/modules/form.vue                         |    0 
 src/views/transfer/modules/form.vue                                 |    0 
 src/views/salesnotice/modules/line-list.vue                         |    0 
 src/api/wls/returnvendor/detail/index.ts                            |   59 
 src/views/arrivalnotice/components/line-select.vue                  |    0 
 src/views/barcode/config/index.vue                                  |    0 
 src/views/wls/stocktaking/task/modules/line-list.vue                |  151 
 src/views/outsourceissue/modules/detail-form.vue                    |    0 
 src/views/wls/productissue/index.vue                                |  219 
 src/views/batch/components/select.vue                               |    0 
 src/views/wls/arrivalnotice/components/line-select.vue              |  153 
 src/api/mes/wm/approval/index.ts                                    |   69 
 src/views/wls/outsourceissue/data.ts                                |  487 
 src/views/wls/barcode/data.ts                                       |  735 +
 src/views/wls/packages/components/select-dialog.vue                 |  231 
 src/views/productsales/modules/form.vue                             |    0 
 src/views/transfer/modules/line-list.vue                            |    0 
 src/views/outsourceissue/modules/form.vue                           |    0 
 src/views/arrivalnotice/modules/line-list.vue                       |    0 
 src/views/wls/miscissue/modules/line-list.vue                       |  140 
 src/views/warehouse/area/index.vue                                  |    0 
 src/api/wls/packages/index.ts                                       |   83 
 src/views/stocktaking/task/modules/form.vue                         |    0 
 src/views/barcode/index.vue                                         |    0 
 src/views/productissue/modules/detail-list.vue                      |    0 
 src/views/wls/barcode/components/barcode.vue                        |   51 
 src/views/wls/arrivalnotice/data.ts                                 |  354 
 src/views/productreceipt/index.vue                                  |    0 
 src/views/productsales/index.vue                                    |    0 
 src/views/sn/index.vue                                              |    0 
 src/views/wls/stocktaking/task/modules/result-list.vue              |  136 
 src/views/outsourcereceipt/modules/line-list.vue                    |    0 
 src/views/returnissue/modules/detail-list.vue                       |    0 
 src/views/wls/materialstock/components/index.ts                     |    0 
 src/views/outsourceissue/data.ts                                    |    0 
 src/views/wls/outsourceissue/modules/detail-list.vue                |    0 
 src/views/arrivalnotice/index.vue                                   |    0 
 src/api/wls/approval/index.ts                                       |   69 
 src/views/stocktaking/plan/components/condition-value-input.vue     |    0 
 src/views/stocktaking/plan/data.ts                                  |    0 
 src/views/packages/components/select.vue                            |    0 
 src/views/wls/materialstock/components/select.vue                   |  158 
 src/views/miscissue/modules/line-list.vue                           |    0 
 src/views/wls/miscreceipt/index.vue                                 |  187 
 src/api/wls/outsourceissue/index.ts                                 |   90 
 src/api/wls/stocktaking/task/line/index.ts                          |   72 
 src/views/wls/returnsales/modules/line-form.vue                     |  102 
 src/views/transfer/index.vue                                        |    0 
 src/api/wls/sn/index.ts                                             |   90 
 src/api/wls/stocktaking/task/result/index.ts                        |   66 
 src/api/wls/packages/line/index.ts                                  |   54 
 src/views/returnsales/data.ts                                       |    0 
 src/views/wls/packages/components/select.vue                        |  143 
 src/views/wls/barcode/modules/form.vue                              |  111 
 src/views/salesnotice/index.vue                                     |    0 
 src/views/wls/outsourcereceipt/data.ts                              |  540 
 src/views/miscreceipt/modules/form.vue                              |    0 
 src/views/arrivalnotice/components/index.ts                         |    0 
 src/views/productissue/index.vue                                    |    0 
 src/views/transfer/modules/detail-list.vue                          |    0 
 src/views/wls/approval/config/data.ts                               |  209 
 src/views/materialstock/index.vue                                   |    0 
 src/views/wls/salesnotice/modules/line-list.vue                     |  140 
 src/views/wls/productreceipt/modules/detail-form.vue                |  104 
 src/views/wls/stocktaking/plan/data.ts                              |  472 
 src/api/wls/itemreceipt/detail/index.ts                             |   58 
 src/views/returnsales/modules/detail-list.vue                       |    0 
 src/views/wls/warehouse/data.ts                                     |  221 
 src/views/itemreceipt/data.ts                                       |    0 
 src/views/outsourceissue/modules/line-form.vue                      |    0 
 src/views/sn/data.ts                                                |    0 
 src/views/wls/productsales/index.vue                                |  215 
 src/views/materialstock/components/index.ts                         |    0 
 src/views/wls/miscreceipt/data.ts                                   |  422 
 src/views/wls/returnvendor/index.vue                                |  201 
 src/views/wls/returnissue/data.ts                                   |  567 
 src/views/itemreceipt/modules/detail-list.vue                       |    0 
 src/views/wls/warehouse/area/data.ts                                |  338 
 src/api/wls/itemreceipt/index.ts                                    |   86 
 src/views/outsourcereceipt/modules/detail-list.vue                  |    0 
 src/views/materialstock/components/select-dialog.vue                |    0 
 src/api/wls/miscissue/line/index.ts                                 |   70 
 src/views/wls/materialstock/components/select-dialog.vue            |    0 
 src/views/wls/batch/data.ts                                         |  372 
 src/views/materialstock/data.ts                                     |    0 
 src/views/stocktaking/task/modules/result-list.vue                  |    0 
 src/views/wls/outsourceissue/modules/line-form.vue                  |   97 
 src/views/batch/components/detail.vue                               |    0 
 src/views/wls/itemreceipt/modules/form.vue                          |  242 
 src/views/wls/warehouse/index.vue                                   |  175 
 src/views/wls/arrivalnotice/components/select-dialog.vue            |  288 
 src/views/productsales/modules/line-list.vue                        |    0 
 src/views/miscissue/modules/form.vue                                |    0 
 src/views/returnvendor/modules/detail-form.vue                      |    0 
 src/views/wls/arrivalnotice/modules/line-list.vue                   |  140 
 src/views/stocktaking/task/modules/line-list.vue                    |    0 
 src/views/miscissue/modules/line-form.vue                           |    0 
 src/api/wls/returnissue/detail/index.ts                             |   56 
 src/views/wls/warehouse/location/data.ts                            |  193 
 src/views/salesnotice/modules/line-form.vue                         |    0 
 src/views/wls/returnsales/modules/detail-form.vue                   |  104 
 src/views/erp/purchase/order/components/select-dialog.vue           |  196 
 src/api/wls/outsourceissue/detail/index.ts                          |   61 
 src/views/productsales/modules/detail-list.vue                      |    0 
 src/views/packages/modules/sub-package-list.vue                     |    0 
 src/views/wls/returnvendor/modules/line-form.vue                    |   99 
 src/views/salesnotice/components/select-dialog.vue                  |    0 
 src/views/wls/itemreceipt/index.vue                                 |  201 
 src/views/wls/outsourceissue/modules/detail-form.vue                |  104 
 src/views/batch/data.ts                                             |    0 
 src/views/itemreceipt/index.vue                                     |    0 
 src/api/wls/itemconsume/line/index.ts                               |   37 
 src/views/wls/productreceipt/data.ts                                |  452 
 src/views/barcode/data.ts                                           |    0 
 src/api/wls/transfer/line/index.ts                                  |   55 
 src/api/wls/itemreceipt/line/index.ts                               |   63 
 src/views/wls/productreceipt/modules/line-form.vue                  |   95 
 src/api/wls/productissue/detail/index.ts                            |   59 
 src/views/wls/batch/components/detail.vue                           |   52 
 src/views/wls/returnsales/index.vue                                 |  213 
 src/views/wls/miscreceipt/modules/line-form.vue                     |   98 
 src/views/wls/transfer/modules/detail-list.vue                      |    0 
 src/views/wls/productissue/modules/line-list.vue                    |  223 
 src/views/barcode/modules/form.vue                                  |    0 
 src/views/wls/outsourcereceipt/modules/detail-list.vue              |    0 
 src/views/wls/returnsales/data.ts                                   |  515 
 src/api/wls/warehouse/index.ts                                      |   55 
 src/views/wls/packages/index.vue                                    |  160 
 src/views/wls/arrivalnotice/components/index.ts                     |    0 
 src/views/wls/salesnotice/modules/line-form.vue                     |   94 
 src/views/wls/warehouse/components/location-select.vue              |    0 
 src/views/warehouse/location/index.vue                              |    0 
 src/views/wls/productsales/data.ts                                  |  701 +
 src/api/wls/returnsales/index.ts                                    |   78 
 src/views/stocktaking/task/modules/result-form.vue                  |    0 
 src/views/salesnotice/data.ts                                       |    0 
 src/views/wls/stocktaking/plan/components/select-dialog.vue         |  213 
 src/views/outsourcereceipt/data.ts                                  |    0 
 src/views/returnvendor/data.ts                                      |    0 
 src/views/wls/sn/index.vue                                          |  177 
 src/views/wls/stocktaking/plan/components/select.vue                |  135 
 src/api/wls/productreceipt/detail/index.ts                          |   58 
 src/views/wls/productreceipt/modules/detail-list.vue                |    0 
 src/views/wls/returnsales/modules/form.vue                          |  231 
 src/views/wls/packages/modules/sub-package-list.vue                 |    0 
 src/views/outsourceissue/modules/line-list.vue                      |    0 
 src/views/erp/purchase/order/components/select.vue                  |  138 
 src/views/productreceipt/modules/detail-form.vue                    |    0 
 src/views/wls/miscissue/modules/form.vue                            |  199 
 src/api/wls/warehouse/location/index.ts                             |   72 
 src/api/wls/salesnotice/line/index.ts                               |   55 
 src/views/productissue/modules/line-form.vue                        |    0 
 src/views/wls/productissue/data.ts                                  |  475 
 src/views/batch/components/select-dialog.vue                        |    0 
 src/views/wls/returnissue/modules/line-list.vue                     |  250 
 src/api/wls/productissue/line/index.ts                              |   52 
 src/views/wls/miscissue/data.ts                                     |  452 
 src/api/wls/stocktaking/plan/index.ts                               |   68 
 src/views/wls/productissue/modules/detail-list.vue                  |    0 
 src/views/barcode/config/modules/form.vue                           |    0 
 src/views/wls/stocktaking/plan/modules/param-form.vue               |  108 
 src/api/wls/arrivalnotice/index.ts                                  |   64 
 src/views/wls/approval/config/index.vue                             |  113 
 src/views/wls/arrivalnotice/modules/form.vue                        |  171 
 src/views/miscreceipt/data.ts                                       |    0 
 src/views/salesnotice/components/line-select.vue                    |    0 
 src/views/packages/components/select-dialog.vue                     |    0 
 src/views/wls/transfer/modules/line-list.vue                        |  228 
 src/views/warehouse/index.vue                                       |    0 
 src/views/wls/warehouse/modules/form.vue                            |  124 
 src/views/arrivalnotice/modules/line-form.vue                       |    0 
 src/views/wls/packages/modules/package-line-list.vue                |  132 
 src/views/outsourcereceipt/modules/detail-form.vue                  |    0 
 src/views/returnissue/modules/line-form.vue                         |    0 
 src/views/returnsales/modules/line-list.vue                         |    0 
 391 files changed, 34,590 insertions(+), 1 deletions(-)

diff --git a/src/api/erp/warehouseMapping/index.ts b/src/api/erp/warehouseMapping/index.ts
new file mode 100644
index 0000000..245a938
--- /dev/null
+++ b/src/api/erp/warehouseMapping/index.ts
@@ -0,0 +1,68 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace ErpWarehouseMappingApi {
+  /** 浠撳簱鏄犲皠 */
+  export interface WarehouseMapping {
+    id?: number; // 鏄犲皠缂栧彿
+    erpWarehouseId: number; // ERP浠撳簱ID
+    erpWarehouseName?: string; // ERP浠撳簱鍚嶇О
+    mesWarehouseId: number; // MES浠撳簱ID
+    mesWarehouseName?: string; // MES浠撳簱鍚嶇О
+    status: number; // 鐘舵��(0:绂佺敤 1:鍚敤)
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鍒嗛〉鏌ヨ鏄犲皠鍒楄〃 */
+export function getMappingPage(params: PageParam) {
+  return requestClient.get<PageResult<ErpWarehouseMappingApi.WarehouseMapping>>(
+    '/erp/warehouse-mapping/page',
+    { params },
+  );
+}
+
+/** 鑾峰彇鍗曚釜鏄犲皠 */
+export function getMapping(id: number) {
+  return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping>(
+    '/erp/warehouse-mapping/get',
+    {
+      params: { id },
+    },
+  );
+}
+
+/** 鑾峰彇鏄犲皠绠�鍗曞垪琛� */
+export function getMappingSimpleList() {
+  return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping[]>(
+    '/erp/warehouse-mapping/simple-list',
+  );
+}
+
+/** 鏍规嵁ERP浠撳簱ID鑾峰彇MES浠撳簱鏄犲皠 */
+export function getMappingByErpWarehouseId(erpWarehouseId: number) {
+  return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping>(
+    '/erp/warehouse-mapping/get-by-erp',
+    {
+      params: { erpWarehouseId },
+    },
+  );
+}
+
+/** 鏂板鏄犲皠 */
+export function createMapping(data: ErpWarehouseMappingApi.WarehouseMapping) {
+  return requestClient.post('/erp/warehouse-mapping/create', data);
+}
+
+/** 鏇存柊鏄犲皠 */
+export function updateMapping(data: ErpWarehouseMappingApi.WarehouseMapping) {
+  return requestClient.put('/erp/warehouse-mapping/update', data);
+}
+
+/** 鍒犻櫎鏄犲皠 */
+export function deleteMapping(id: number) {
+  return requestClient.delete('/erp/warehouse-mapping/delete', {
+    params: { id },
+  });
+}
diff --git a/src/api/mes/wm/approval/index.ts b/src/api/mes/wm/approval/index.ts
new file mode 100644
index 0000000..d9fb0fa
--- /dev/null
+++ b/src/api/mes/wm/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WmsApprovalApi {
+  /** 瀹℃壒閰嶇疆 */
+  export interface ApprovalConfig {
+    id?: number;
+    warehouseId: number;
+    warehouseName?: string;
+    bizType: string;
+    approvalEnabled: boolean;
+    processDefinitionKey?: string;
+    processDefinitionName?: string;
+    categoryCode?: string;
+    createTime?: Date;
+  }
+
+  /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+  export interface ProcessSimpleInfo {
+    key: string;
+    name: string;
+  }
+
+  /** 涓氬姟绫诲瀷 */
+  export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig>(
+    '/mes/wms/approval/config/get',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+    '/mes/wms/approval/config/list',
+    {
+      params: { warehouseId },
+    },
+  );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+    '/mes/wms/approval/config/all-list',
+  );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WmsApprovalApi.ApprovalConfig) {
+  return requestClient.post('/mes/wms/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+  return requestClient.get<WmsApprovalApi.ProcessSimpleInfo[]>(
+    '/mes/wms/approval/process-list',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
\ No newline at end of file
diff --git a/src/api/wls/approval/index.ts b/src/api/wls/approval/index.ts
new file mode 100644
index 0000000..020bb3e
--- /dev/null
+++ b/src/api/wls/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsApprovalApi {
+  /** 瀹℃壒閰嶇疆 */
+  export interface ApprovalConfig {
+    id?: number;
+    warehouseId: number;
+    warehouseName?: string;
+    bizType: string;
+    approvalEnabled: boolean;
+    processDefinitionKey?: string;
+    processDefinitionName?: string;
+    categoryCode?: string;
+    createTime?: Date;
+  }
+
+  /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+  export interface ProcessSimpleInfo {
+    key: string;
+    name: string;
+  }
+
+  /** 涓氬姟绫诲瀷 */
+  export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+  return requestClient.get<WlsApprovalApi.ApprovalConfig>(
+    '/mes/wm/approval/config/get',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+  return requestClient.get<WlsApprovalApi.ApprovalConfig[]>(
+    '/mes/wm/approval/config/list',
+    {
+      params: { warehouseId },
+    },
+  );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+  return requestClient.get<WlsApprovalApi.ApprovalConfig[]>(
+    '/mes/wm/approval/config/all-list',
+  );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WlsApprovalApi.ApprovalConfig) {
+  return requestClient.post('/mes/wm/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+  return requestClient.get<WlsApprovalApi.ProcessSimpleInfo[]>(
+    '/mes/wm/approval/process-list',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
\ No newline at end of file
diff --git a/src/api/wls/arrivalnotice/index.ts b/src/api/wls/arrivalnotice/index.ts
new file mode 100644
index 0000000..5238aee
--- /dev/null
+++ b/src/api/wls/arrivalnotice/index.ts
@@ -0,0 +1,64 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsArrivalNoticeApi {
+  /** MES 鍒拌揣閫氱煡鍗� */
+  export interface ArrivalNotice {
+    id?: number; // 閫氱煡鍗曠紪鍙�
+    code?: string; // 閫氱煡鍗曠紪鍙�
+    name?: string; // 閫氱煡鍗曞悕绉�
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟缂栧彿
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    arrivalDate?: number; // 鍒拌揣鏃ユ湡
+    contactName?: string; // 鑱旂郴浜�
+    contactTelephone?: string; // 鑱旂郴鏂瑰紡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曞垎椤� */
+export function getArrivalNoticePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsArrivalNoticeApi.ArrivalNotice>>(
+    '/mes/wm/arrival-notice/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡鎯� */
+export function getArrivalNotice(id: number) {
+  return requestClient.get<WlsArrivalNoticeApi.ArrivalNotice>(
+    `/mes/wm/arrival-notice/get?id=${id}`,
+  );
+}
+
+/** 鏂板鍒拌揣閫氱煡鍗� */
+export function createArrivalNotice(data: WlsArrivalNoticeApi.ArrivalNotice) {
+  return requestClient.post<number>('/mes/wm/arrival-notice/create', data);
+}
+
+/** 淇敼鍒拌揣閫氱煡鍗� */
+export function updateArrivalNotice(data: WlsArrivalNoticeApi.ArrivalNotice) {
+  return requestClient.put('/mes/wm/arrival-notice/update', data);
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗� */
+export function deleteArrivalNotice(id: number) {
+  return requestClient.delete(`/mes/wm/arrival-notice/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍒拌揣閫氱煡鍗� */
+export function submitArrivalNotice(id: number) {
+  return requestClient.put(`/mes/wm/arrival-notice/submit?id=${id}`);
+}
+
+/** 瀵煎嚭鍒拌揣閫氱煡鍗� */
+export function exportArrivalNotice(params: any) {
+  return requestClient.download('/mes/wm/arrival-notice/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/arrivalnotice/line/index.ts b/src/api/wls/arrivalnotice/line/index.ts
new file mode 100644
index 0000000..1740b71
--- /dev/null
+++ b/src/api/wls/arrivalnotice/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsArrivalNoticeLineApi {
+  /** MES 鍒拌揣閫氱煡鍗曡 */
+  export interface ArrivalNoticeLine {
+    id?: number; // 琛岀紪鍙�
+    noticeId?: number; // 鍒拌揣閫氱煡鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    arrivalQuantity?: number; // 鍒拌揣鏁伴噺
+    qualifiedQuantity?: number; // 鍚堟牸鏁伴噺
+    iqcCheckFlag?: boolean; // 鏄惁妫�楠�
+    iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+    iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡鍒嗛〉 */
+export function getArrivalNoticeLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsArrivalNoticeLineApi.ArrivalNoticeLine>
+  >('/mes/wm/arrival-notice-line/page', { params });
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡璇︽儏 */
+export function getArrivalNoticeLine(id: number) {
+  return requestClient.get<WlsArrivalNoticeLineApi.ArrivalNoticeLine>(
+    `/mes/wm/arrival-notice-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板鍒拌揣閫氱煡鍗曡 */
+export function createArrivalNoticeLine(
+  data: WlsArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+  return requestClient.post<number>('/mes/wm/arrival-notice-line/create', data);
+}
+
+/** 淇敼鍒拌揣閫氱煡鍗曡 */
+export function updateArrivalNoticeLine(
+  data: WlsArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+  return requestClient.put('/mes/wm/arrival-notice-line/update', data);
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗曡 */
+export function deleteArrivalNoticeLine(id: number) {
+  return requestClient.delete(`/mes/wm/arrival-notice-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/barcode/config/index.ts b/src/api/wls/barcode/config/index.ts
new file mode 100644
index 0000000..0688d67
--- /dev/null
+++ b/src/api/wls/barcode/config/index.ts
@@ -0,0 +1,49 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBarcodeConfigApi {
+  /** MES 鏉$爜閰嶇疆 */
+  export interface BarcodeConfig {
+    id?: number; // 缂栧彿
+    format?: number; // 鏉$爜鏍煎紡
+    bizType?: number; // 涓氬姟绫诲瀷
+    contentFormat?: string; // 鍐呭鏍煎紡妯℃澘
+    contentExample?: string; // 鍐呭鏍蜂緥
+    autoGenerateFlag?: boolean; // 鏄惁鑷姩鐢熸垚
+    defaultTemplate?: string; // 榛樿鎵撳嵃妯℃澘
+    status?: number; // 鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鏉$爜閰嶇疆鍒嗛〉 */
+export function getBarcodeConfigPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsBarcodeConfigApi.BarcodeConfig>>(
+    '/mes/wm/barcode-config/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鏉$爜閰嶇疆璇︽儏 */
+export function getBarcodeConfig(id: number) {
+  return requestClient.get<WlsBarcodeConfigApi.BarcodeConfig>(
+    `/mes/wm/barcode-config/get?id=${id}`,
+  );
+}
+
+/** 鏂板鏉$爜閰嶇疆 */
+export function createBarcodeConfig(data: WlsBarcodeConfigApi.BarcodeConfig) {
+  return requestClient.post('/mes/wm/barcode-config/create', data);
+}
+
+/** 淇敼鏉$爜閰嶇疆 */
+export function updateBarcodeConfig(data: WlsBarcodeConfigApi.BarcodeConfig) {
+  return requestClient.put('/mes/wm/barcode-config/update', data);
+}
+
+/** 鍒犻櫎鏉$爜閰嶇疆 */
+export function deleteBarcodeConfig(id: number) {
+  return requestClient.delete(`/mes/wm/barcode-config/delete?id=${id}`);
+}
diff --git a/src/api/wls/barcode/index.ts b/src/api/wls/barcode/index.ts
new file mode 100644
index 0000000..e7812c0
--- /dev/null
+++ b/src/api/wls/barcode/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBarcodeApi {
+  /** MES 鏉$爜娓呭崟 */
+  export interface Barcode {
+    id?: number; // 鏉$爜缂栧彿
+    configId?: number; // 鏉$爜閰嶇疆缂栧彿
+    format?: number; // 鏉$爜鏍煎紡
+    bizType?: number; // 涓氬姟绫诲瀷
+    content?: string; // 鏉$爜鍐呭
+    bizId?: number; // 涓氬姟瀵硅薄缂栧彿
+    bizCode?: string; // 涓氬姟瀵硅薄缂栫爜
+    bizName?: string; // 涓氬姟瀵硅薄鍚嶇О
+    status?: number; // 鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鏉$爜鍒嗛〉 */
+export function getBarcodePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsBarcodeApi.Barcode>>(
+    '/mes/wm/barcode/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鏉$爜璇︽儏 */
+export function getBarcode(id: number) {
+  return requestClient.get<WlsBarcodeApi.Barcode>(
+    `/mes/wm/barcode/get?id=${id}`,
+  );
+}
+
+/** 鏍规嵁涓氬姟瀵硅薄鑾峰彇鏉$爜 */
+export function getBarcodeByBusiness(bizType: number, bizId: number) {
+  return requestClient.get<WlsBarcodeApi.Barcode>(
+    '/mes/wm/barcode/get-by-business',
+    { params: { bizId, bizType } },
+  );
+}
+
+/** 鏂板鏉$爜 */
+export function createBarcode(data: WlsBarcodeApi.Barcode) {
+  return requestClient.post('/mes/wm/barcode/create', data);
+}
+
+/** 淇敼鏉$爜 */
+export function updateBarcode(data: WlsBarcodeApi.Barcode) {
+  return requestClient.put('/mes/wm/barcode/update', data);
+}
+
+/** 鍒犻櫎鏉$爜 */
+export function deleteBarcode(id: number) {
+  return requestClient.delete(`/mes/wm/barcode/delete?id=${id}`);
+}
+
+/** 瀵煎嚭鏉$爜 */
+export function exportBarcode(params: any) {
+  return requestClient.download('/mes/wm/barcode/export-excel', { params });
+}
+
+/** 鐢熸垚鏉$爜鍐呭 */
+export function generateBarcodeContent(bizType: number, bizCode: string) {
+  return requestClient.get<string>('/mes/wm/barcode/generate-content', {
+    params: { bizCode, bizType },
+  });
+}
diff --git a/src/api/wls/batch/index.ts b/src/api/wls/batch/index.ts
new file mode 100644
index 0000000..fef1d78
--- /dev/null
+++ b/src/api/wls/batch/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBatchApi {
+  /** MES 鎵规 */
+  export interface Batch {
+    id?: number; // 鎵规 ID
+    code?: string; // 鎵规缂栫爜
+    itemId?: number; // 鐗╂枡 ID
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    itemSpecification?: string; // 瑙勬牸鍨嬪彿
+    unitName?: string; // 鍗曚綅鍚嶇О
+    produceDate?: Date; // 鐢熶骇鏃ユ湡
+    expireDate?: Date; // 鏈夋晥鏈�
+    receiptDate?: Date; // 鍏ュ簱鏃ユ湡
+    vendorId?: number; // 渚涘簲鍟� ID
+    vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    clientId?: number; // 瀹㈡埛 ID
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟缂栧彿
+    salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+    workOrderId?: number; // 鐢熶骇璁㈠崟 ID
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栧彿
+    taskId?: number; // 鐢熶骇浠诲姟 ID
+    taskCode?: string; // 鐢熶骇浠诲姟缂栧彿
+    workstationId?: number; // 宸ヤ綔绔� ID
+    workstationCode?: string; // 宸ヤ綔绔欑紪鐮�
+    toolId?: number; // 宸ュ叿 ID
+    toolCode?: string; // 宸ュ叿缂栧彿
+    moldId?: number; // 妯″叿 ID
+    lotNumber?: string; // 鐢熶骇鎵瑰彿
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    remark?: string; // 澶囨敞
+  }
+
+  /** MES 鎵规鍒嗛〉鏌ヨ鍙傛暟 */
+  export interface PageParams extends PageParam {
+    code?: string; // 鎵规鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    clientId?: number; // 瀹㈡埛缂栧彿
+    workOrderId?: number; // 璁㈠崟缂栧彿
+    taskId?: number; // 鐢熶骇浠诲姟缂栧彿
+    workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+    toolId?: number; // 宸ュ叿缂栧彿
+    moldId?: number; // 妯″叿缂栧彿
+    salesOrderCode?: string; // 閿�鍞鍗曞彿
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+    lotNumber?: string; // 鎵瑰彿
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    produceDate?: string[]; // 鐢熶骇鏃ユ湡
+    expireDate?: string[]; // 杩囨湡鏃ユ湡
+    receiptDate?: string[]; // 鍏ュ簱鏃ユ湡
+  }
+}
+
+/** 鏌ヨ鎵规璇︽儏 */
+export function getBatch(id: number) {
+  return requestClient.get<WlsBatchApi.Batch>(`/mes/wm/batch/get?id=${id}`);
+}
+
+/** 鏌ヨ鎵规鍒嗛〉 */
+export function getBatchPage(params: WlsBatchApi.PageParams) {
+  return requestClient.get<PageResult<WlsBatchApi.Batch>>(
+    '/mes/wm/batch/page',
+    { params },
+  );
+}
+
+/** 鎵规鍚戝墠杩芥函 */
+export function getForwardBatchList(code: string) {
+  return requestClient.get<WlsBatchApi.Batch[]>(
+    '/mes/wm/batch/forward-list',
+    {
+      params: { code },
+    },
+  );
+}
+
+/** 鎵规鍚戝悗杩芥函 */
+export function getBackwardBatchList(code: string) {
+  return requestClient.get<WlsBatchApi.Batch[]>(
+    '/mes/wm/batch/backward-list',
+    { params: { code } },
+  );
+}
diff --git a/src/api/wls/itemconsume/line/index.ts b/src/api/wls/itemconsume/line/index.ts
new file mode 100644
index 0000000..5c817ea
--- /dev/null
+++ b/src/api/wls/itemconsume/line/index.ts
@@ -0,0 +1,37 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemConsumeLineApi {
+  /** MES 鐗╂枡娑堣�楄 */
+  export interface ItemConsumeLine {
+    id?: number;
+    feedbackId?: number; // 鎶ュ伐缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╄祫缂栫爜
+    itemName?: string; // 鐗╄祫鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitId?: number; // 鍗曚綅缂栧彿
+    unitName?: string; // 鍗曚綅
+    quantity?: number; // 娑堣�楁暟閲�
+    batchCode?: string; // 鎵规鍙�
+    locationId?: number; // 搴撲綅缂栧彿
+    locationName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+
+  /** MES 鐗╂枡娑堣�楄鍒嗛〉鏌ヨ鍙傛暟 */
+  export interface PageParams extends PageParam {
+    feedbackId?: number;
+  }
+}
+
+/** 鏌ヨ鐗╂枡娑堣�楄鍒嗛〉 */
+export function getItemConsumeLinePage(
+  params: WlsItemConsumeLineApi.PageParams,
+) {
+  return requestClient.get<PageResult<WlsItemConsumeLineApi.ItemConsumeLine>>(
+    '/mes/wm/item-consume-line/page',
+    { params },
+  );
+}
diff --git a/src/api/wls/itemreceipt/detail/index.ts b/src/api/wls/itemreceipt/detail/index.ts
new file mode 100644
index 0000000..1311798
--- /dev/null
+++ b/src/api/wls/itemreceipt/detail/index.ts
@@ -0,0 +1,58 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptDetailApi {
+  /** MES 閲囪喘鍏ュ簱鏄庣粏 */
+  export interface ItemReceiptDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    lineId?: number; // 鍏ュ簱鍗曡缂栧彿
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getItemReceiptDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsItemReceiptDetailApi.ItemReceiptDetail[]>(
+    '/mes/wm/item-receipt-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鏄庣粏璇︽儏 */
+export function getItemReceiptDetail(id: number) {
+  return requestClient.get<WlsItemReceiptDetailApi.ItemReceiptDetail>(
+    `/mes/wm/item-receipt-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鏄庣粏 */
+export function createItemReceiptDetail(
+  data: WlsItemReceiptDetailApi.ItemReceiptDetail,
+) {
+  return requestClient.post<number>('/mes/wm/item-receipt-detail/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鏄庣粏 */
+export function updateItemReceiptDetail(
+  data: WlsItemReceiptDetailApi.ItemReceiptDetail,
+) {
+  return requestClient.put('/mes/wm/item-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鏄庣粏 */
+export function deleteItemReceiptDetail(id: number) {
+  return requestClient.delete(`/mes/wm/item-receipt-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/itemreceipt/index.ts b/src/api/wls/itemreceipt/index.ts
new file mode 100644
index 0000000..8c1265c
--- /dev/null
+++ b/src/api/wls/itemreceipt/index.ts
@@ -0,0 +1,86 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptApi {
+  /** MES 閲囪喘鍏ュ簱鍗� */
+  export interface ItemReceipt {
+    id?: number; // 鍏ュ簱鍗曠紪鍙�
+    code?: string; // 鍏ュ簱鍗曠紪鐮�
+    name?: string; // 鍏ュ簱鍗曞悕绉�
+    iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+    iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+    noticeId?: number; // 鍒拌揣閫氱煡鍗曠紪鍙�
+    noticeCode?: string; // 鍒拌揣閫氱煡鍗曠紪鐮�
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    receiptDate?: number; // 鍏ュ簱鏃ユ湡
+    status?: number; // 鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曞垎椤� */
+export function getItemReceiptPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsItemReceiptApi.ItemReceipt>>(
+    '/mes/wm/item-receipt/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡鎯� */
+export function getItemReceipt(id: number) {
+  return requestClient.get<WlsItemReceiptApi.ItemReceipt>(
+    `/mes/wm/item-receipt/get?id=${id}`,
+  );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鍗� */
+export function createItemReceipt(data: WlsItemReceiptApi.ItemReceipt) {
+  return requestClient.post<number>('/mes/wm/item-receipt/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鍗� */
+export function updateItemReceipt(data: WlsItemReceiptApi.ItemReceipt) {
+  return requestClient.put('/mes/wm/item-receipt/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗� */
+export function deleteItemReceipt(id: number) {
+  return requestClient.delete(`/mes/wm/item-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦閲囪喘鍏ュ簱鍗� */
+export function submitItemReceipt(id: number) {
+  return requestClient.put(`/mes/wm/item-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockItemReceipt(id: number) {
+  return requestClient.put(`/mes/wm/item-receipt/stock?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishItemReceipt(id: number) {
+  return requestClient.put(`/mes/wm/item-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷閲囪喘鍏ュ簱鍗� */
+export function cancelItemReceipt(id: number) {
+  return requestClient.put(`/mes/wm/item-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭閲囪喘鍏ュ簱鍗� */
+export function exportItemReceipt(params: any) {
+  return requestClient.download('/mes/wm/item-receipt/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/itemreceipt/line/index.ts b/src/api/wls/itemreceipt/line/index.ts
new file mode 100644
index 0000000..481e642
--- /dev/null
+++ b/src/api/wls/itemreceipt/line/index.ts
@@ -0,0 +1,63 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptLineApi {
+  /** MES 閲囪喘鍏ュ簱鍗曡 */
+  export interface ItemReceiptLine {
+    id?: number; // 琛岀紪鍙�
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    receiptCode?: string; // 鍏ュ簱鍗曠紪鐮�
+    arrivalNoticeLineId?: number; // 鍒拌揣閫氱煡鍗曡缂栧彿
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    receivedQuantity?: number; // 鍏ュ簱鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    productionDate?: number; // 鐢熶骇鏃ユ湡
+    expireDate?: number; // 鏈夋晥鏈�
+    lotNumber?: string; // 鐢熶骇鎵瑰彿
+    iqcCheckFlag?: boolean; // 鏄惁妫�楠�
+    iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+    iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡鍒嗛〉 */
+export function getItemReceiptLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsItemReceiptLineApi.ItemReceiptLine>>(
+    '/mes/wm/item-receipt-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡璇︽儏 */
+export function getItemReceiptLine(id: number) {
+  return requestClient.get<WlsItemReceiptLineApi.ItemReceiptLine>(
+    `/mes/wm/item-receipt-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鍗曡 */
+export function createItemReceiptLine(
+  data: WlsItemReceiptLineApi.ItemReceiptLine,
+) {
+  return requestClient.post<number>('/mes/wm/item-receipt-line/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鍗曡 */
+export function updateItemReceiptLine(
+  data: WlsItemReceiptLineApi.ItemReceiptLine,
+) {
+  return requestClient.put('/mes/wm/item-receipt-line/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗曡 */
+export function deleteItemReceiptLine(id: number) {
+  return requestClient.delete(`/mes/wm/item-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/materialstock/index.ts b/src/api/wls/materialstock/index.ts
new file mode 100644
index 0000000..2f03a8d
--- /dev/null
+++ b/src/api/wls/materialstock/index.ts
@@ -0,0 +1,61 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMaterialStockApi {
+  /** MES 搴撳瓨鍙拌处 */
+  export interface MaterialStock {
+    id?: number; // 缂栧彿
+    itemTypeId?: number; // 鐗╂枡鍒嗙被缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseCode?: string; // 浠撳簱缂栫爜
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    quantity?: number; // 鍦ㄥ簱鏁伴噺
+    receiptTime?: string; // 鍏ュ簱鏃ユ湡
+    frozen?: boolean; // 鏄惁鍐荤粨
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ搴撳瓨鍙拌处鍒嗛〉 */
+export function getMaterialStockPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsMaterialStockApi.MaterialStock>>(
+    '/mes/wm/material-stock/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ搴撳瓨鍙拌处璇︽儏 */
+export function getMaterialStock(id: number) {
+  return requestClient.get<WlsMaterialStockApi.MaterialStock>(
+    `/mes/wm/material-stock/get?id=${id}`,
+  );
+}
+
+/** 鏇存柊搴撳瓨鍐荤粨鐘舵�� */
+export function updateMaterialStockFrozen(data: {
+  frozen: boolean;
+  id: number;
+}) {
+  return requestClient.put('/mes/wm/material-stock/update-frozen', data);
+}
+
+/** 瀵煎嚭搴撳瓨鍙拌处 */
+export function exportMaterialStock(params: any) {
+  return requestClient.download('/mes/wm/material-stock/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/miscissue/index.ts b/src/api/wls/miscissue/index.ts
new file mode 100644
index 0000000..f08625e
--- /dev/null
+++ b/src/api/wls/miscissue/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscIssueApi {
+  /** MES 鏉傞」鍑哄簱鍗� */
+  export interface MiscIssue {
+    id?: number; // 缂栧彿
+    code?: string; // 鍑哄簱鍗曠紪鍙�
+    name?: string; // 鍑哄簱鍗曞悕绉�
+    type?: number; // 涓氬姟绫诲瀷
+    sourceDocType?: string; // 鏉ユ簮鍗曟嵁绫诲瀷
+    sourceDocId?: number; // 鏉ユ簮鍗曟嵁缂栧彿
+    sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
+    issueDate?: number; // 鍑哄簱鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曞垎椤� */
+export function getMiscIssuePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsMiscIssueApi.MiscIssue>>(
+    '/mes/wm/misc-issue/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鎯� */
+export function getMiscIssue(id: number) {
+  return requestClient.get<WlsMiscIssueApi.MiscIssue>(
+    `/mes/wm/misc-issue/get?id=${id}`,
+  );
+}
+
+/** 鏂板鏉傞」鍑哄簱鍗� */
+export function createMiscIssue(data: WlsMiscIssueApi.MiscIssue) {
+  return requestClient.post<number>('/mes/wm/misc-issue/create', data);
+}
+
+/** 淇敼鏉傞」鍑哄簱鍗� */
+export function updateMiscIssue(data: WlsMiscIssueApi.MiscIssue) {
+  return requestClient.put('/mes/wm/misc-issue/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗� */
+export function deleteMiscIssue(id: number) {
+  return requestClient.delete(`/mes/wm/misc-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦鏉傞」鍑哄簱鍗� */
+export function submitMiscIssue(id: number) {
+  return requestClient.put(`/mes/wm/misc-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鍑哄簱 */
+export function finishMiscIssue(id: number) {
+  return requestClient.put(`/mes/wm/misc-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷鏉傞」鍑哄簱鍗� */
+export function cancelMiscIssue(id: number) {
+  return requestClient.put(`/mes/wm/misc-issue/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鏉傞」鍑哄簱鍗� */
+export function exportMiscIssue(params: any) {
+  return requestClient.download('/mes/wm/misc-issue/export-excel', { params });
+}
diff --git a/src/api/wls/miscissue/line/index.ts b/src/api/wls/miscissue/line/index.ts
new file mode 100644
index 0000000..09ebe1d
--- /dev/null
+++ b/src/api/wls/miscissue/line/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscIssueLineApi {
+  /** MES 鏉傞」鍑哄簱鍗曡 */
+  export interface MiscIssueLine {
+    id?: number; // 缂栧彿
+    issueId?: number; // 鍑哄簱鍗曠紪鍙�
+    sourceDocLineId?: number; // 鏉ユ簮鍗曟嵁琛岀紪鍙�
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasure?: string; // 璁¢噺鍗曚綅
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 鍑哄簱鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseCode?: string; // 浠撳簱缂栫爜
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationCode?: string; // 搴撳尯缂栫爜
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaCode?: string; // 搴撲綅缂栫爜
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+    quantityMax?: number; // 鍙嚭搴撴渶澶ф暟閲忥紙鍓嶇鐢ㄤ簬闄愬埗鍑哄簱鏁伴噺锛屽彇鑷簱瀛樺湪搴撴暟閲忥級
+  }
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鍒嗛〉 */
+export function getMiscIssueLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsMiscIssueLineApi.MiscIssueLine>>(
+    '/mes/wm/misc-issue-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鍒楄〃 */
+export function getMiscIssueLineListByIssueId(issueId: number) {
+  return requestClient.get<WlsMiscIssueLineApi.MiscIssueLine[]>(
+    `/mes/wm/misc-issue-line/list-by-issue-id?issueId=${issueId}`,
+  );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡璇︽儏 */
+export function getMiscIssueLine(id: number) {
+  return requestClient.get<WlsMiscIssueLineApi.MiscIssueLine>(
+    `/mes/wm/misc-issue-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板鏉傞」鍑哄簱鍗曡 */
+export function createMiscIssueLine(data: WlsMiscIssueLineApi.MiscIssueLine) {
+  return requestClient.post('/mes/wm/misc-issue-line/create', data);
+}
+
+/** 淇敼鏉傞」鍑哄簱鍗曡 */
+export function updateMiscIssueLine(data: WlsMiscIssueLineApi.MiscIssueLine) {
+  return requestClient.put('/mes/wm/misc-issue-line/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗曡 */
+export function deleteMiscIssueLine(id: number) {
+  return requestClient.delete(`/mes/wm/misc-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/miscreceipt/index.ts b/src/api/wls/miscreceipt/index.ts
new file mode 100644
index 0000000..0253771
--- /dev/null
+++ b/src/api/wls/miscreceipt/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscReceiptApi {
+  /** MES 鏉傞」鍏ュ簱鍗� */
+  export interface MiscReceipt {
+    id?: number; // 缂栧彿
+    code?: string; // 鍏ュ簱鍗曠紪鍙�
+    name?: string; // 鍏ュ簱鍗曞悕绉�
+    type?: number; // 鏉傞」绫诲瀷
+    sourceDocType?: string; // 鏉ユ簮鍗曟嵁绫诲瀷
+    sourceDocId?: number; // 鏉ユ簮鍗曟嵁缂栧彿
+    sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
+    receiptDate?: number; // 鍏ュ簱鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曞垎椤� */
+export function getMiscReceiptPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsMiscReceiptApi.MiscReceipt>>(
+    '/mes/wm/misc-receipt/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡鎯� */
+export function getMiscReceipt(id: number) {
+  return requestClient.get<WlsMiscReceiptApi.MiscReceipt>(
+    `/mes/wm/misc-receipt/get?id=${id}`,
+  );
+}
+
+/** 鏂板鏉傞」鍏ュ簱鍗� */
+export function createMiscReceipt(data: WlsMiscReceiptApi.MiscReceipt) {
+  return requestClient.post<number>('/mes/wm/misc-receipt/create', data);
+}
+
+/** 淇敼鏉傞」鍏ュ簱鍗� */
+export function updateMiscReceipt(data: WlsMiscReceiptApi.MiscReceipt) {
+  return requestClient.put('/mes/wm/misc-receipt/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗� */
+export function deleteMiscReceipt(id: number) {
+  return requestClient.delete(`/mes/wm/misc-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦鏉傞」鍏ュ簱鍗� */
+export function submitMiscReceipt(id: number) {
+  return requestClient.put(`/mes/wm/misc-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishMiscReceipt(id: number) {
+  return requestClient.put(`/mes/wm/misc-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷鏉傞」鍏ュ簱鍗� */
+export function cancelMiscReceipt(id: number) {
+  return requestClient.put(`/mes/wm/misc-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鏉傞」鍏ュ簱鍗� */
+export function exportMiscReceipt(params: any) {
+  return requestClient.download('/mes/wm/misc-receipt/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/miscreceipt/line/index.ts b/src/api/wls/miscreceipt/line/index.ts
new file mode 100644
index 0000000..d8e915b
--- /dev/null
+++ b/src/api/wls/miscreceipt/line/index.ts
@@ -0,0 +1,57 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscReceiptLineApi {
+  /** MES 鏉傞」鍏ュ簱鍗曡 */
+  export interface MiscReceiptLine {
+    id?: number; // 缂栧彿
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 鍏ュ簱鏁伴噺
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡鍒楄〃 */
+export function getMiscReceiptLineListByReceiptId(receiptId: number) {
+  return requestClient.get<WlsMiscReceiptLineApi.MiscReceiptLine[]>(
+    `/mes/wm/misc-receipt-line/list-by-receipt-id?receiptId=${receiptId}`,
+  );
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡璇︽儏 */
+export function getMiscReceiptLine(id: number) {
+  return requestClient.get<WlsMiscReceiptLineApi.MiscReceiptLine>(
+    `/mes/wm/misc-receipt-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板鏉傞」鍏ュ簱鍗曡 */
+export function createMiscReceiptLine(
+  data: WlsMiscReceiptLineApi.MiscReceiptLine,
+) {
+  return requestClient.post('/mes/wm/misc-receipt-line/create', data);
+}
+
+/** 淇敼鏉傞」鍏ュ簱鍗曡 */
+export function updateMiscReceiptLine(
+  data: WlsMiscReceiptLineApi.MiscReceiptLine,
+) {
+  return requestClient.put('/mes/wm/misc-receipt-line/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗曡 */
+export function deleteMiscReceiptLine(id: number) {
+  return requestClient.delete(`/mes/wm/misc-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourceissue/detail/index.ts b/src/api/wls/outsourceissue/detail/index.ts
new file mode 100644
index 0000000..c183aec
--- /dev/null
+++ b/src/api/wls/outsourceissue/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueDetailApi {
+  /** MES 澶栧崗鍙戞枡鍗曟槑缁� */
+  export interface OutsourceIssueDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    lineId?: number; // 琛岀紪鍙�
+    issueId?: number; // 鍙戞枡鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 鏁伴噺
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规缂栫爜
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曟槑缁嗗垪琛� */
+export function getOutsourceIssueDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsOutsourceIssueDetailApi.OutsourceIssueDetail[]>(
+    '/mes/wm/outsource-issue-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曟槑缁嗚鎯� */
+export function getOutsourceIssueDetail(id: number) {
+  return requestClient.get<WlsOutsourceIssueDetailApi.OutsourceIssueDetail>(
+    `/mes/wm/outsource-issue-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗曟槑缁� */
+export function createOutsourceIssueDetail(
+  data: WlsOutsourceIssueDetailApi.OutsourceIssueDetail,
+) {
+  return requestClient.post('/mes/wm/outsource-issue-detail/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗曟槑缁� */
+export function updateOutsourceIssueDetail(
+  data: WlsOutsourceIssueDetailApi.OutsourceIssueDetail,
+) {
+  return requestClient.put('/mes/wm/outsource-issue-detail/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗曟槑缁� */
+export function deleteOutsourceIssueDetail(id: number) {
+  return requestClient.delete(`/mes/wm/outsource-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourceissue/index.ts b/src/api/wls/outsourceissue/index.ts
new file mode 100644
index 0000000..b1adf8e
--- /dev/null
+++ b/src/api/wls/outsourceissue/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueApi {
+  /** MES 澶栧崗鍙戞枡鍗� */
+  export interface OutsourceIssue {
+    id?: number; // 鍙戞枡鍗曠紪鍙�
+    code?: string; // 鍙戞枡鍗曠紪鍙�
+    name?: string; // 鍙戞枡鍗曞悕绉�
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+    workOrderName?: string; // 鐢熶骇璁㈠崟鍚嶇О
+    issueDate?: number; // 鍙戞枡鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曞垎椤� */
+export function getOutsourceIssuePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsOutsourceIssueApi.OutsourceIssue>>(
+    '/mes/wm/outsource-issue/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡鎯� */
+export function getOutsourceIssue(id: number) {
+  return requestClient.get<WlsOutsourceIssueApi.OutsourceIssue>(
+    `/mes/wm/outsource-issue/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗� */
+export function createOutsourceIssue(
+  data: WlsOutsourceIssueApi.OutsourceIssue,
+) {
+  return requestClient.post<number>('/mes/wm/outsource-issue/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗� */
+export function updateOutsourceIssue(
+  data: WlsOutsourceIssueApi.OutsourceIssue,
+) {
+  return requestClient.put('/mes/wm/outsource-issue/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗� */
+export function deleteOutsourceIssue(id: number) {
+  return requestClient.delete(`/mes/wm/outsource-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦澶栧崗鍙戞枡鍗� */
+export function submitOutsourceIssue(id: number) {
+  return requestClient.put(`/mes/wm/outsource-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockOutsourceIssue(id: number) {
+  return requestClient.put(`/mes/wm/outsource-issue/stock?id=${id}`);
+}
+
+/** 鎵ц棰嗗嚭 */
+export function finishOutsourceIssue(id: number) {
+  return requestClient.put(`/mes/wm/outsource-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷澶栧崗鍙戞枡鍗� */
+export function cancelOutsourceIssue(id: number) {
+  return requestClient.put(`/mes/wm/outsource-issue/cancel?id=${id}`);
+}
+
+/** 鏍¢獙澶栧崗鍙戞枡鍗曟嫞璐ф暟閲忔槸鍚︿笌鍙戞枡鏁伴噺涓�鑷� */
+export function checkOutsourceIssueQuantity(id: number) {
+  return requestClient.get<boolean>(
+    `/mes/wm/outsource-issue/check-quantity?id=${id}`,
+  );
+}
+
+/** 瀵煎嚭澶栧崗鍙戞枡鍗� */
+export function exportOutsourceIssue(params: any) {
+  return requestClient.download('/mes/wm/outsource-issue/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/outsourceissue/line/index.ts b/src/api/wls/outsourceissue/line/index.ts
new file mode 100644
index 0000000..4d373a1
--- /dev/null
+++ b/src/api/wls/outsourceissue/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueLineApi {
+  /** MES 澶栧崗鍙戞枡鍗曡 */
+  export interface OutsourceIssueLine {
+    id?: number; // 琛岀紪鍙�
+    issueId?: number; // 鍙戞枡鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 鍙戞枡鏁伴噺
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规缂栫爜
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡鍒嗛〉 */
+export function getOutsourceIssueLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsOutsourceIssueLineApi.OutsourceIssueLine>
+  >('/mes/wm/outsource-issue-line/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡璇︽儏 */
+export function getOutsourceIssueLine(id: number) {
+  return requestClient.get<WlsOutsourceIssueLineApi.OutsourceIssueLine>(
+    `/mes/wm/outsource-issue-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗曡 */
+export function createOutsourceIssueLine(
+  data: WlsOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+  return requestClient.post('/mes/wm/outsource-issue-line/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗曡 */
+export function updateOutsourceIssueLine(
+  data: WlsOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+  return requestClient.put('/mes/wm/outsource-issue-line/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗曡 */
+export function deleteOutsourceIssueLine(id: number) {
+  return requestClient.delete(`/mes/wm/outsource-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourcereceipt/detail/index.ts b/src/api/wls/outsourcereceipt/detail/index.ts
new file mode 100644
index 0000000..45d4da2
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptDetailApi {
+  /** MES 澶栧崗鍏ュ簱鍗曟槑缁� */
+  export interface OutsourceReceiptDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    lineId?: number; // 琛岀紪鍙�
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 涓婃灦鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规缂栫爜
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曟槑缁嗗垪琛� */
+export function getOutsourceReceiptDetailListByLineId(lineId: number) {
+  return requestClient.get<
+    WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail[]
+  >('/mes/wm/outsource-receipt-detail/list-by-line', { params: { lineId } });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曟槑缁嗚鎯� */
+export function getOutsourceReceiptDetail(id: number) {
+  return requestClient.get<WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail>(
+    `/mes/wm/outsource-receipt-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗曟槑缁� */
+export function createOutsourceReceiptDetail(
+  data: WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail,
+) {
+  return requestClient.post('/mes/wm/outsource-receipt-detail/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗曟槑缁� */
+export function updateOutsourceReceiptDetail(
+  data: WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail,
+) {
+  return requestClient.put('/mes/wm/outsource-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗曟槑缁� */
+export function deleteOutsourceReceiptDetail(id: number) {
+  return requestClient.delete(
+    `/mes/wm/outsource-receipt-detail/delete?id=${id}`,
+  );
+}
diff --git a/src/api/wls/outsourcereceipt/index.ts b/src/api/wls/outsourcereceipt/index.ts
new file mode 100644
index 0000000..e5d495f
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/index.ts
@@ -0,0 +1,80 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptApi {
+  /** MES 澶栧崗鍏ュ簱鍗� */
+  export interface OutsourceReceipt {
+    id?: number; // 鍏ュ簱鍗曠紪鍙�
+    code?: string; // 鍏ュ簱鍗曠紪鐮�
+    name?: string; // 鍏ュ簱鍗曞悕绉�
+    workOrderId?: number; // 澶栧崗璁㈠崟缂栧彿
+    workOrderCode?: string; // 澶栧崗璁㈠崟缂栫爜
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    receiptDate?: number; // 鍏ュ簱鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曞垎椤� */
+export function getOutsourceReceiptPage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsOutsourceReceiptApi.OutsourceReceipt>
+  >('/mes/wm/outsource-receipt/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡鎯� */
+export function getOutsourceReceipt(id: number) {
+  return requestClient.get<WlsOutsourceReceiptApi.OutsourceReceipt>(
+    `/mes/wm/outsource-receipt/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗� */
+export function createOutsourceReceipt(
+  data: WlsOutsourceReceiptApi.OutsourceReceipt,
+) {
+  return requestClient.post<number>('/mes/wm/outsource-receipt/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗� */
+export function updateOutsourceReceipt(
+  data: WlsOutsourceReceiptApi.OutsourceReceipt,
+) {
+  return requestClient.put('/mes/wm/outsource-receipt/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗� */
+export function deleteOutsourceReceipt(id: number) {
+  return requestClient.delete(`/mes/wm/outsource-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦澶栧崗鍏ュ簱鍗� */
+export function submitOutsourceReceipt(id: number) {
+  return requestClient.put(`/mes/wm/outsource-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockOutsourceReceipt(id: number) {
+  return requestClient.put(`/mes/wm/outsource-receipt/stock?id=${id}`);
+}
+
+/** 瀹屾垚鍏ュ簱 */
+export function finishOutsourceReceipt(id: number) {
+  return requestClient.put(`/mes/wm/outsource-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷澶栧崗鍏ュ簱鍗� */
+export function cancelOutsourceReceipt(id: number) {
+  return requestClient.put(`/mes/wm/outsource-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭澶栧崗鍏ュ簱鍗� */
+export function exportOutsourceReceipt(params: any) {
+  return requestClient.download('/mes/wm/outsource-receipt/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/outsourcereceipt/line/index.ts b/src/api/wls/outsourcereceipt/line/index.ts
new file mode 100644
index 0000000..af03c02
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/line/index.ts
@@ -0,0 +1,59 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptLineApi {
+  /** MES 澶栧崗鍏ュ簱鍗曡 */
+  export interface OutsourceReceiptLine {
+    id?: number; // 琛岀紪鍙�
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 鍏ュ簱鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规缂栫爜
+    productionDate?: number; // 鐢熶骇鏃ユ湡
+    expireDate?: number; // 鏈夋晥鏈�
+    lotNumber?: string; // 鐢熶骇鎵瑰彿
+    iqcCheckFlag?: boolean; // 鏄惁闇�瑕佽川妫�
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡鍒嗛〉 */
+export function getOutsourceReceiptLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsOutsourceReceiptLineApi.OutsourceReceiptLine>
+  >('/mes/wm/outsource-receipt-line/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡璇︽儏 */
+export function getOutsourceReceiptLine(id: number) {
+  return requestClient.get<WlsOutsourceReceiptLineApi.OutsourceReceiptLine>(
+    `/mes/wm/outsource-receipt-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗曡 */
+export function createOutsourceReceiptLine(
+  data: WlsOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+  return requestClient.post('/mes/wm/outsource-receipt-line/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗曡 */
+export function updateOutsourceReceiptLine(
+  data: WlsOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+  return requestClient.put('/mes/wm/outsource-receipt-line/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗曡 */
+export function deleteOutsourceReceiptLine(id: number) {
+  return requestClient.delete(`/mes/wm/outsource-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/packages/index.ts b/src/api/wls/packages/index.ts
new file mode 100644
index 0000000..e1e2a4f
--- /dev/null
+++ b/src/api/wls/packages/index.ts
@@ -0,0 +1,83 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsPackageApi {
+  /** 瑁呯鍗� */
+  export interface Package {
+    id?: number; // 瑁呯鍗曠紪鍙�
+    code?: string; // 瑁呯鍗曠紪鐮�
+    parentId?: number; // 鐖剁缂栧彿
+    packageDate?: number; // 瑁呯鏃ユ湡
+    salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+    invoiceCode?: string; // 鍙戠エ缂栧彿
+    clientId?: number; // 瀹㈡埛缂栧彿
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    clientNickname?: string; // 瀹㈡埛绠�绉�
+    length?: number; // 绠遍暱搴�
+    width?: number; // 绠卞搴�
+    height?: number; // 绠遍珮搴�
+    sizeUnitId?: number; // 灏哄鍗曚綅缂栧彿
+    sizeUnitName?: string; // 灏哄鍗曚綅鍚嶇О
+    netWeight?: number; // 鍑�閲�
+    grossWeight?: number; // 姣涢噸
+    weightUnitId?: number; // 閲嶉噺鍗曚綅缂栧彿
+    weightUnitName?: string; // 閲嶉噺鍗曚綅鍚嶇О
+    inspectorUserId?: number; // 妫�鏌ュ憳鐢ㄦ埛缂栧彿
+    inspectorName?: string; // 妫�鏌ュ憳鍚嶇О
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+    children?: Package[]; // 瀛愮鍒楄〃
+  }
+}
+
+/** 鏌ヨ瑁呯鍗曞垎椤� */
+export function getPackagePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsPackageApi.Package>>(
+    '/mes/wm/package/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ瑁呯鍗曡鎯� */
+export function getPackage(id: number) {
+  return requestClient.get<WlsPackageApi.Package>(
+    `/mes/wm/package/get?id=${id}`,
+  );
+}
+
+/** 鏂板瑁呯鍗� */
+export function createPackage(data: WlsPackageApi.Package) {
+  return requestClient.post<number>('/mes/wm/package/create', data);
+}
+
+/** 淇敼瑁呯鍗� */
+export function updatePackage(data: WlsPackageApi.Package) {
+  return requestClient.put('/mes/wm/package/update', data);
+}
+
+/** 鍒犻櫎瑁呯鍗� */
+export function deletePackage(id: number) {
+  return requestClient.delete(`/mes/wm/package/delete?id=${id}`);
+}
+
+/** 瀹屾垚瑁呯鍗� */
+export function finishPackage(id: number) {
+  return requestClient.put(`/mes/wm/package/finish?id=${id}`);
+}
+
+/** 娣诲姞瀛愮 */
+export function addChildPackage(parentId: number, childId: number) {
+  return requestClient.put('/mes/wm/package/add-child-package', null, {
+    params: { childId, parentId },
+  });
+}
+
+/** 绉婚櫎瀛愮 */
+export function removeChildPackage(childId: number) {
+  return requestClient.put(
+    `/mes/wm/package/remove-child-package?childId=${childId}`,
+  );
+}
diff --git a/src/api/wls/packages/line/index.ts b/src/api/wls/packages/line/index.ts
new file mode 100644
index 0000000..7bce713
--- /dev/null
+++ b/src/api/wls/packages/line/index.ts
@@ -0,0 +1,54 @@
+锘縤mport type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsPackageLineApi {
+  /** 瑁呯鏄庣粏 */
+  export interface PackageLine {
+    id?: number; // 瑁呯鏄庣粏缂栧彿
+    packageId?: number; // 瑁呯鍗曠紪鍙�
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    itemId?: number; // 浜у搧鐗╂枡缂栧彿
+    itemCode?: string; // 浜у搧鐗╂枡缂栫爜
+    itemName?: string; // 浜у搧鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    quantity?: number; // 瑁呯鏁伴噺
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    expireDate?: number; // 鏈夋晥鏈�
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ瑁呯鏄庣粏鍒嗛〉 */
+export function getPackageLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsPackageLineApi.PackageLine>>(
+    '/mes/wm/package-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ瑁呯鏄庣粏璇︽儏 */
+export function getPackageLine(id: number) {
+  return requestClient.get<WlsPackageLineApi.PackageLine>(
+    `/mes/wm/package-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板瑁呯鏄庣粏 */
+export function createPackageLine(data: WlsPackageLineApi.PackageLine) {
+  return requestClient.post('/mes/wm/package-line/create', data);
+}
+
+/** 淇敼瑁呯鏄庣粏 */
+export function updatePackageLine(data: WlsPackageLineApi.PackageLine) {
+  return requestClient.put('/mes/wm/package-line/update', data);
+}
+
+/** 鍒犻櫎瑁呯鏄庣粏 */
+export function deletePackageLine(id: number) {
+  return requestClient.delete(`/mes/wm/package-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productissue/detail/index.ts b/src/api/wls/productissue/detail/index.ts
new file mode 100644
index 0000000..9d6f584
--- /dev/null
+++ b/src/api/wls/productissue/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueDetailApi {
+  /** MES 棰嗘枡鍑哄簱鏄庣粏 */
+  export interface ProductIssueDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    issueId?: number; // 棰嗘枡鍗曠紪鍙�
+    lineId?: number; // 棰嗘枡鍗曡缂栧彿
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getProductIssueDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsProductIssueDetailApi.ProductIssueDetail[]>(
+    '/mes/wm/product-issue-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鏄庣粏璇︽儏 */
+export function getProductIssueDetail(id: number) {
+  return requestClient.get<WlsProductIssueDetailApi.ProductIssueDetail>(
+    `/mes/wm/product-issue-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鏄庣粏 */
+export function createProductIssueDetail(
+  data: WlsProductIssueDetailApi.ProductIssueDetail,
+) {
+  return requestClient.post<number>(
+    '/mes/wm/product-issue-detail/create',
+    data,
+  );
+}
+
+/** 淇敼棰嗘枡鍑哄簱鏄庣粏 */
+export function updateProductIssueDetail(
+  data: WlsProductIssueDetailApi.ProductIssueDetail,
+) {
+  return requestClient.put('/mes/wm/product-issue-detail/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鏄庣粏 */
+export function deleteProductIssueDetail(id: number) {
+  return requestClient.delete(`/mes/wm/product-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productissue/index.ts b/src/api/wls/productissue/index.ts
new file mode 100644
index 0000000..8d4d440
--- /dev/null
+++ b/src/api/wls/productissue/index.ts
@@ -0,0 +1,87 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueApi {
+  /** MES 棰嗘枡鍑哄簱鍗� */
+  export interface ProductIssue {
+    id?: number; // 棰嗘枡鍗曠紪鍙�
+    code?: string; // 棰嗘枡鍗曠紪鍙�
+    name?: string; // 棰嗘枡鍗曞悕绉�
+    workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+    workstationCode?: string; // 宸ヤ綔绔欑紪鐮�
+    workstationName?: string; // 宸ヤ綔绔欏悕绉�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    requiredTime?: number; // 闇�姹傛椂闂�
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曞垎椤� */
+export function getProductIssuePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsProductIssueApi.ProductIssue>>(
+    '/mes/wm/product-issue/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡鎯� */
+export function getProductIssue(id: number) {
+  return requestClient.get<WlsProductIssueApi.ProductIssue>(
+    `/mes/wm/product-issue/get?id=${id}`,
+  );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鍗� */
+export function createProductIssue(data: WlsProductIssueApi.ProductIssue) {
+  return requestClient.post<number>('/mes/wm/product-issue/create', data);
+}
+
+/** 淇敼棰嗘枡鍑哄簱鍗� */
+export function updateProductIssue(data: WlsProductIssueApi.ProductIssue) {
+  return requestClient.put('/mes/wm/product-issue/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗� */
+export function deleteProductIssue(id: number) {
+  return requestClient.delete(`/mes/wm/product-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦棰嗘枡鍑哄簱鍗� */
+export function submitProductIssue(id: number) {
+  return requestClient.put(`/mes/wm/product-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockProductIssue(id: number) {
+  return requestClient.put(`/mes/wm/product-issue/stock?id=${id}`);
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱鍗� */
+export function finishProductIssue(id: number) {
+  return requestClient.put(`/mes/wm/product-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷棰嗘枡鍑哄簱鍗� */
+export function cancelProductIssue(id: number) {
+  return requestClient.put(`/mes/wm/product-issue/cancel?id=${id}`);
+}
+
+/** 鏍¢獙棰嗘枡鍑哄簱鍗曟嫞璐ф暟閲忔槸鍚︿笌棰嗘枡鏁伴噺涓�鑷� */
+export function checkProductIssueQuantity(id: number) {
+  return requestClient.get<boolean>(
+    `/mes/wm/product-issue/check-quantity?id=${id}`,
+  );
+}
+
+/** 瀵煎嚭棰嗘枡鍑哄簱鍗� */
+export function exportProductIssue(params: any) {
+  return requestClient.download('/mes/wm/product-issue/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/productissue/line/index.ts b/src/api/wls/productissue/line/index.ts
new file mode 100644
index 0000000..f74552f
--- /dev/null
+++ b/src/api/wls/productissue/line/index.ts
@@ -0,0 +1,52 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueLineApi {
+  /** MES 棰嗘枡鍑哄簱鍗曡 */
+  export interface ProductIssueLine {
+    id?: number; // 琛岀紪鍙�
+    issueId?: number; // 棰嗘枡鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 棰嗘枡鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡鍒嗛〉 */
+export function getProductIssueLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsProductIssueLineApi.ProductIssueLine>
+  >('/mes/wm/product-issue-line/page', { params });
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡璇︽儏 */
+export function getProductIssueLine(id: number) {
+  return requestClient.get<WlsProductIssueLineApi.ProductIssueLine>(
+    `/mes/wm/product-issue-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鍗曡 */
+export function createProductIssueLine(
+  data: WlsProductIssueLineApi.ProductIssueLine,
+) {
+  return requestClient.post<number>('/mes/wm/product-issue-line/create', data);
+}
+
+/** 淇敼棰嗘枡鍑哄簱鍗曡 */
+export function updateProductIssueLine(
+  data: WlsProductIssueLineApi.ProductIssueLine,
+) {
+  return requestClient.put('/mes/wm/product-issue-line/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗曡 */
+export function deleteProductIssueLine(id: number) {
+  return requestClient.delete(`/mes/wm/product-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productproduce/line/index.ts b/src/api/wls/productproduce/line/index.ts
new file mode 100644
index 0000000..4649ad5
--- /dev/null
+++ b/src/api/wls/productproduce/line/index.ts
@@ -0,0 +1,37 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductProduceLineApi {
+  /** MES 浜у搧浜у嚭琛� */
+  export interface ProductProduceLine {
+    id?: number;
+    feedbackId?: number; // 鎶ュ伐缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╄祫缂栫爜
+    itemName?: string; // 鐗╄祫鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureId?: number; // 鍗曚綅缂栧彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 浜у嚭鏁伴噺
+    batchCode?: string; // 鎵规鍙�
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    locationId?: number; // 搴撲綅缂栧彿
+    locationName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+
+  /** MES 浜у搧浜у嚭琛屽垎椤垫煡璇㈠弬鏁� */
+  export interface PageParams extends PageParam {
+    feedbackId?: number;
+  }
+}
+
+/** 鏌ヨ浜у搧浜у嚭琛屽垎椤� */
+export function getProductProduceLinePage(
+  params: WlsProductProduceLineApi.PageParams,
+) {
+  return requestClient.get<
+    PageResult<WlsProductProduceLineApi.ProductProduceLine>
+  >('/mes/wm/product-produce-line/page', { params });
+}
diff --git a/src/api/wls/productreceipt/detail/index.ts b/src/api/wls/productreceipt/detail/index.ts
new file mode 100644
index 0000000..f21c9c4
--- /dev/null
+++ b/src/api/wls/productreceipt/detail/index.ts
@@ -0,0 +1,58 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptDetailApi {
+  /** MES 浜у搧鍏ュ簱鏄庣粏 */
+  export interface ProductReceiptDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    lineId?: number; // 鍏ュ簱鍗曡缂栧彿
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getProductReceiptDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsProductReceiptDetailApi.ProductReceiptDetail[]>(
+    '/mes/wm/product-receipt-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鏄庣粏璇︽儏 */
+export function getProductReceiptDetail(id: number) {
+  return requestClient.get<WlsProductReceiptDetailApi.ProductReceiptDetail>(
+    `/mes/wm/product-receipt-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板浜у搧鍏ュ簱鏄庣粏 */
+export function createProductReceiptDetail(
+  data: WlsProductReceiptDetailApi.ProductReceiptDetail,
+) {
+  return requestClient.post<number>(
+    '/mes/wm/product-receipt-detail/create',
+    data,
+  );
+}
+
+/** 淇敼浜у搧鍏ュ簱鏄庣粏 */
+export function updateProductReceiptDetail(
+  data: WlsProductReceiptDetailApi.ProductReceiptDetail,
+) {
+  return requestClient.put('/mes/wm/product-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鏄庣粏 */
+export function deleteProductReceiptDetail(id: number) {
+  return requestClient.delete(`/mes/wm/product-receipt-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productreceipt/index.ts b/src/api/wls/productreceipt/index.ts
new file mode 100644
index 0000000..918c31b
--- /dev/null
+++ b/src/api/wls/productreceipt/index.ts
@@ -0,0 +1,91 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptApi {
+  /** MES 浜у搧鍏ュ簱鍗� */
+  export interface ProductReceipt {
+    id?: number; // 鍏ュ簱鍗曠紪鍙�
+    code?: string; // 鍏ュ簱鍗曠紪鐮�
+    name?: string; // 鍏ュ簱鍗曞悕绉�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+    itemId?: number; // 浜у搧鐗╂枡缂栧彿
+    itemCode?: string; // 浜у搧鐗╂枡缂栫爜
+    itemName?: string; // 浜у搧鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    receiptDate?: number; // 鍏ュ簱鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曞垎椤� */
+export function getProductReceiptPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsProductReceiptApi.ProductReceipt>>(
+    '/mes/wm/product-receipt/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡鎯� */
+export function getProductReceipt(id: number) {
+  return requestClient.get<WlsProductReceiptApi.ProductReceipt>(
+    `/mes/wm/product-receipt/get?id=${id}`,
+  );
+}
+
+/** 鏂板浜у搧鍏ュ簱鍗� */
+export function createProductReceipt(
+  data: WlsProductReceiptApi.ProductReceipt,
+) {
+  return requestClient.post<number>('/mes/wm/product-receipt/create', data);
+}
+
+/** 淇敼浜у搧鍏ュ簱鍗� */
+export function updateProductReceipt(
+  data: WlsProductReceiptApi.ProductReceipt,
+) {
+  return requestClient.put('/mes/wm/product-receipt/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗� */
+export function deleteProductReceipt(id: number) {
+  return requestClient.delete(`/mes/wm/product-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦浜у搧鍏ュ簱鍗� */
+export function submitProductReceipt(id: number) {
+  return requestClient.put(`/mes/wm/product-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockProductReceipt(id: number) {
+  return requestClient.put(`/mes/wm/product-receipt/stock?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishProductReceipt(id: number) {
+  return requestClient.put(`/mes/wm/product-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷浜у搧鍏ュ簱鍗� */
+export function cancelProductReceipt(id: number) {
+  return requestClient.put(`/mes/wm/product-receipt/cancel?id=${id}`);
+}
+
+/** 鏍¢獙浜у搧鍏ュ簱鍗曟槑缁嗘暟閲忔槸鍚︿笌琛屾敹璐ф暟閲忎竴鑷� */
+export function checkProductReceiptQuantity(id: number) {
+  return requestClient.get<boolean>(
+    `/mes/wm/product-receipt/check-quantity?id=${id}`,
+  );
+}
+
+/** 瀵煎嚭浜у搧鍏ュ簱鍗� */
+export function exportProductReceipt(params: any) {
+  return requestClient.download('/mes/wm/product-receipt/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/productreceipt/line/index.ts b/src/api/wls/productreceipt/line/index.ts
new file mode 100644
index 0000000..2a96c0f
--- /dev/null
+++ b/src/api/wls/productreceipt/line/index.ts
@@ -0,0 +1,57 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptLineApi {
+  /** MES 浜у搧鍏ュ簱鍗曡 */
+  export interface ProductReceiptLine {
+    id?: number; // 琛岀紪鍙�
+    receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 鍏ュ簱鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡鍒嗛〉 */
+export function getProductReceiptLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsProductReceiptLineApi.ProductReceiptLine>
+  >('/mes/wm/product-receipt-line/page', { params });
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡璇︽儏 */
+export function getProductReceiptLine(id: number) {
+  return requestClient.get<WlsProductReceiptLineApi.ProductReceiptLine>(
+    `/mes/wm/product-receipt-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板浜у搧鍏ュ簱鍗曡 */
+export function createProductReceiptLine(
+  data: WlsProductReceiptLineApi.ProductReceiptLine,
+) {
+  return requestClient.post<number>(
+    '/mes/wm/product-receipt-line/create',
+    data,
+  );
+}
+
+/** 淇敼浜у搧鍏ュ簱鍗曡 */
+export function updateProductReceiptLine(
+  data: WlsProductReceiptLineApi.ProductReceiptLine,
+) {
+  return requestClient.put('/mes/wm/product-receipt-line/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗曡 */
+export function deleteProductReceiptLine(id: number) {
+  return requestClient.delete(`/mes/wm/product-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productsales/detail/index.ts b/src/api/wls/productsales/detail/index.ts
new file mode 100644
index 0000000..ab9b0d0
--- /dev/null
+++ b/src/api/wls/productsales/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesDetailApi {
+  /** MES 閿�鍞嚭搴撴槑缁� */
+  export interface ProductSalesDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    lineId?: number; // 鍑哄簱鍗曡缂栧彿
+    salesId?: number; // 鍑哄簱鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    quantity?: number; // 鏁伴噺
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撴槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getProductSalesDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsProductSalesDetailApi.ProductSalesDetail[]>(
+    '/mes/wm/product-sales-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ閿�鍞嚭搴撴槑缁嗚鎯� */
+export function getProductSalesDetail(id: number) {
+  return requestClient.get<WlsProductSalesDetailApi.ProductSalesDetail>(
+    `/mes/wm/product-sales-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞嚭搴撴槑缁� */
+export function createProductSalesDetail(
+  data: WlsProductSalesDetailApi.ProductSalesDetail,
+) {
+  return requestClient.post<number>(
+    '/mes/wm/product-sales-detail/create',
+    data,
+  );
+}
+
+/** 淇敼閿�鍞嚭搴撴槑缁� */
+export function updateProductSalesDetail(
+  data: WlsProductSalesDetailApi.ProductSalesDetail,
+) {
+  return requestClient.put('/mes/wm/product-sales-detail/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撴槑缁� */
+export function deleteProductSalesDetail(id: number) {
+  return requestClient.delete(`/mes/wm/product-sales-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productsales/index.ts b/src/api/wls/productsales/index.ts
new file mode 100644
index 0000000..d97981c
--- /dev/null
+++ b/src/api/wls/productsales/index.ts
@@ -0,0 +1,96 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesApi {
+  /** MES 閿�鍞嚭搴撳崟 */
+  export interface ProductSales {
+    id?: number; // 鍑哄簱鍗曠紪鍙�
+    code?: string; // 鍑哄簱鍗曠紪鍙�
+    name?: string; // 鍑哄簱鍗曞悕绉�
+    noticeId?: number; // 鍙戣揣閫氱煡鍗曠紪鍙�
+    noticeCode?: string; // 鍙戣揣閫氱煡鍗曠紪鐮�
+    clientId?: number; // 瀹㈡埛缂栧彿
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+    salesDate?: number; // 鍑哄簱鏃ユ湡
+    contactName?: string; // 鏀惰揣浜�
+    contactTelephone?: string; // 鑱旂郴鏂瑰紡
+    contactAddress?: string; // 鏀惰揣鍦板潃
+    carrier?: string; // 鎵胯繍鍟�
+    shippingNumber?: string; // 杩愯緭鍗曞彿
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟鍒嗛〉 */
+export function getProductSalesPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsProductSalesApi.ProductSales>>(
+    '/mes/wm/product-sales/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟璇︽儏 */
+export function getProductSales(id: number) {
+  return requestClient.get<WlsProductSalesApi.ProductSales>(
+    `/mes/wm/product-sales/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞嚭搴撳崟 */
+export function createProductSales(data: WlsProductSalesApi.ProductSales) {
+  return requestClient.post<number>('/mes/wm/product-sales/create', data);
+}
+
+/** 淇敼閿�鍞嚭搴撳崟 */
+export function updateProductSales(data: WlsProductSalesApi.ProductSales) {
+  return requestClient.put('/mes/wm/product-sales/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟 */
+export function deleteProductSales(id: number) {
+  return requestClient.delete(`/mes/wm/product-sales/delete?id=${id}`);
+}
+
+/** 鎻愪氦閿�鍞嚭搴撳崟 */
+export function submitProductSales(id: number) {
+  return requestClient.put(`/mes/wm/product-sales/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockProductSales(id: number) {
+  return requestClient.put(`/mes/wm/product-sales/stock?id=${id}`);
+}
+
+/** 濉啓杩愬崟 */
+export function shippingProductSales(data: WlsProductSalesApi.ProductSales) {
+  return requestClient.put('/mes/wm/product-sales/shipping', data);
+}
+
+/** 鎵ц鍑哄簱 */
+export function finishProductSales(id: number) {
+  return requestClient.put(`/mes/wm/product-sales/finish?id=${id}`);
+}
+
+/** 鍙栨秷閿�鍞嚭搴撳崟 */
+export function cancelProductSales(id: number) {
+  return requestClient.put(`/mes/wm/product-sales/cancel?id=${id}`);
+}
+
+/** 鏍¢獙閿�鍞嚭搴撳崟鎷h揣鏁伴噺鏄惁涓庡嚭搴撴暟閲忎竴鑷� */
+export function checkProductSalesQuantity(id: number) {
+  return requestClient.get<boolean>(
+    `/mes/wm/product-sales/check-quantity?id=${id}`,
+  );
+}
+
+/** 瀵煎嚭閿�鍞嚭搴撳崟 */
+export function exportProductSales(params: any) {
+  return requestClient.download('/mes/wm/product-sales/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/productsales/line/index.ts b/src/api/wls/productsales/line/index.ts
new file mode 100644
index 0000000..7da238c
--- /dev/null
+++ b/src/api/wls/productsales/line/index.ts
@@ -0,0 +1,56 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesLineApi {
+  /** MES 閿�鍞嚭搴撳崟琛� */
+  export interface ProductSalesLine {
+    id?: number; // 琛岀紪鍙�
+    salesId?: number; // 鍑哄簱鍗曠紪鍙�
+    noticeLineId?: number; // 鍙戣揣閫氱煡鍗曡缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 鍑哄簱鏁伴噺
+    pickedQuantity?: number; // 宸叉嫞璐ф暟閲�
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    oqcCheckFlag?: boolean; // 鏄惁妫�楠�
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟琛屽垎椤� */
+export function getProductSalesLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsProductSalesLineApi.ProductSalesLine>
+  >('/mes/wm/product-sales-line/page', { params });
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟琛岃鎯� */
+export function getProductSalesLine(id: number) {
+  return requestClient.get<WlsProductSalesLineApi.ProductSalesLine>(
+    `/mes/wm/product-sales-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞嚭搴撳崟琛� */
+export function createProductSalesLine(
+  data: WlsProductSalesLineApi.ProductSalesLine,
+) {
+  return requestClient.post<number>('/mes/wm/product-sales-line/create', data);
+}
+
+/** 淇敼閿�鍞嚭搴撳崟琛� */
+export function updateProductSalesLine(
+  data: WlsProductSalesLineApi.ProductSalesLine,
+) {
+  return requestClient.put('/mes/wm/product-sales-line/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟琛� */
+export function deleteProductSalesLine(id: number) {
+  return requestClient.delete(`/mes/wm/product-sales-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnissue/detail/index.ts b/src/api/wls/returnissue/detail/index.ts
new file mode 100644
index 0000000..f7edcdc
--- /dev/null
+++ b/src/api/wls/returnissue/detail/index.ts
@@ -0,0 +1,56 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueDetailApi {
+  /** MES 鐢熶骇閫�鏂欐槑缁� */
+  export interface ReturnIssueDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    issueId?: number; // 閫�鏂欏崟缂栧彿
+    lineId?: number; // 閫�鏂欏崟琛岀紪鍙�
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欐槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnIssueDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsReturnIssueDetailApi.ReturnIssueDetail[]>(
+    '/mes/wm/return-issue-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欐槑缁嗚鎯� */
+export function getReturnIssueDetail(id: number) {
+  return requestClient.get<WlsReturnIssueDetailApi.ReturnIssueDetail>(
+    `/mes/wm/return-issue-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐢熶骇閫�鏂欐槑缁� */
+export function createReturnIssueDetail(
+  data: WlsReturnIssueDetailApi.ReturnIssueDetail,
+) {
+  return requestClient.post<number>('/mes/wm/return-issue-detail/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欐槑缁� */
+export function updateReturnIssueDetail(
+  data: WlsReturnIssueDetailApi.ReturnIssueDetail,
+) {
+  return requestClient.put('/mes/wm/return-issue-detail/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欐槑缁� */
+export function deleteReturnIssueDetail(id: number) {
+  return requestClient.delete(`/mes/wm/return-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnissue/index.ts b/src/api/wls/returnissue/index.ts
new file mode 100644
index 0000000..a1078e7
--- /dev/null
+++ b/src/api/wls/returnissue/index.ts
@@ -0,0 +1,78 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueApi {
+  /** MES 鐢熶骇閫�鏂欏崟 */
+  export interface ReturnIssue {
+    id?: number; // 閫�鏂欏崟缂栧彿
+    code?: string; // 閫�鏂欏崟缂栧彿
+    name?: string; // 閫�鏂欏崟鍚嶇О
+    workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+    workstationName?: string; // 宸ヤ綔绔欏悕绉�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+    type?: number; // 閫�鏂欑被鍨�
+    returnDate?: number; // 閫�鏂欐棩鏈�
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟鍒嗛〉 */
+export function getReturnIssuePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsReturnIssueApi.ReturnIssue>>(
+    '/mes/wm/return-issue/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟璇︽儏 */
+export function getReturnIssue(id: number) {
+  return requestClient.get<WlsReturnIssueApi.ReturnIssue>(
+    `/mes/wm/return-issue/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐢熶骇閫�鏂欏崟 */
+export function createReturnIssue(data: WlsReturnIssueApi.ReturnIssue) {
+  return requestClient.post<number>('/mes/wm/return-issue/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欏崟 */
+export function updateReturnIssue(data: WlsReturnIssueApi.ReturnIssue) {
+  return requestClient.put('/mes/wm/return-issue/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟 */
+export function deleteReturnIssue(id: number) {
+  return requestClient.delete(`/mes/wm/return-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦鐢熶骇閫�鏂欏崟 */
+export function submitReturnIssue(id: number) {
+  return requestClient.put(`/mes/wm/return-issue/submit?id=${id}`);
+}
+
+/** 鍏ュ簱涓婃灦 */
+export function stockReturnIssue(id: number) {
+  return requestClient.put(`/mes/wm/return-issue/stock?id=${id}`);
+}
+
+/** 瀹屾垚鐢熶骇閫�鏂欏崟 */
+export function finishReturnIssue(id: number) {
+  return requestClient.put(`/mes/wm/return-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷鐢熶骇閫�鏂欏崟 */
+export function cancelReturnIssue(id: number) {
+  return requestClient.put(`/mes/wm/return-issue/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鐢熶骇閫�鏂欏崟 */
+export function exportReturnIssue(params: any) {
+  return requestClient.download('/mes/wm/return-issue/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/returnissue/line/index.ts b/src/api/wls/returnissue/line/index.ts
new file mode 100644
index 0000000..d9919dd
--- /dev/null
+++ b/src/api/wls/returnissue/line/index.ts
@@ -0,0 +1,58 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueLineApi {
+  /** MES 鐢熶骇閫�鏂欏崟琛� */
+  export interface ReturnIssueLine {
+    id?: number; // 琛岀紪鍙�
+    issueId?: number; // 閫�鏂欏崟缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    quantity?: number; // 閫�鏂欐暟閲�
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    rqcCheckFlag?: boolean; // 鏄惁妫�娴�
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    rqcId?: number; // 閫�璐ф楠屽崟缂栧彿
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟琛屽垎椤� */
+export function getReturnIssueLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsReturnIssueLineApi.ReturnIssueLine>>(
+    '/mes/wm/return-issue-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟琛岃鎯� */
+export function getReturnIssueLine(id: number) {
+  return requestClient.get<WlsReturnIssueLineApi.ReturnIssueLine>(
+    `/mes/wm/return-issue-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐢熶骇閫�鏂欏崟琛� */
+export function createReturnIssueLine(
+  data: WlsReturnIssueLineApi.ReturnIssueLine,
+) {
+  return requestClient.post<number>('/mes/wm/return-issue-line/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欏崟琛� */
+export function updateReturnIssueLine(
+  data: WlsReturnIssueLineApi.ReturnIssueLine,
+) {
+  return requestClient.put('/mes/wm/return-issue-line/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟琛� */
+export function deleteReturnIssueLine(id: number) {
+  return requestClient.delete(`/mes/wm/return-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnsales/detail/index.ts b/src/api/wls/returnsales/detail/index.ts
new file mode 100644
index 0000000..6aa18e6
--- /dev/null
+++ b/src/api/wls/returnsales/detail/index.ts
@@ -0,0 +1,55 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesDetailApi {
+  /** MES 閿�鍞��璐ф槑缁� */
+  export interface ReturnSalesDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    returnId?: number; // 閫�璐у崟缂栧彿
+    lineId?: number; // 閫�璐у崟琛岀紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閿�鍞��璐ф槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnSalesDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsReturnSalesDetailApi.ReturnSalesDetail[]>(
+    '/mes/wm/return-sales-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ閿�鍞��璐ф槑缁嗚鎯� */
+export function getReturnSalesDetail(id: number) {
+  return requestClient.get<WlsReturnSalesDetailApi.ReturnSalesDetail>(
+    `/mes/wm/return-sales-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞��璐ф槑缁� */
+export function createReturnSalesDetail(
+  data: WlsReturnSalesDetailApi.ReturnSalesDetail,
+) {
+  return requestClient.post<number>('/mes/wm/return-sales-detail/create', data);
+}
+
+/** 淇敼閿�鍞��璐ф槑缁� */
+export function updateReturnSalesDetail(
+  data: WlsReturnSalesDetailApi.ReturnSalesDetail,
+) {
+  return requestClient.put('/mes/wm/return-sales-detail/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐ф槑缁� */
+export function deleteReturnSalesDetail(id: number) {
+  return requestClient.delete(`/mes/wm/return-sales-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnsales/index.ts b/src/api/wls/returnsales/index.ts
new file mode 100644
index 0000000..6f20fa1
--- /dev/null
+++ b/src/api/wls/returnsales/index.ts
@@ -0,0 +1,78 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesApi {
+  /** MES 閿�鍞��璐у崟 */
+  export interface ReturnSales {
+    id?: number; // 閫�璐у崟缂栧彿
+    code?: string; // 閫�璐у崟缂栧彿
+    name?: string; // 閫�璐у崟鍚嶇О
+    salesOrderCode?: string; // 閿�鍞鍗曞彿
+    clientId?: number; // 瀹㈡埛缂栧彿
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    returnDate?: number; // 閫�璐ф棩鏈�
+    returnReason?: string; // 閫�璐у師鍥�
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ閿�鍞��璐у崟鍒嗛〉 */
+export function getReturnSalesPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsReturnSalesApi.ReturnSales>>(
+    '/mes/wm/return-sales/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閿�鍞��璐у崟璇︽儏 */
+export function getReturnSales(id: number) {
+  return requestClient.get<WlsReturnSalesApi.ReturnSales>(
+    `/mes/wm/return-sales/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞��璐у崟 */
+export function createReturnSales(data: WlsReturnSalesApi.ReturnSales) {
+  return requestClient.post<number>('/mes/wm/return-sales/create', data);
+}
+
+/** 淇敼閿�鍞��璐у崟 */
+export function updateReturnSales(data: WlsReturnSalesApi.ReturnSales) {
+  return requestClient.put('/mes/wm/return-sales/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟 */
+export function deleteReturnSales(id: number) {
+  return requestClient.delete(`/mes/wm/return-sales/delete?id=${id}`);
+}
+
+/** 鎻愪氦閿�鍞��璐у崟 */
+export function submitReturnSales(id: number) {
+  return requestClient.put(`/mes/wm/return-sales/submit?id=${id}`);
+}
+
+/** 鎵ц閫�璐� */
+export function finishReturnSales(id: number) {
+  return requestClient.put(`/mes/wm/return-sales/finish?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockReturnSales(id: number) {
+  return requestClient.put(`/mes/wm/return-sales/stock?id=${id}`);
+}
+
+/** 鍙栨秷閿�鍞��璐у崟 */
+export function cancelReturnSales(id: number) {
+  return requestClient.put(`/mes/wm/return-sales/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭閿�鍞��璐у崟 */
+export function exportReturnSales(params: any) {
+  return requestClient.download('/mes/wm/return-sales/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/returnsales/line/index.ts b/src/api/wls/returnsales/line/index.ts
new file mode 100644
index 0000000..3952ce5
--- /dev/null
+++ b/src/api/wls/returnsales/line/index.ts
@@ -0,0 +1,57 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesLineApi {
+  /** MES 閿�鍞��璐у崟琛� */
+  export interface ReturnSalesLine {
+    id?: number; // 琛岀紪鍙�
+    returnId?: number; // 閫�璐у崟缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 閫�璐ф暟閲�
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    rqcCheckFlag?: boolean; // 鏄惁闇�瑕佽川妫�
+    rqcId?: number; // 閫�璐ф楠屽崟缂栧彿
+    qualityStatus?: number; // 璐ㄩ噺鐘舵��
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ閿�鍞��璐у崟琛屽垎椤� */
+export function getReturnSalesLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsReturnSalesLineApi.ReturnSalesLine>>(
+    '/mes/wm/return-sales-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ閿�鍞��璐у崟琛岃鎯� */
+export function getReturnSalesLine(id: number) {
+  return requestClient.get<WlsReturnSalesLineApi.ReturnSalesLine>(
+    `/mes/wm/return-sales-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板閿�鍞��璐у崟琛� */
+export function createReturnSalesLine(
+  data: WlsReturnSalesLineApi.ReturnSalesLine,
+) {
+  return requestClient.post<number>('/mes/wm/return-sales-line/create', data);
+}
+
+/** 淇敼閿�鍞��璐у崟琛� */
+export function updateReturnSalesLine(
+  data: WlsReturnSalesLineApi.ReturnSalesLine,
+) {
+  return requestClient.put('/mes/wm/return-sales-line/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟琛� */
+export function deleteReturnSalesLine(id: number) {
+  return requestClient.delete(`/mes/wm/return-sales-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnvendor/detail/index.ts b/src/api/wls/returnvendor/detail/index.ts
new file mode 100644
index 0000000..af62167
--- /dev/null
+++ b/src/api/wls/returnvendor/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorDetailApi {
+  /** MES 渚涘簲鍟嗛��璐ф槑缁� */
+  export interface ReturnVendorDetail {
+    id?: number; // 鏄庣粏缂栧彿
+    returnId?: number; // 閫�璐у崟缂栧彿
+    lineId?: number; // 閫�璐у崟琛岀紪鍙�
+    materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐ф槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnVendorDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsReturnVendorDetailApi.ReturnVendorDetail[]>(
+    '/mes/wm/return-vendor-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐ф槑缁嗚鎯� */
+export function getReturnVendorDetail(id: number) {
+  return requestClient.get<WlsReturnVendorDetailApi.ReturnVendorDetail>(
+    `/mes/wm/return-vendor-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐ф槑缁� */
+export function createReturnVendorDetail(
+  data: WlsReturnVendorDetailApi.ReturnVendorDetail,
+) {
+  return requestClient.post<number>(
+    '/mes/wm/return-vendor-detail/create',
+    data,
+  );
+}
+
+/** 淇敼渚涘簲鍟嗛��璐ф槑缁� */
+export function updateReturnVendorDetail(
+  data: WlsReturnVendorDetailApi.ReturnVendorDetail,
+) {
+  return requestClient.put('/mes/wm/return-vendor-detail/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐ф槑缁� */
+export function deleteReturnVendorDetail(id: number) {
+  return requestClient.delete(`/mes/wm/return-vendor-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnvendor/index.ts b/src/api/wls/returnvendor/index.ts
new file mode 100644
index 0000000..092888f
--- /dev/null
+++ b/src/api/wls/returnvendor/index.ts
@@ -0,0 +1,88 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorApi {
+  /** MES 渚涘簲鍟嗛��璐у崟 */
+  export interface ReturnVendor {
+    id?: number; // 閫�璐у崟缂栧彿
+    code?: string; // 閫�璐у崟缂栧彿
+    name?: string; // 閫�璐у崟鍚嶇О
+    purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+    vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+    vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+    vendorName?: string; // 渚涘簲鍟嗗悕绉�
+    vendorNickname?: string; // 渚涘簲鍟嗙畝绉�
+    returnDate?: number; // 閫�璐ф棩鏈�
+    returnReason?: string; // 閫�璐у師鍥�
+    transportCode?: string; // 杩愬崟鍙�
+    transportTelephone?: string; // 鑱旂郴鐢佃瘽
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟鍒嗛〉 */
+export function getReturnVendorPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsReturnVendorApi.ReturnVendor>>(
+    '/mes/wm/return-vendor/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟璇︽儏 */
+export function getReturnVendor(id: number) {
+  return requestClient.get<WlsReturnVendorApi.ReturnVendor>(
+    `/mes/wm/return-vendor/get?id=${id}`,
+  );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐у崟 */
+export function createReturnVendor(data: WlsReturnVendorApi.ReturnVendor) {
+  return requestClient.post<number>('/mes/wm/return-vendor/create', data);
+}
+
+/** 淇敼渚涘簲鍟嗛��璐у崟 */
+export function updateReturnVendor(data: WlsReturnVendorApi.ReturnVendor) {
+  return requestClient.put('/mes/wm/return-vendor/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟 */
+export function deleteReturnVendor(id: number) {
+  return requestClient.delete(`/mes/wm/return-vendor/delete?id=${id}`);
+}
+
+/** 鎻愪氦渚涘簲鍟嗛��璐у崟 */
+export function submitReturnVendor(id: number) {
+  return requestClient.put(`/mes/wm/return-vendor/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockReturnVendor(id: number) {
+  return requestClient.put(`/mes/wm/return-vendor/stock?id=${id}`);
+}
+
+/** 瀹屾垚渚涘簲鍟嗛��璐у崟 */
+export function finishReturnVendor(id: number) {
+  return requestClient.put(`/mes/wm/return-vendor/finish?id=${id}`);
+}
+
+/** 鍙栨秷渚涘簲鍟嗛��璐у崟 */
+export function cancelReturnVendor(id: number) {
+  return requestClient.put(`/mes/wm/return-vendor/cancel?id=${id}`);
+}
+
+/** 鏍¢獙渚涘簲鍟嗛��璐у崟鎷h揣鏁伴噺鏄惁涓庨��璐ф暟閲忎竴鑷� */
+export function checkReturnVendorQuantity(id: number) {
+  return requestClient.get<boolean>(
+    `/mes/wm/return-vendor/check-quantity?id=${id}`,
+  );
+}
+
+/** 瀵煎嚭渚涘簲鍟嗛��璐у崟 */
+export function exportReturnVendor(params: any) {
+  return requestClient.download('/mes/wm/return-vendor/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/returnvendor/line/index.ts b/src/api/wls/returnvendor/line/index.ts
new file mode 100644
index 0000000..c194fb7
--- /dev/null
+++ b/src/api/wls/returnvendor/line/index.ts
@@ -0,0 +1,53 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorLineApi {
+  /** MES 渚涘簲鍟嗛��璐у崟琛� */
+  export interface ReturnVendorLine {
+    id?: number; // 琛岀紪鍙�
+    returnId?: number; // 閫�璐у崟缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    quantity?: number; // 閫�璐ф暟閲�
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟琛屽垎椤� */
+export function getReturnVendorLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsReturnVendorLineApi.ReturnVendorLine>
+  >('/mes/wm/return-vendor-line/page', { params });
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟琛岃鎯� */
+export function getReturnVendorLine(id: number) {
+  return requestClient.get<WlsReturnVendorLineApi.ReturnVendorLine>(
+    `/mes/wm/return-vendor-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐у崟琛� */
+export function createReturnVendorLine(
+  data: WlsReturnVendorLineApi.ReturnVendorLine,
+) {
+  return requestClient.post<number>('/mes/wm/return-vendor-line/create', data);
+}
+
+/** 淇敼渚涘簲鍟嗛��璐у崟琛� */
+export function updateReturnVendorLine(
+  data: WlsReturnVendorLineApi.ReturnVendorLine,
+) {
+  return requestClient.put('/mes/wm/return-vendor-line/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟琛� */
+export function deleteReturnVendorLine(id: number) {
+  return requestClient.delete(`/mes/wm/return-vendor-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/salesnotice/index.ts b/src/api/wls/salesnotice/index.ts
new file mode 100644
index 0000000..fc1222d
--- /dev/null
+++ b/src/api/wls/salesnotice/index.ts
@@ -0,0 +1,65 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSalesNoticeApi {
+  /** MES 鍙戣揣閫氱煡鍗� */
+  export interface SalesNotice {
+    id?: number; // 閫氱煡鍗曠紪鍙�
+    code?: string; // 閫氱煡鍗曠紪鍙�
+    name?: string; // 閫氱煡鍗曞悕绉�
+    salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+    clientId?: number; // 瀹㈡埛缂栧彿
+    clientCode?: string; // 瀹㈡埛缂栫爜
+    clientName?: string; // 瀹㈡埛鍚嶇О
+    salesDate?: number; // 鍙戣揣鏃ユ湡
+    recipientName?: string; // 鏀惰揣浜�
+    recipientTelephone?: string; // 鑱旂郴鏂瑰紡
+    recipientAddress?: string; // 鏀惰揣鍦板潃
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曞垎椤� */
+export function getSalesNoticePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsSalesNoticeApi.SalesNotice>>(
+    '/mes/wm/sales-notice/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡鎯� */
+export function getSalesNotice(id: number) {
+  return requestClient.get<WlsSalesNoticeApi.SalesNotice>(
+    `/mes/wm/sales-notice/get?id=${id}`,
+  );
+}
+
+/** 鏂板鍙戣揣閫氱煡鍗� */
+export function createSalesNotice(data: WlsSalesNoticeApi.SalesNotice) {
+  return requestClient.post<number>('/mes/wm/sales-notice/create', data);
+}
+
+/** 淇敼鍙戣揣閫氱煡鍗� */
+export function updateSalesNotice(data: WlsSalesNoticeApi.SalesNotice) {
+  return requestClient.put('/mes/wm/sales-notice/update', data);
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗� */
+export function deleteSalesNotice(id: number) {
+  return requestClient.delete(`/mes/wm/sales-notice/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍙戣揣閫氱煡鍗� */
+export function submitSalesNotice(id: number) {
+  return requestClient.put(`/mes/wm/sales-notice/submit?id=${id}`);
+}
+
+/** 瀵煎嚭鍙戣揣閫氱煡鍗� */
+export function exportSalesNotice(params: any) {
+  return requestClient.download('/mes/wm/sales-notice/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/salesnotice/line/index.ts b/src/api/wls/salesnotice/line/index.ts
new file mode 100644
index 0000000..1a61ea4
--- /dev/null
+++ b/src/api/wls/salesnotice/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSalesNoticeLineApi {
+  /** MES 鍙戣揣閫氱煡鍗曡 */
+  export interface SalesNoticeLine {
+    id?: number; // 琛岀紪鍙�
+    noticeId?: number; // 鍙戣揣閫氱煡鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    quantity?: number; // 鍙戣揣鏁伴噺
+    oqcCheckFlag?: boolean; // 鏄惁妫�楠�
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡鍒嗛〉 */
+export function getSalesNoticeLinePage(params: PageParam) {
+  return requestClient.get<PageResult<WlsSalesNoticeLineApi.SalesNoticeLine>>(
+    '/mes/wm/sales-notice-line/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡璇︽儏 */
+export function getSalesNoticeLine(id: number) {
+  return requestClient.get<WlsSalesNoticeLineApi.SalesNoticeLine>(
+    `/mes/wm/sales-notice-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板鍙戣揣閫氱煡鍗曡 */
+export function createSalesNoticeLine(
+  data: WlsSalesNoticeLineApi.SalesNoticeLine,
+) {
+  return requestClient.post<number>('/mes/wm/sales-notice-line/create', data);
+}
+
+/** 淇敼鍙戣揣閫氱煡鍗曡 */
+export function updateSalesNoticeLine(
+  data: WlsSalesNoticeLineApi.SalesNoticeLine,
+) {
+  return requestClient.put('/mes/wm/sales-notice-line/update', data);
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗曡 */
+export function deleteSalesNoticeLine(id: number) {
+  return requestClient.delete(`/mes/wm/sales-notice-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/sn/index.ts b/src/api/wls/sn/index.ts
new file mode 100644
index 0000000..63b04df
--- /dev/null
+++ b/src/api/wls/sn/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSnApi {
+  /** MES SN 鐮佸垎缁� */
+  export interface SnGroup {
+    uuid?: string; // 鎵规 UUID
+    count?: number; // SN 鐮佹暟閲�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitName?: string; // 鍗曚綅鍚嶇О
+    batchCode?: string; // 鎵规鍙�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    createTime?: Date; // 鐢熸垚鏃堕棿
+  }
+
+  /** MES SN 鐮佹槑缁� */
+  export interface Sn {
+    id?: number; // 缂栧彿
+    uuid?: string; // 鎵规 UUID
+    code?: string; // SN 鐮�
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitName?: string; // 鍗曚綅鍚嶇О
+    batchCode?: string; // 鎵规鍙�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    createTime?: Date; // 鐢熸垚鏃堕棿
+  }
+
+  /** MES SN 鐮佺敓鎴愬弬鏁� */
+  export interface SnGenerate {
+    itemId?: number; // 鐗╂枡缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+    count?: number; // 鐢熸垚鏁伴噺
+  }
+
+  /** MES SN 鐮佸垎缁勫垎椤垫煡璇㈠弬鏁� */
+  export interface PageParams extends PageParam {
+    uuid?: string; // 鍒嗙粍 UUID
+    code?: string; // SN 鐮�
+    itemId?: number; // 鐗╂枡缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    createTime?: string[]; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鐢熸垚 SN 鐮� */
+export function generateSnCodes(data: WlsSnApi.SnGenerate) {
+  return requestClient.post('/mes/wm/sn/generate', data);
+}
+
+/** 鏌ヨ SN 鐮佸垎缁勫垎椤� */
+export function getSnGroupPage(params: WlsSnApi.PageParams) {
+  return requestClient.get<PageResult<WlsSnApi.SnGroup>>(
+    '/mes/wm/sn/group-page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鎵规 SN 鐮佹槑缁嗗垪琛� */
+export function getSnListByUuid(uuid: string) {
+  return requestClient.get<WlsSnApi.Sn[]>('/mes/wm/sn/list-by-uuid', {
+    params: { uuid },
+  });
+}
+
+/** 鎵归噺鍒犻櫎 SN 鐮侊紙鎸夋壒娆� UUID锛� */
+export function deleteSnBatch(uuid: string) {
+  return requestClient.delete('/mes/wm/sn/delete-batch', {
+    params: { uuid },
+  });
+}
+
+/** 瀵煎嚭 SN 鐮佸垎缁� Excel */
+export function exportSnGroupExcel(params: WlsSnApi.PageParams) {
+  return requestClient.download('/mes/wm/sn/group-export-excel', { params });
+}
+
+/** 瀵煎嚭鎵规 SN 鐮佹槑缁� Excel */
+export function exportSnDetailExcel(uuid: string) {
+  return requestClient.download('/mes/wm/sn/export-excel', {
+    params: { uuid },
+  });
+}
diff --git a/src/api/wls/stocktaking/plan/index.ts b/src/api/wls/stocktaking/plan/index.ts
new file mode 100644
index 0000000..905db8f
--- /dev/null
+++ b/src/api/wls/stocktaking/plan/index.ts
@@ -0,0 +1,68 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingPlanApi {
+  /** 鐩樼偣鏂规 */
+  export interface StockTakingPlan {
+    id?: number; // 鏂规缂栧彿
+    code?: string; // 鏂规缂栫爜
+    name?: string; // 鏂规鍚嶇О
+    type?: number; // 鐩樼偣绫诲瀷
+    startTime?: number; // 寮�濮嬫椂闂�
+    endTime?: number; // 缁撴潫鏃堕棿
+    blindFlag?: boolean; // 鏄惁鐩茬洏
+    frozen?: boolean; // 鏄惁鍐荤粨搴撳瓨
+    status?: number; // 鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鐩樼偣鏂规鍒嗛〉 */
+export function getStockTakingPlanPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsStockTakingPlanApi.StockTakingPlan>>(
+    '/mes/wm/stocktaking-plan/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鐩樼偣鏂规璇︽儏 */
+export function getStockTakingPlan(id: number) {
+  return requestClient.get<WlsStockTakingPlanApi.StockTakingPlan>(
+    `/mes/wm/stocktaking-plan/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐩樼偣鏂规 */
+export function createStockTakingPlan(
+  data: WlsStockTakingPlanApi.StockTakingPlan,
+) {
+  return requestClient.post<number>('/mes/wm/stocktaking-plan/create', data);
+}
+
+/** 淇敼鐩樼偣鏂规 */
+export function updateStockTakingPlan(
+  data: WlsStockTakingPlanApi.StockTakingPlan,
+) {
+  return requestClient.put('/mes/wm/stocktaking-plan/update', data);
+}
+
+/** 淇敼鐩樼偣鏂规鐘舵�� */
+export function updateStockTakingPlanStatus(id: number, status: number) {
+  return requestClient.put('/mes/wm/stocktaking-plan/update-status', null, {
+    params: { id, status },
+  });
+}
+
+/** 鍒犻櫎鐩樼偣鏂规 */
+export function deleteStockTakingPlan(id: number) {
+  return requestClient.delete(`/mes/wm/stocktaking-plan/delete?id=${id}`);
+}
+
+/** 瀵煎嚭鐩樼偣鏂规 */
+export function exportStockTakingPlan(params: any) {
+  return requestClient.download('/mes/wm/stocktaking-plan/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/stocktaking/plan/param/index.ts b/src/api/wls/stocktaking/plan/param/index.ts
new file mode 100644
index 0000000..7591b01
--- /dev/null
+++ b/src/api/wls/stocktaking/plan/param/index.ts
@@ -0,0 +1,49 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingPlanParamApi {
+  /** 鐩樼偣鏂规鏉′欢 */
+  export interface StockTakingPlanParam {
+    id?: number; // 鏉′欢缂栧彿
+    planId?: number; // 鏂规缂栧彿
+    type?: number; // 鏉′欢绫诲瀷
+    valueId?: number; // 鏉′欢鍊肩紪鍙�
+    valueCode?: string; // 鏉′欢鍊肩紪鐮�
+    valueName?: string; // 鏉′欢鍊煎悕绉�
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鐩樼偣鏂规鏉′欢鍒嗛〉 */
+export function getStockTakingPlanParamPage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsStockTakingPlanParamApi.StockTakingPlanParam>
+  >('/mes/wm/stocktaking-plan-param/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣鏂规鏉′欢璇︽儏 */
+export function getStockTakingPlanParam(id: number) {
+  return requestClient.get<WlsStockTakingPlanParamApi.StockTakingPlanParam>(
+    `/mes/wm/stocktaking-plan-param/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐩樼偣鏂规鏉′欢 */
+export function createStockTakingPlanParam(
+  data: WlsStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+  return requestClient.post('/mes/wm/stocktaking-plan-param/create', data);
+}
+
+/** 淇敼鐩樼偣鏂规鏉′欢 */
+export function updateStockTakingPlanParam(
+  data: WlsStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+  return requestClient.put('/mes/wm/stocktaking-plan-param/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣鏂规鏉′欢 */
+export function deleteStockTakingPlanParam(id: number) {
+  return requestClient.delete(`/mes/wm/stocktaking-plan-param/delete?id=${id}`);
+}
diff --git a/src/api/wls/stocktaking/task/index.ts b/src/api/wls/stocktaking/task/index.ts
new file mode 100644
index 0000000..360fc68
--- /dev/null
+++ b/src/api/wls/stocktaking/task/index.ts
@@ -0,0 +1,86 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingTaskApi {
+  /** 鐩樼偣浠诲姟 */
+  export interface StockTakingTask {
+    id?: number; // 浠诲姟缂栧彿
+    code?: string; // 浠诲姟缂栫爜
+    name?: string; // 浠诲姟鍚嶇О
+    takingDate?: string; // 鐩樼偣鏃ユ湡
+    type?: number; // 鐩樼偣绫诲瀷
+    userId?: number; // 鐩樼偣浜虹敤鎴风紪鍙�
+    userNickname?: string; // 鐩樼偣浜哄悕绉�
+    planId?: number; // 鐩樼偣鏂规缂栧彿
+    planCode?: string; // 鐩樼偣鏂规缂栫爜
+    planName?: string; // 鐩樼偣鏂规鍚嶇О
+    blindFlag?: boolean; // 鏄惁鐩茬洏
+    frozen?: boolean; // 鏄惁鍐荤粨搴撳瓨
+    startTime?: number; // 寮�濮嬫椂闂�
+    endTime?: number; // 缁撴潫鏃堕棿
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟鍒嗛〉 */
+export function getStockTakingPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsStockTakingTaskApi.StockTakingTask>>(
+    '/mes/wm/stocktaking-task/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟璇︽儏 */
+export function getStockTaking(id: number) {
+  return requestClient.get<WlsStockTakingTaskApi.StockTakingTask>(
+    `/mes/wm/stocktaking-task/get?id=${id}`,
+  );
+}
+
+/** 鏂板鐩樼偣浠诲姟 */
+export function createStockTaking(
+  data: WlsStockTakingTaskApi.StockTakingTask,
+) {
+  return requestClient.post<number>('/mes/wm/stocktaking-task/create', data);
+}
+
+/** 淇敼鐩樼偣浠诲姟 */
+export function updateStockTaking(
+  data: WlsStockTakingTaskApi.StockTakingTask,
+) {
+  return requestClient.put('/mes/wm/stocktaking-task/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟 */
+export function deleteStockTaking(id: number) {
+  return requestClient.delete(`/mes/wm/stocktaking-task/delete?id=${id}`);
+}
+
+/** 鎻愪氦鐩樼偣浠诲姟 */
+export function submitStockTaking(id: number) {
+  return requestClient.put('/mes/wm/stocktaking-task/submit', null, {
+    params: { id },
+  });
+}
+
+/** 鍙栨秷鐩樼偣浠诲姟 */
+export function cancelStockTaking(id: number) {
+  return requestClient.put('/mes/wm/stocktaking-task/cancel', null, {
+    params: { id },
+  });
+}
+
+/** 鎵ц鐩樼偣浠诲姟 */
+export function finishStockTaking(id: number) {
+  return requestClient.put('/mes/wm/stocktaking-task/finish', { id });
+}
+
+/** 瀵煎嚭鐩樼偣浠诲姟 */
+export function exportStockTaking(params: any) {
+  return requestClient.download('/mes/wm/stocktaking-task/export-excel', {
+    params,
+  });
+}
diff --git a/src/api/wls/stocktaking/task/line/index.ts b/src/api/wls/stocktaking/task/line/index.ts
new file mode 100644
index 0000000..2ed3642
--- /dev/null
+++ b/src/api/wls/stocktaking/task/line/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingTaskLineApi {
+  /** 鐩樼偣浠诲姟琛� */
+  export interface StockTakingTaskLine {
+    id?: number; // 鐩樼偣琛岀紪鍙�
+    taskId?: number; // 浠诲姟缂栧彿
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    quantity?: number; // 鍦ㄥ簱鏁伴噺
+    takingQuantity?: number; // 鐩樼偣鏁伴噺
+    differenceQuantity?: number; // 宸紓鏁伴噺
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    status?: number; // 鐘舵��
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛屽垎椤� */
+export function getStockTakingTaskLinePage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsStockTakingTaskLineApi.StockTakingTaskLine>
+  >('/mes/wm/stocktaking-task-line/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛岀簿绠�鍒楄〃 */
+export function getStockTakingTaskLineSimpleList(taskId: number) {
+  return requestClient.get<WlsStockTakingTaskLineApi.StockTakingTaskLine[]>(
+    '/mes/wm/stocktaking-task-line/simple-list',
+    { params: { taskId } },
+  );
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛岃鎯� */
+export function getStockTakingTaskLine(id: number) {
+  return requestClient.get<WlsStockTakingTaskLineApi.StockTakingTaskLine>(
+    '/mes/wm/stocktaking-task-line/get',
+    { params: { id } },
+  );
+}
+
+/** 鏂板鐩樼偣浠诲姟琛� */
+export function createStockTakingTaskLine(
+  data: WlsStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+  return requestClient.post('/mes/wm/stocktaking-task-line/create', data);
+}
+
+/** 淇敼鐩樼偣浠诲姟琛� */
+export function updateStockTakingTaskLine(
+  data: WlsStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+  return requestClient.put('/mes/wm/stocktaking-task-line/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟琛� */
+export function deleteStockTakingTaskLine(id: number) {
+  return requestClient.delete(`/mes/wm/stocktaking-task-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/stocktaking/task/result/index.ts b/src/api/wls/stocktaking/task/result/index.ts
new file mode 100644
index 0000000..4e7715f
--- /dev/null
+++ b/src/api/wls/stocktaking/task/result/index.ts
@@ -0,0 +1,66 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingResultApi {
+  /** 鐩樼偣缁撴灉 */
+  export interface StockTakingResult {
+    id?: number; // 缁撴灉缂栧彿
+    taskId?: number; // 浠诲姟缂栧彿
+    lineId?: number; // 鐩樼偣琛岀紪鍙�
+    materialStockId?: number; // 搴撳瓨缂栧彿
+    itemId?: number; // 鐗╂枡缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    areaId?: number; // 搴撲綅缂栧彿
+    areaName?: string; // 搴撲綅鍚嶇О
+    quantity?: number; // 鍦ㄥ簱鏁伴噺
+    takingQuantity?: number; // 鐩樼偣鏁伴噺
+    remark?: string; // 澶囨敞
+    createTime?: string; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ鐩樼偣缁撴灉鍒嗛〉 */
+export function getStockTakingResultPage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsStockTakingResultApi.StockTakingResult>
+  >('/mes/wm/stocktaking-task-result/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣缁撴灉璇︽儏 */
+export function getStockTakingResult(id: number) {
+  return requestClient.get<WlsStockTakingResultApi.StockTakingResult>(
+    '/mes/wm/stocktaking-task-result/get',
+    { params: { id } },
+  );
+}
+
+/** 鏂板鐩樼偣缁撴灉 */
+export function createStockTakingResult(
+  data: WlsStockTakingResultApi.StockTakingResult,
+) {
+  return requestClient.post('/mes/wm/stocktaking-task-result/create', data);
+}
+
+/** 淇敼鐩樼偣缁撴灉 */
+export function updateStockTakingResult(
+  data: WlsStockTakingResultApi.StockTakingResult,
+) {
+  return requestClient.put('/mes/wm/stocktaking-task-result/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣缁撴灉 */
+export function deleteStockTakingResult(id: number) {
+  return requestClient.delete(
+    `/mes/wm/stocktaking-task-result/delete?id=${id}`,
+  );
+}
diff --git a/src/api/wls/transfer/detail/index.ts b/src/api/wls/transfer/detail/index.ts
new file mode 100644
index 0000000..90de160
--- /dev/null
+++ b/src/api/wls/transfer/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferDetailApi {
+  /** MES 璋冩嫧鏄庣粏 */
+  export interface TransferDetail {
+    id?: number; // 缂栧彿
+    lineId?: number; // 杞Щ鍗曡缂栧彿
+    transferId?: number; // 杞Щ鍗曠紪鍙�
+    itemId?: number; // 鐗╂枡浜у搧缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅鍚嶇О
+    quantity?: number; // 鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    toWarehouseId?: number; // 绉诲叆浠撳簱缂栧彿
+    toWarehouseName?: string; // 绉诲叆浠撳簱鍚嶇О
+    toLocationId?: number; // 绉诲叆搴撳尯缂栧彿
+    toLocationName?: string; // 绉诲叆搴撳尯鍚嶇О
+    toAreaId?: number; // 绉诲叆搴撲綅缂栧彿
+    toAreaName?: string; // 绉诲叆搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ璋冩嫧鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getTransferDetailListByLineId(lineId: number) {
+  return requestClient.get<WlsTransferDetailApi.TransferDetail[]>(
+    '/mes/wm/transfer-detail/list-by-line',
+    { params: { lineId } },
+  );
+}
+
+/** 鏌ヨ璋冩嫧鏄庣粏璇︽儏 */
+export function getTransferDetail(id: number) {
+  return requestClient.get<WlsTransferDetailApi.TransferDetail>(
+    `/mes/wm/transfer-detail/get?id=${id}`,
+  );
+}
+
+/** 鏂板璋冩嫧鏄庣粏 */
+export function createTransferDetail(
+  data: WlsTransferDetailApi.TransferDetail,
+) {
+  return requestClient.post('/mes/wm/transfer-detail/create', data);
+}
+
+/** 淇敼璋冩嫧鏄庣粏 */
+export function updateTransferDetail(
+  data: WlsTransferDetailApi.TransferDetail,
+) {
+  return requestClient.put('/mes/wm/transfer-detail/update', data);
+}
+
+/** 鍒犻櫎璋冩嫧鏄庣粏 */
+export function deleteTransferDetail(id: number) {
+  return requestClient.delete(`/mes/wm/transfer-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/transfer/index.ts b/src/api/wls/transfer/index.ts
new file mode 100644
index 0000000..f4b5d8d
--- /dev/null
+++ b/src/api/wls/transfer/index.ts
@@ -0,0 +1,92 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferApi {
+  /** MES 杞Щ鍗� */
+  export interface Transfer {
+    id?: number; // 缂栧彿
+    code?: string; // 杞Щ鍗曠紪鍙�
+    name?: string; // 杞Щ鍗曞悕绉�
+    type?: number; // 杞Щ鍗曠被鍨�
+    deliveryFlag?: boolean; // 鏄惁閰嶉��
+    recipientName?: string; // 鏀惰揣浜�
+    recipientTelephone?: string; // 鑱旂郴鐢佃瘽
+    destinationAddress?: string; // 鐩殑鍦�
+    carrier?: string; // 鎵胯繍鍟�
+    shippingNumber?: string; // 杩愯緭鍗曞彿
+    confirmFlag?: boolean; // 鏄惁纭
+    transferDate?: string; // 杞Щ鏃ユ湡
+    status?: number; // 鍗曟嵁鐘舵��
+    remark?: string; // 澶囨敞
+    createTime?: number; // 鍒涘缓鏃堕棿
+  }
+
+  /** MES 杞Щ鍗曞垎椤垫煡璇㈠弬鏁� */
+  export interface PageParams extends PageParam {
+    code?: string;
+    name?: string;
+    type?: number;
+    status?: number;
+  }
+}
+
+/** 鏌ヨ杞Щ鍗曞垎椤� */
+export function getTransferPage(params: WlsTransferApi.PageParams) {
+  return requestClient.get<PageResult<WlsTransferApi.Transfer>>(
+    '/mes/wm/transfer/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ杞Щ鍗曡鎯� */
+export function getTransfer(id: number) {
+  return requestClient.get<WlsTransferApi.Transfer>(
+    `/mes/wm/transfer/get?id=${id}`,
+  );
+}
+
+/** 鏂板杞Щ鍗� */
+export function createTransfer(data: WlsTransferApi.Transfer) {
+  return requestClient.post<number>('/mes/wm/transfer/create', data);
+}
+
+/** 淇敼杞Щ鍗� */
+export function updateTransfer(data: WlsTransferApi.Transfer) {
+  return requestClient.put('/mes/wm/transfer/update', data);
+}
+
+/** 鍒犻櫎杞Щ鍗� */
+export function deleteTransfer(id: number) {
+  return requestClient.delete(`/mes/wm/transfer/delete?id=${id}`);
+}
+
+/** 鎻愪氦杞Щ鍗� */
+export function submitTransfer(id: number) {
+  return requestClient.put(`/mes/wm/transfer/submit?id=${id}`);
+}
+
+/** 鍒拌揣纭 */
+export function confirmTransfer(id: number) {
+  return requestClient.put(`/mes/wm/transfer/confirm?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockTransfer(id: number) {
+  return requestClient.put(`/mes/wm/transfer/stock?id=${id}`);
+}
+
+/** 瀹屾垚杞Щ */
+export function finishTransfer(id: number) {
+  return requestClient.put(`/mes/wm/transfer/finish?id=${id}`);
+}
+
+/** 鍙栨秷杞Щ鍗� */
+export function cancelTransfer(id: number) {
+  return requestClient.put(`/mes/wm/transfer/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭杞Щ鍗� */
+export function exportTransfer(params: any) {
+  return requestClient.download('/mes/wm/transfer/export-excel', { params });
+}
diff --git a/src/api/wls/transfer/line/index.ts b/src/api/wls/transfer/line/index.ts
new file mode 100644
index 0000000..07881cd
--- /dev/null
+++ b/src/api/wls/transfer/line/index.ts
@@ -0,0 +1,55 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferLineApi {
+  /** MES 杞Щ鍗曡 */
+  export interface TransferLine {
+    id?: number; // 缂栧彿
+    transferId?: number; // 杞Щ鍗曠紪鍙�
+    materialStockId?: number; // 搴撳瓨鍙拌处缂栧彿
+    itemId?: number; // 鐗╂枡浜у搧缂栧彿
+    itemCode?: string; // 鐗╂枡缂栫爜
+    itemName?: string; // 鐗╂枡鍚嶇О
+    specification?: string; // 瑙勬牸鍨嬪彿
+    unitMeasureName?: string; // 鍗曚綅鍚嶇О
+    quantity?: number; // 杞Щ鏁伴噺
+    batchId?: number; // 鎵规缂栧彿
+    batchCode?: string; // 鎵规鍙�
+    fromWarehouseId?: number; // 绉诲嚭浠撳簱缂栧彿
+    fromWarehouseName?: string; // 绉诲嚭浠撳簱鍚嶇О
+    fromLocationId?: number; // 绉诲嚭搴撳尯缂栧彿
+    fromLocationName?: string; // 绉诲嚭搴撳尯鍚嶇О
+    fromAreaId?: number; // 绉诲嚭搴撲綅缂栧彿
+    fromAreaName?: string; // 绉诲嚭搴撲綅鍚嶇О
+    remark?: string; // 澶囨敞
+  }
+}
+
+/** 鏌ヨ杞Щ鍗曡鍒楄〃 */
+export function getTransferLineList(transferId: number) {
+  return requestClient.get<WlsTransferLineApi.TransferLine[]>(
+    '/mes/wm/transfer-line/list',
+    { params: { transferId } },
+  );
+}
+
+/** 鏌ヨ杞Щ鍗曡璇︽儏 */
+export function getTransferLine(id: number) {
+  return requestClient.get<WlsTransferLineApi.TransferLine>(
+    `/mes/wm/transfer-line/get?id=${id}`,
+  );
+}
+
+/** 鏂板杞Щ鍗曡 */
+export function createTransferLine(data: WlsTransferLineApi.TransferLine) {
+  return requestClient.post('/mes/wm/transfer-line/create', data);
+}
+
+/** 淇敼杞Щ鍗曡 */
+export function updateTransferLine(data: WlsTransferLineApi.TransferLine) {
+  return requestClient.put('/mes/wm/transfer-line/update', data);
+}
+
+/** 鍒犻櫎杞Щ鍗曡 */
+export function deleteTransferLine(id: number) {
+  return requestClient.delete(`/mes/wm/transfer-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/area/index.ts b/src/api/wls/warehouse/area/index.ts
new file mode 100644
index 0000000..7135c6f
--- /dev/null
+++ b/src/api/wls/warehouse/area/index.ts
@@ -0,0 +1,65 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsWarehouseAreaApi {
+  /** MES 搴撲綅 */
+  export interface WarehouseArea {
+    id?: number; // 搴撲綅缂栧彿
+    code?: string; // 搴撲綅缂栫爜
+    name?: string; // 搴撲綅鍚嶇О
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    locationId?: number; // 搴撳尯缂栧彿
+    locationName?: string; // 搴撳尯鍚嶇О
+    area?: number; // 闈㈢Н
+    maxLoad?: number; // 鏈�澶ц浇鑽�
+    positionX?: number; // X 鍧愭爣
+    positionY?: number; // Y 鍧愭爣
+    positionZ?: number; // Z 鍧愭爣
+    status?: number; // 鐘舵��
+    frozen?: boolean; // 鏄惁鍐荤粨
+    allowItemMixing?: boolean; // 鏄惁鍏佽鐗╂枡娣锋斁
+    allowBatchMixing?: boolean; // 鏄惁鍏佽鎵规娣锋斁
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ搴撲綅鍒嗛〉 */
+export function getWarehouseAreaPage(params: PageParam) {
+  return requestClient.get<PageResult<WlsWarehouseAreaApi.WarehouseArea>>(
+    '/mes/wm/warehouse-area/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ搴撲綅绮剧畝鍒楄〃 */
+export function getWarehouseAreaSimpleList(locationId?: number) {
+  return requestClient.get<WlsWarehouseAreaApi.WarehouseArea[]>(
+    '/mes/wm/warehouse-area/simple-list',
+    { params: { locationId } },
+  );
+}
+
+/** 鏌ヨ搴撲綅璇︽儏 */
+export function getWarehouseArea(id: number) {
+  return requestClient.get<WlsWarehouseAreaApi.WarehouseArea>(
+    `/mes/wm/warehouse-area/get?id=${id}`,
+  );
+}
+
+/** 鏂板搴撲綅 */
+export function createWarehouseArea(data: WlsWarehouseAreaApi.WarehouseArea) {
+  return requestClient.post('/mes/wm/warehouse-area/create', data);
+}
+
+/** 淇敼搴撲綅 */
+export function updateWarehouseArea(data: WlsWarehouseAreaApi.WarehouseArea) {
+  return requestClient.put('/mes/wm/warehouse-area/update', data);
+}
+
+/** 鍒犻櫎搴撲綅 */
+export function deleteWarehouseArea(id: number) {
+  return requestClient.delete(`/mes/wm/warehouse-area/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/index.ts b/src/api/wls/warehouse/index.ts
new file mode 100644
index 0000000..c31419d
--- /dev/null
+++ b/src/api/wls/warehouse/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesWmWarehouseApi {
+  /** MES 浠撳簱 */
+  export interface Warehouse {
+    id?: number; // 浠撳簱缂栧彿
+    code?: string; // 浠撳簱缂栫爜
+    name?: string; // 浠撳簱鍚嶇О
+    address?: string; // 鍦板潃
+    area?: number; // 闈㈢Н
+    chargeUserId?: number; // 璐熻矗浜�
+    frozen?: boolean; // 鏄惁鍐荤粨
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ浠撳簱鍒嗛〉 */
+export function getWarehousePage(params: PageParam) {
+  return requestClient.get<PageResult<MesWmWarehouseApi.Warehouse>>(
+    '/mes/wm/warehouse/page',
+    { params },
+  );
+}
+
+/** 鏌ヨ浠撳簱绮剧畝鍒楄〃 */
+export function getWarehouseSimpleList() {
+  return requestClient.get<MesWmWarehouseApi.Warehouse[]>(
+    '/mes/wm/warehouse/simple-list',
+  );
+}
+
+/** 鏌ヨ浠撳簱璇︽儏 */
+export function getWarehouse(id: number) {
+  return requestClient.get<MesWmWarehouseApi.Warehouse>(
+    `/mes/wm/warehouse/get?id=${id}`,
+  );
+}
+
+/** 鏂板浠撳簱 */
+export function createWarehouse(data: MesWmWarehouseApi.Warehouse) {
+  return requestClient.post('/mes/wm/warehouse/create', data);
+}
+
+/** 淇敼浠撳簱 */
+export function updateWarehouse(data: MesWmWarehouseApi.Warehouse) {
+  return requestClient.put('/mes/wm/warehouse/update', data);
+}
+
+/** 鍒犻櫎浠撳簱 */
+export function deleteWarehouse(id: number) {
+  return requestClient.delete(`/mes/wm/warehouse/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/location/index.ts b/src/api/wls/warehouse/location/index.ts
new file mode 100644
index 0000000..486113c
--- /dev/null
+++ b/src/api/wls/warehouse/location/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsWarehouseLocationApi {
+  /** MES 搴撳尯 */
+  export interface WarehouseLocation {
+    id?: number; // 搴撳尯缂栧彿
+    code?: string; // 搴撳尯缂栫爜
+    name?: string; // 搴撳尯鍚嶇О
+    warehouseId?: number; // 浠撳簱缂栧彿
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    area?: number; // 闈㈢Н
+    frozen?: boolean; // 鏄惁鍐荤粨
+    remark?: string; // 澶囨敞
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+}
+
+/** 鏌ヨ搴撳尯鍒嗛〉 */
+export function getWarehouseLocationPage(params: PageParam) {
+  return requestClient.get<
+    PageResult<WlsWarehouseLocationApi.WarehouseLocation>
+  >('/mes/wm/warehouse-location/page', { params });
+}
+
+/** 鏌ヨ搴撳尯绮剧畝鍒楄〃 */
+export function getWarehouseLocationSimpleList(warehouseId?: number) {
+  return requestClient.get<WlsWarehouseLocationApi.WarehouseLocation[]>(
+    '/mes/wm/warehouse-location/simple-list',
+    { params: { warehouseId } },
+  );
+}
+
+/** 鏌ヨ搴撳尯璇︽儏 */
+export function getWarehouseLocation(id: number) {
+  return requestClient.get<WlsWarehouseLocationApi.WarehouseLocation>(
+    `/mes/wm/warehouse-location/get?id=${id}`,
+  );
+}
+
+/** 鏂板搴撳尯 */
+export function createWarehouseLocation(
+  data: WlsWarehouseLocationApi.WarehouseLocation,
+) {
+  return requestClient.post('/mes/wm/warehouse-location/create', data);
+}
+
+/** 淇敼搴撳尯 */
+export function updateWarehouseLocation(
+  data: WlsWarehouseLocationApi.WarehouseLocation,
+) {
+  return requestClient.put('/mes/wm/warehouse-location/update', data);
+}
+
+/** 鍒犻櫎搴撳尯 */
+export function deleteWarehouseLocation(id: number) {
+  return requestClient.delete(`/mes/wm/warehouse-location/delete?id=${id}`);
+}
+
+/** 鎵归噺璁剧疆搴撳尯涓嬫墍鏈夊簱浣嶇殑娣锋斁瑙勫垯 */
+export function updateAreaByLocationId(
+  locationId: number,
+  allowItemMixing?: boolean,
+  allowBatchMixing?: boolean,
+) {
+  return requestClient.put(
+    '/mes/wm/warehouse-location/update-by-location-id',
+    null,
+    { params: { allowBatchMixing, allowItemMixing, locationId } },
+  );
+}
diff --git a/src/api/wms/approval/index.ts b/src/api/wms/approval/index.ts
new file mode 100644
index 0000000..4676014
--- /dev/null
+++ b/src/api/wms/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WmsApprovalApi {
+  /** 瀹℃壒閰嶇疆 */
+  export interface ApprovalConfig {
+    id?: number; // 閰嶇疆缂栧彿
+    warehouseId: number; // 浠撳簱ID
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    bizType: string; // 涓氬姟绫诲瀷(INBOUND/OUTBOUND/TRANSFER/CHECK)
+    approvalEnabled: boolean; // 鏄惁鍚敤瀹℃壒
+    processDefinitionKey?: string; // 娴佺▼瀹氫箟Key
+    processDefinitionName?: string; // 娴佺▼瀹氫箟鍚嶇О
+    categoryCode?: string; // 娴佺▼鍒嗙被缂栫爜
+    createTime?: Date; // 鍒涘缓鏃堕棿
+  }
+
+  /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+  export interface ProcessSimpleInfo {
+    key: string; // 娴佺▼瀹氫箟Key
+    name: string; // 娴佺▼瀹氫箟鍚嶇О
+  }
+
+  /** 涓氬姟绫诲瀷鏋氫妇 */
+  export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig>(
+    '/mes/wms/approval/config/get',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+    '/mes/wms/approval/config/list',
+    {
+      params: { warehouseId },
+    },
+  );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+  return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+    '/mes/wms/approval/config/all-list',
+  );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WmsApprovalApi.ApprovalConfig) {
+  return requestClient.post('/mes/wms/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+  return requestClient.get<WmsApprovalApi.ProcessSimpleInfo[]>(
+    '/mes/wms/approval/process-list',
+    {
+      params: { warehouseId, bizType },
+    },
+  );
+}
diff --git a/src/views/mes/wm/arrivalnotice/components/index.ts b/src/views/arrivalnotice/components/index.ts
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/index.ts
rename to src/views/arrivalnotice/components/index.ts
diff --git a/src/views/mes/wm/arrivalnotice/components/line-select-dialog.vue b/src/views/arrivalnotice/components/line-select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/line-select-dialog.vue
rename to src/views/arrivalnotice/components/line-select-dialog.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/line-select.vue b/src/views/arrivalnotice/components/line-select.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/line-select.vue
rename to src/views/arrivalnotice/components/line-select.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/select-dialog.vue b/src/views/arrivalnotice/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/select-dialog.vue
rename to src/views/arrivalnotice/components/select-dialog.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/select.vue b/src/views/arrivalnotice/components/select.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/select.vue
rename to src/views/arrivalnotice/components/select.vue
diff --git a/src/views/mes/wm/arrivalnotice/data.ts b/src/views/arrivalnotice/data.ts
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/data.ts
rename to src/views/arrivalnotice/data.ts
diff --git a/src/views/mes/wm/arrivalnotice/index.vue b/src/views/arrivalnotice/index.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/index.vue
rename to src/views/arrivalnotice/index.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/form.vue b/src/views/arrivalnotice/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/form.vue
rename to src/views/arrivalnotice/modules/form.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/line-form.vue b/src/views/arrivalnotice/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/line-form.vue
rename to src/views/arrivalnotice/modules/line-form.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/line-list.vue b/src/views/arrivalnotice/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/line-list.vue
rename to src/views/arrivalnotice/modules/line-list.vue
diff --git a/src/views/mes/wm/barcode/components/barcode.vue b/src/views/barcode/components/barcode.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/barcode.vue
rename to src/views/barcode/components/barcode.vue
diff --git a/src/views/mes/wm/barcode/components/detail.vue b/src/views/barcode/components/detail.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/detail.vue
rename to src/views/barcode/components/detail.vue
diff --git a/src/views/mes/wm/barcode/components/index.ts b/src/views/barcode/components/index.ts
similarity index 100%
rename from src/views/mes/wm/barcode/components/index.ts
rename to src/views/barcode/components/index.ts
diff --git a/src/views/mes/wm/barcode/components/printer-label.vue b/src/views/barcode/components/printer-label.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/printer-label.vue
rename to src/views/barcode/components/printer-label.vue
diff --git a/src/views/mes/wm/barcode/config/data.ts b/src/views/barcode/config/data.ts
similarity index 100%
rename from src/views/mes/wm/barcode/config/data.ts
rename to src/views/barcode/config/data.ts
diff --git a/src/views/mes/wm/barcode/config/index.vue b/src/views/barcode/config/index.vue
similarity index 100%
rename from src/views/mes/wm/barcode/config/index.vue
rename to src/views/barcode/config/index.vue
diff --git a/src/views/mes/wm/barcode/config/modules/form.vue b/src/views/barcode/config/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/barcode/config/modules/form.vue
rename to src/views/barcode/config/modules/form.vue
diff --git a/src/views/mes/wm/barcode/data.ts b/src/views/barcode/data.ts
similarity index 100%
rename from src/views/mes/wm/barcode/data.ts
rename to src/views/barcode/data.ts
diff --git a/src/views/mes/wm/barcode/index.vue b/src/views/barcode/index.vue
similarity index 100%
rename from src/views/mes/wm/barcode/index.vue
rename to src/views/barcode/index.vue
diff --git a/src/views/mes/wm/barcode/modules/form.vue b/src/views/barcode/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/barcode/modules/form.vue
rename to src/views/barcode/modules/form.vue
diff --git a/src/views/mes/wm/batch/components/detail.vue b/src/views/batch/components/detail.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/detail.vue
rename to src/views/batch/components/detail.vue
diff --git a/src/views/mes/wm/batch/components/index.ts b/src/views/batch/components/index.ts
similarity index 100%
rename from src/views/mes/wm/batch/components/index.ts
rename to src/views/batch/components/index.ts
diff --git a/src/views/mes/wm/batch/components/select-dialog.vue b/src/views/batch/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/select-dialog.vue
rename to src/views/batch/components/select-dialog.vue
diff --git a/src/views/mes/wm/batch/components/select.vue b/src/views/batch/components/select.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/select.vue
rename to src/views/batch/components/select.vue
diff --git a/src/views/mes/wm/batch/data.ts b/src/views/batch/data.ts
similarity index 100%
rename from src/views/mes/wm/batch/data.ts
rename to src/views/batch/data.ts
diff --git a/src/views/erp/purchase/order/components/index.ts b/src/views/erp/purchase/order/components/index.ts
new file mode 100644
index 0000000..7861658
--- /dev/null
+++ b/src/views/erp/purchase/order/components/index.ts
@@ -0,0 +1,4 @@
+import ErpPurchaseOrderSelect from './select.vue';
+import ErpPurchaseOrderSelectDialog from './select-dialog.vue';
+
+export { ErpPurchaseOrderSelect, ErpPurchaseOrderSelectDialog };
\ No newline at end of file
diff --git a/src/views/erp/purchase/order/components/select-dialog.vue b/src/views/erp/purchase/order/components/select-dialog.vue
new file mode 100644
index 0000000..d2dace1
--- /dev/null
+++ b/src/views/erp/purchase/order/components/select-dialog.vue
@@ -0,0 +1,196 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getPurchaseOrderPage } from '#/api/erp/purchase/order';
+
+defineOptions({ name: 'ErpPurchaseOrderSelectDialog' });
+
+const emit = defineEmits<{
+  selected: [rows: ErpPurchaseOrderApi.PurchaseOrder[]];
+}>();
+
+const open = ref(false);
+const multiple = ref(true);
+const selectedRows = ref<ErpPurchaseOrderApi.PurchaseOrder[]>([]);
+const preSelectedIds = ref<number[]>([]);
+
+/** 鑾峰彇褰撳墠琛ㄦ牸鏁版嵁 */
+function getTableRows() {
+  return gridApi.grid.getTableData().fullData as ErpPurchaseOrderApi.PurchaseOrder[];
+}
+
+/** 鑾峰彇澶氶�夎褰� */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, ErpPurchaseOrderApi.PurchaseOrder>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as ErpPurchaseOrderApi.PurchaseOrder[];
+  records.forEach((row) => {
+    if (row.id) {
+      selectedMap.set(row.id, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: ErpPurchaseOrderApi.PurchaseOrder) {
+  selectedRows.value = [row];
+}
+
+/** 澶勭悊琛屽弻鍑� */
+async function handleCellDblclick({ row }: { row: ErpPurchaseOrderApi.PurchaseOrder }) {
+  if (multiple.value) {
+    const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+    await gridApi.grid.setCheckboxRow(row, !selected);
+    selectedRows.value = getMultipleSelectedRows();
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐噰璐鍗� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = getTableRows();
+  for (const row of rows) {
+    if (row.id && preSelectedIds.value.includes(row.id)) {
+      if (multiple.value) {
+        await gridApi.grid.setCheckboxRow(row, true);
+      } else {
+        await gridApi.grid.setRadioRow(row);
+        selectedRows.value = [row];
+        return;
+      }
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+/** 鍒楄〃瀛楁 */
+function useGridColumns(isMultiple: boolean): VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>['columns'] {
+  return [
+    { type: isMultiple ? 'checkbox' : 'radio', width: 50 },
+    { field: 'no', title: '璁㈠崟鍙�', minWidth: 140 },
+    { field: 'supplierName', title: '渚涘簲鍟�', minWidth: 120 },
+    { field: 'orderTime', title: '璁㈠崟鏃堕棿', width: 120, formatter: 'formatDate' },
+    { field: 'totalCount', title: '鍚堣鏁伴噺', width: 100 },
+    { field: 'totalPrice', title: '鍚堣閲戦', width: 100 },
+  ];
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useGridColumns(true),
+    height: 400,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getPurchaseOrderPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+  } as VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: ErpPurchaseOrderApi.PurchaseOrder }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+}
+
+/** 鎵撳紑閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? true;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  await resetQueryState();
+}
+
+/** 纭閫夋嫨閲囪喘璁㈠崟 */
+function handleConfirm() {
+  const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+  if (rows.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', multiple.value ? rows : [rows[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="閲囪喘璁㈠崟閫夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @ok="handleConfirm"
+    @cancel="closeModal"
+  >
+    <Grid table-title="閲囪喘璁㈠崟鍒楄〃" />
+  </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/order/components/select.vue b/src/views/erp/purchase/order/components/select.vue
new file mode 100644
index 0000000..a1db22d
--- /dev/null
+++ b/src/views/erp/purchase/order/components/select.vue
@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getPurchaseOrder } from '#/api/erp/purchase/order';
+
+import ErpPurchaseOrderSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'ErpPurchaseOrderSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨閲囪喘璁㈠崟',
+  },
+);
+const emit = defineEmits<{
+  change: [item: ErpPurchaseOrderApi.PurchaseOrder | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof ErpPurchaseOrderSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<ErpPurchaseOrderApi.PurchaseOrder>();
+
+const displayLabel = computed(() => selectedItem.value?.no ?? '');
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁閲囪喘璁㈠崟缂栧彿鍥炴樉閫夋嫨鍣� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getPurchaseOrder(id);
+}
+
+watch(
+  () => props.modelValue,
+  (value) => {
+    resolveItemById(value);
+  },
+  { immediate: true },
+);
+
+/** 娓呯┖宸查�夐噰璐鍗� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 鍥炲~閫変腑鐨勯噰璐鍗� */
+function handleSelected(rows: ErpPurchaseOrderApi.PurchaseOrder[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>璁㈠崟鍙凤細{{ selectedItem.no || '-' }}</div>
+          <div>渚涘簲鍟嗭細{{ selectedItem.supplierName || '-' }}</div>
+          <div>璁㈠崟鏃堕棿锛歿{ selectedItem.orderTime || '-' }}</div>
+          <div>鍚堣閲戦锛歿{ selectedItem.totalPrice || '-' }} 鍏�</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <ErpPurchaseOrderSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/erp/stock/warehouseMapping/data.ts b/src/views/erp/stock/warehouseMapping/data.ts
new file mode 100644
index 0000000..219c3bc
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/data.ts
@@ -0,0 +1,143 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpWarehouseMappingApi } from '#/api/erp/warehouseMapping';
+
+import { getWarehouseSimpleList as getErpWarehouseSimpleList } from '#/api/erp/stock/warehouse';
+import { getWarehouseSimpleList as getMesWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 鐘舵�侀�夐」 */
+export const STATUS_OPTIONS = [
+  { label: '鍚敤', value: 1 },
+  { label: '绂佺敤', value: 0 },
+];
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'erpWarehouseId',
+      label: 'ERP浠撳簱',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getErpWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨ERP浠撳簱',
+      },
+    },
+    {
+      fieldName: 'mesWarehouseId',
+      label: 'MES浠撳簱',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getMesWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨MES浠撳簱',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: STATUS_OPTIONS,
+        placeholder: '璇烽�夋嫨鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<ErpWarehouseMappingApi.WarehouseMapping>['columns'] {
+  return [
+    {
+      field: 'erpWarehouseName',
+      title: 'ERP浠撳簱',
+      minWidth: 160,
+    },
+    {
+      field: 'mesWarehouseName',
+      title: 'MES浠撳簱',
+      minWidth: 160,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 100,
+      align: 'center',
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          options: STATUS_OPTIONS,
+          colorKeyMap: {
+            0: 'error',
+            1: 'success',
+          },
+        },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 160,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'erpWarehouseId',
+      label: 'ERP浠撳簱',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getErpWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨ERP浠撳簱',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'mesWarehouseId',
+      label: 'MES浠撳簱',
+      component: 'ApiSelect',
+      componentProps: {
+        api: getMesWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨MES浠撳簱',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'RadioGroup',
+      defaultValue: 1,
+      componentProps: {
+        options: STATUS_OPTIONS,
+      },
+    },
+  ];
+}
diff --git a/src/views/erp/stock/warehouseMapping/index.vue b/src/views/erp/stock/warehouseMapping/index.vue
new file mode 100644
index 0000000..b1a6f6b
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/index.vue
@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpWarehouseMappingApi } from '#/api/erp/warehouseMapping';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteMapping,
+  getMappingPage,
+} from '#/api/erp/warehouseMapping';
+import { $t } from '#/locales';
+
+import MappingForm from './modules/form.vue';
+import { useGridColumns, useGridFormSchema } from './data';
+
+defineOptions({ name: 'ErpWarehouseMapping' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: MappingForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鏄犲皠 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 缂栬緫鏄犲皠 */
+function handleEdit(row: ErpWarehouseMappingApi.WarehouseMapping) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏄犲皠 */
+async function handleDelete(row: ErpWarehouseMappingApi.WarehouseMapping) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.erpWarehouseName]),
+    duration: 0,
+  });
+  try {
+    await deleteMapping(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.erpWarehouseName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getMappingPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<ErpWarehouseMappingApi.WarehouseMapping>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+
+    <Grid table-title="浠撳簱鏄犲皠鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['浠撳簱鏄犲皠']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['erp:warehouse:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['erp:warehouse:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['erp:warehouse:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.erpWarehouseName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/erp/stock/warehouseMapping/modules/form.vue b/src/views/erp/stock/warehouseMapping/modules/form.vue
new file mode 100644
index 0000000..2b95d07
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/modules/form.vue
@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createMapping, getMapping, updateMapping } from '#/api/erp/warehouseMapping';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ErpWarehouseMappingForm' });
+
+const emit = defineEmits<{ success: [] }>();
+
+const formType = ref<'create' | 'update'>('create');
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const values = await formApi.getValues();
+    try {
+      if (formType.value === 'create') {
+        await createMapping(values);
+        message.success('鍒涘缓鎴愬姛');
+      } else {
+        await updateMapping(values);
+        message.success('鏇存柊鎴愬姛');
+      }
+      emit('success');
+      modalApi.close();
+    } catch (error) {
+      console.error('淇濆瓨澶辫触:', error);
+    }
+  },
+  onOpenChange(isOpen) {
+    if (isOpen) {
+      const data = modalApi.getData() as {
+        formType: 'create' | 'update';
+        id?: number;
+      };
+      if (data) {
+        formType.value = data.formType;
+        if (data.formType === 'update' && data.id) {
+          // 鍔犺浇璇︽儏
+          getMapping(data.id).then((detail) => {
+            formApi.setValues(detail);
+          });
+        } else {
+          formApi.resetForm();
+        }
+      }
+    }
+  },
+});
+
+const modalTitle = computed(() => {
+  return formType.value === 'create' ? '鏂板浠撳簱鏄犲皠' : '缂栬緫浠撳簱鏄犲皠';
+});
+</script>
+
+<template>
+  <Modal :title="modalTitle" class="w-[500px]">
+    <Form />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/itemreceipt/data.ts b/src/views/itemreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/itemreceipt/data.ts
rename to src/views/itemreceipt/data.ts
diff --git a/src/views/mes/wm/itemreceipt/index.vue b/src/views/itemreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/index.vue
rename to src/views/itemreceipt/index.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-form.vue b/src/views/itemreceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/detail-form.vue
rename to src/views/itemreceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-list.vue b/src/views/itemreceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/detail-list.vue
rename to src/views/itemreceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/form.vue b/src/views/itemreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/form.vue
rename to src/views/itemreceipt/modules/form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/line-form.vue b/src/views/itemreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/line-form.vue
rename to src/views/itemreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/line-list.vue b/src/views/itemreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/line-list.vue
rename to src/views/itemreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/materialstock/components/index.ts b/src/views/materialstock/components/index.ts
similarity index 100%
rename from src/views/mes/wm/materialstock/components/index.ts
rename to src/views/materialstock/components/index.ts
diff --git a/src/views/mes/wm/materialstock/components/select-dialog.vue b/src/views/materialstock/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/components/select-dialog.vue
rename to src/views/materialstock/components/select-dialog.vue
diff --git a/src/views/mes/wm/materialstock/components/select.vue b/src/views/materialstock/components/select.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/components/select.vue
rename to src/views/materialstock/components/select.vue
diff --git a/src/views/mes/wm/materialstock/data.ts b/src/views/materialstock/data.ts
similarity index 100%
rename from src/views/mes/wm/materialstock/data.ts
rename to src/views/materialstock/data.ts
diff --git a/src/views/mes/wm/materialstock/index.vue b/src/views/materialstock/index.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/index.vue
rename to src/views/materialstock/index.vue
diff --git a/src/views/mes/wm/miscissue/data.ts b/src/views/miscissue/data.ts
similarity index 100%
rename from src/views/mes/wm/miscissue/data.ts
rename to src/views/miscissue/data.ts
diff --git a/src/views/mes/wm/miscissue/index.vue b/src/views/miscissue/index.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/index.vue
rename to src/views/miscissue/index.vue
diff --git a/src/views/mes/wm/miscissue/modules/form.vue b/src/views/miscissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/form.vue
rename to src/views/miscissue/modules/form.vue
diff --git a/src/views/mes/wm/miscissue/modules/line-form.vue b/src/views/miscissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/line-form.vue
rename to src/views/miscissue/modules/line-form.vue
diff --git a/src/views/mes/wm/miscissue/modules/line-list.vue b/src/views/miscissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/line-list.vue
rename to src/views/miscissue/modules/line-list.vue
diff --git a/src/views/mes/wm/miscreceipt/data.ts b/src/views/miscreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/miscreceipt/data.ts
rename to src/views/miscreceipt/data.ts
diff --git a/src/views/mes/wm/miscreceipt/index.vue b/src/views/miscreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/index.vue
rename to src/views/miscreceipt/index.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/form.vue b/src/views/miscreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/form.vue
rename to src/views/miscreceipt/modules/form.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/line-form.vue b/src/views/miscreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/line-form.vue
rename to src/views/miscreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/line-list.vue b/src/views/miscreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/line-list.vue
rename to src/views/miscreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/outsourceissue/data.ts b/src/views/outsourceissue/data.ts
similarity index 100%
rename from src/views/mes/wm/outsourceissue/data.ts
rename to src/views/outsourceissue/data.ts
diff --git a/src/views/mes/wm/outsourceissue/index.vue b/src/views/outsourceissue/index.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/index.vue
rename to src/views/outsourceissue/index.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-form.vue b/src/views/outsourceissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/detail-form.vue
rename to src/views/outsourceissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-list.vue b/src/views/outsourceissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/detail-list.vue
rename to src/views/outsourceissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/form.vue b/src/views/outsourceissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/form.vue
rename to src/views/outsourceissue/modules/form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/line-form.vue b/src/views/outsourceissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/line-form.vue
rename to src/views/outsourceissue/modules/line-form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/line-list.vue b/src/views/outsourceissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/line-list.vue
rename to src/views/outsourceissue/modules/line-list.vue
diff --git a/src/views/mes/wm/outsourcereceipt/data.ts b/src/views/outsourcereceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/data.ts
rename to src/views/outsourcereceipt/data.ts
diff --git a/src/views/mes/wm/outsourcereceipt/index.vue b/src/views/outsourcereceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/index.vue
rename to src/views/outsourcereceipt/index.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-form.vue b/src/views/outsourcereceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/detail-form.vue
rename to src/views/outsourcereceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-list.vue b/src/views/outsourcereceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/detail-list.vue
rename to src/views/outsourcereceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/form.vue b/src/views/outsourcereceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/form.vue
rename to src/views/outsourcereceipt/modules/form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/line-form.vue b/src/views/outsourcereceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/line-form.vue
rename to src/views/outsourcereceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/line-list.vue b/src/views/outsourcereceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/line-list.vue
rename to src/views/outsourcereceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/packages/components/index.ts b/src/views/packages/components/index.ts
similarity index 100%
rename from src/views/mes/wm/packages/components/index.ts
rename to src/views/packages/components/index.ts
diff --git a/src/views/mes/wm/packages/components/select-dialog.vue b/src/views/packages/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/packages/components/select-dialog.vue
rename to src/views/packages/components/select-dialog.vue
diff --git a/src/views/mes/wm/packages/components/select.vue b/src/views/packages/components/select.vue
similarity index 100%
rename from src/views/mes/wm/packages/components/select.vue
rename to src/views/packages/components/select.vue
diff --git a/src/views/mes/wm/packages/data.ts b/src/views/packages/data.ts
similarity index 100%
rename from src/views/mes/wm/packages/data.ts
rename to src/views/packages/data.ts
diff --git a/src/views/mes/wm/packages/index.vue b/src/views/packages/index.vue
similarity index 100%
rename from src/views/mes/wm/packages/index.vue
rename to src/views/packages/index.vue
diff --git a/src/views/mes/wm/packages/modules/form.vue b/src/views/packages/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/form.vue
rename to src/views/packages/modules/form.vue
diff --git a/src/views/mes/wm/packages/modules/line-form.vue b/src/views/packages/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/line-form.vue
rename to src/views/packages/modules/line-form.vue
diff --git a/src/views/mes/wm/packages/modules/package-line-list.vue b/src/views/packages/modules/package-line-list.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/package-line-list.vue
rename to src/views/packages/modules/package-line-list.vue
diff --git a/src/views/mes/wm/packages/modules/sub-package-list.vue b/src/views/packages/modules/sub-package-list.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/sub-package-list.vue
rename to src/views/packages/modules/sub-package-list.vue
diff --git a/src/views/mes/wm/productissue/data.ts b/src/views/productissue/data.ts
similarity index 100%
rename from src/views/mes/wm/productissue/data.ts
rename to src/views/productissue/data.ts
diff --git a/src/views/mes/wm/productissue/index.vue b/src/views/productissue/index.vue
similarity index 100%
rename from src/views/mes/wm/productissue/index.vue
rename to src/views/productissue/index.vue
diff --git a/src/views/mes/wm/productissue/modules/detail-form.vue b/src/views/productissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/detail-form.vue
rename to src/views/productissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/productissue/modules/detail-list.vue b/src/views/productissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/detail-list.vue
rename to src/views/productissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/productissue/modules/form.vue b/src/views/productissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/form.vue
rename to src/views/productissue/modules/form.vue
diff --git a/src/views/mes/wm/productissue/modules/line-form.vue b/src/views/productissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/line-form.vue
rename to src/views/productissue/modules/line-form.vue
diff --git a/src/views/mes/wm/productissue/modules/line-list.vue b/src/views/productissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/line-list.vue
rename to src/views/productissue/modules/line-list.vue
diff --git a/src/views/mes/wm/productreceipt/data.ts b/src/views/productreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/productreceipt/data.ts
rename to src/views/productreceipt/data.ts
diff --git a/src/views/mes/wm/productreceipt/index.vue b/src/views/productreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/index.vue
rename to src/views/productreceipt/index.vue
diff --git a/src/views/mes/wm/productreceipt/modules/detail-form.vue b/src/views/productreceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/detail-form.vue
rename to src/views/productreceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/detail-list.vue b/src/views/productreceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/detail-list.vue
rename to src/views/productreceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/productreceipt/modules/form.vue b/src/views/productreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/form.vue
rename to src/views/productreceipt/modules/form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/line-form.vue b/src/views/productreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/line-form.vue
rename to src/views/productreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/line-list.vue b/src/views/productreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/line-list.vue
rename to src/views/productreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/productsales/data.ts b/src/views/productsales/data.ts
similarity index 100%
rename from src/views/mes/wm/productsales/data.ts
rename to src/views/productsales/data.ts
diff --git a/src/views/mes/wm/productsales/index.vue b/src/views/productsales/index.vue
similarity index 100%
rename from src/views/mes/wm/productsales/index.vue
rename to src/views/productsales/index.vue
diff --git a/src/views/mes/wm/productsales/modules/detail-form.vue b/src/views/productsales/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/detail-form.vue
rename to src/views/productsales/modules/detail-form.vue
diff --git a/src/views/mes/wm/productsales/modules/detail-list.vue b/src/views/productsales/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/detail-list.vue
rename to src/views/productsales/modules/detail-list.vue
diff --git a/src/views/mes/wm/productsales/modules/form.vue b/src/views/productsales/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/form.vue
rename to src/views/productsales/modules/form.vue
diff --git a/src/views/mes/wm/productsales/modules/line-form.vue b/src/views/productsales/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/line-form.vue
rename to src/views/productsales/modules/line-form.vue
diff --git a/src/views/mes/wm/productsales/modules/line-list.vue b/src/views/productsales/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/line-list.vue
rename to src/views/productsales/modules/line-list.vue
diff --git a/src/views/mes/wm/returnissue/data.ts b/src/views/returnissue/data.ts
similarity index 100%
rename from src/views/mes/wm/returnissue/data.ts
rename to src/views/returnissue/data.ts
diff --git a/src/views/mes/wm/returnissue/index.vue b/src/views/returnissue/index.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/index.vue
rename to src/views/returnissue/index.vue
diff --git a/src/views/mes/wm/returnissue/modules/detail-form.vue b/src/views/returnissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/detail-form.vue
rename to src/views/returnissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnissue/modules/detail-list.vue b/src/views/returnissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/detail-list.vue
rename to src/views/returnissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnissue/modules/form.vue b/src/views/returnissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/form.vue
rename to src/views/returnissue/modules/form.vue
diff --git a/src/views/mes/wm/returnissue/modules/line-form.vue b/src/views/returnissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/line-form.vue
rename to src/views/returnissue/modules/line-form.vue
diff --git a/src/views/mes/wm/returnissue/modules/line-list.vue b/src/views/returnissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/line-list.vue
rename to src/views/returnissue/modules/line-list.vue
diff --git a/src/views/mes/wm/returnsales/data.ts b/src/views/returnsales/data.ts
similarity index 100%
rename from src/views/mes/wm/returnsales/data.ts
rename to src/views/returnsales/data.ts
diff --git a/src/views/mes/wm/returnsales/index.vue b/src/views/returnsales/index.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/index.vue
rename to src/views/returnsales/index.vue
diff --git a/src/views/mes/wm/returnsales/modules/detail-form.vue b/src/views/returnsales/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/detail-form.vue
rename to src/views/returnsales/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnsales/modules/detail-list.vue b/src/views/returnsales/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/detail-list.vue
rename to src/views/returnsales/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnsales/modules/form.vue b/src/views/returnsales/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/form.vue
rename to src/views/returnsales/modules/form.vue
diff --git a/src/views/mes/wm/returnsales/modules/line-form.vue b/src/views/returnsales/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/line-form.vue
rename to src/views/returnsales/modules/line-form.vue
diff --git a/src/views/mes/wm/returnsales/modules/line-list.vue b/src/views/returnsales/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/line-list.vue
rename to src/views/returnsales/modules/line-list.vue
diff --git a/src/views/mes/wm/returnvendor/data.ts b/src/views/returnvendor/data.ts
similarity index 100%
rename from src/views/mes/wm/returnvendor/data.ts
rename to src/views/returnvendor/data.ts
diff --git a/src/views/mes/wm/returnvendor/index.vue b/src/views/returnvendor/index.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/index.vue
rename to src/views/returnvendor/index.vue
diff --git a/src/views/mes/wm/returnvendor/modules/detail-form.vue b/src/views/returnvendor/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/detail-form.vue
rename to src/views/returnvendor/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/detail-list.vue b/src/views/returnvendor/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/detail-list.vue
rename to src/views/returnvendor/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnvendor/modules/form.vue b/src/views/returnvendor/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/form.vue
rename to src/views/returnvendor/modules/form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/line-form.vue b/src/views/returnvendor/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/line-form.vue
rename to src/views/returnvendor/modules/line-form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/line-list.vue b/src/views/returnvendor/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/line-list.vue
rename to src/views/returnvendor/modules/line-list.vue
diff --git a/src/views/mes/wm/salesnotice/components/index.ts b/src/views/salesnotice/components/index.ts
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/index.ts
rename to src/views/salesnotice/components/index.ts
diff --git a/src/views/mes/wm/salesnotice/components/line-select-dialog.vue b/src/views/salesnotice/components/line-select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/line-select-dialog.vue
rename to src/views/salesnotice/components/line-select-dialog.vue
diff --git a/src/views/mes/wm/salesnotice/components/line-select.vue b/src/views/salesnotice/components/line-select.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/line-select.vue
rename to src/views/salesnotice/components/line-select.vue
diff --git a/src/views/mes/wm/salesnotice/components/select-dialog.vue b/src/views/salesnotice/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/select-dialog.vue
rename to src/views/salesnotice/components/select-dialog.vue
diff --git a/src/views/mes/wm/salesnotice/components/select.vue b/src/views/salesnotice/components/select.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/select.vue
rename to src/views/salesnotice/components/select.vue
diff --git a/src/views/mes/wm/salesnotice/data.ts b/src/views/salesnotice/data.ts
similarity index 100%
rename from src/views/mes/wm/salesnotice/data.ts
rename to src/views/salesnotice/data.ts
diff --git a/src/views/mes/wm/salesnotice/index.vue b/src/views/salesnotice/index.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/index.vue
rename to src/views/salesnotice/index.vue
diff --git a/src/views/mes/wm/salesnotice/modules/form.vue b/src/views/salesnotice/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/form.vue
rename to src/views/salesnotice/modules/form.vue
diff --git a/src/views/mes/wm/salesnotice/modules/line-form.vue b/src/views/salesnotice/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/line-form.vue
rename to src/views/salesnotice/modules/line-form.vue
diff --git a/src/views/mes/wm/salesnotice/modules/line-list.vue b/src/views/salesnotice/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/line-list.vue
rename to src/views/salesnotice/modules/line-list.vue
diff --git a/src/views/mes/wm/sn/data.ts b/src/views/sn/data.ts
similarity index 100%
rename from src/views/mes/wm/sn/data.ts
rename to src/views/sn/data.ts
diff --git a/src/views/mes/wm/sn/index.vue b/src/views/sn/index.vue
similarity index 100%
rename from src/views/mes/wm/sn/index.vue
rename to src/views/sn/index.vue
diff --git a/src/views/mes/wm/sn/modules/detail.vue b/src/views/sn/modules/detail.vue
similarity index 100%
rename from src/views/mes/wm/sn/modules/detail.vue
rename to src/views/sn/modules/detail.vue
diff --git a/src/views/mes/wm/sn/modules/form.vue b/src/views/sn/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/sn/modules/form.vue
rename to src/views/sn/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/condition-value-input.vue b/src/views/stocktaking/plan/components/condition-value-input.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/condition-value-input.vue
rename to src/views/stocktaking/plan/components/condition-value-input.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/index.ts b/src/views/stocktaking/plan/components/index.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/index.ts
rename to src/views/stocktaking/plan/components/index.ts
diff --git a/src/views/mes/wm/stocktaking/plan/components/select-dialog.vue b/src/views/stocktaking/plan/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/select-dialog.vue
rename to src/views/stocktaking/plan/components/select-dialog.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/select.vue b/src/views/stocktaking/plan/components/select.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/select.vue
rename to src/views/stocktaking/plan/components/select.vue
diff --git a/src/views/mes/wm/stocktaking/plan/data.ts b/src/views/stocktaking/plan/data.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/data.ts
rename to src/views/stocktaking/plan/data.ts
diff --git a/src/views/mes/wm/stocktaking/plan/index.vue b/src/views/stocktaking/plan/index.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/index.vue
rename to src/views/stocktaking/plan/index.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/form.vue b/src/views/stocktaking/plan/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/form.vue
rename to src/views/stocktaking/plan/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/param-form.vue b/src/views/stocktaking/plan/modules/param-form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/param-form.vue
rename to src/views/stocktaking/plan/modules/param-form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/param-list.vue b/src/views/stocktaking/plan/modules/param-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/param-list.vue
rename to src/views/stocktaking/plan/modules/param-list.vue
diff --git a/src/views/mes/wm/stocktaking/task/data.ts b/src/views/stocktaking/task/data.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/data.ts
rename to src/views/stocktaking/task/data.ts
diff --git a/src/views/mes/wm/stocktaking/task/index.vue b/src/views/stocktaking/task/index.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/index.vue
rename to src/views/stocktaking/task/index.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/form.vue b/src/views/stocktaking/task/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/form.vue
rename to src/views/stocktaking/task/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/line-list.vue b/src/views/stocktaking/task/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/line-list.vue
rename to src/views/stocktaking/task/modules/line-list.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/result-form.vue b/src/views/stocktaking/task/modules/result-form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/result-form.vue
rename to src/views/stocktaking/task/modules/result-form.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/result-list.vue b/src/views/stocktaking/task/modules/result-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/result-list.vue
rename to src/views/stocktaking/task/modules/result-list.vue
diff --git a/src/views/mes/wm/transfer/data.ts b/src/views/transfer/data.ts
similarity index 100%
rename from src/views/mes/wm/transfer/data.ts
rename to src/views/transfer/data.ts
diff --git a/src/views/mes/wm/transfer/index.vue b/src/views/transfer/index.vue
similarity index 100%
rename from src/views/mes/wm/transfer/index.vue
rename to src/views/transfer/index.vue
diff --git a/src/views/mes/wm/transfer/modules/detail-form.vue b/src/views/transfer/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/detail-form.vue
rename to src/views/transfer/modules/detail-form.vue
diff --git a/src/views/mes/wm/transfer/modules/detail-list.vue b/src/views/transfer/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/detail-list.vue
rename to src/views/transfer/modules/detail-list.vue
diff --git a/src/views/mes/wm/transfer/modules/form.vue b/src/views/transfer/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/form.vue
rename to src/views/transfer/modules/form.vue
diff --git a/src/views/mes/wm/transfer/modules/line-form.vue b/src/views/transfer/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/line-form.vue
rename to src/views/transfer/modules/line-form.vue
diff --git a/src/views/mes/wm/transfer/modules/line-list.vue b/src/views/transfer/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/line-list.vue
rename to src/views/transfer/modules/line-list.vue
diff --git a/src/views/mes/wm/warehouse/area/data.ts b/src/views/warehouse/area/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/area/data.ts
rename to src/views/warehouse/area/data.ts
diff --git a/src/views/mes/wm/warehouse/area/index.vue b/src/views/warehouse/area/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/area/index.vue
rename to src/views/warehouse/area/index.vue
diff --git a/src/views/mes/wm/warehouse/area/modules/form.vue b/src/views/warehouse/area/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/area/modules/form.vue
rename to src/views/warehouse/area/modules/form.vue
diff --git a/src/views/mes/wm/warehouse/components/area-select.vue b/src/views/warehouse/components/area-select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/area-select.vue
rename to src/views/warehouse/components/area-select.vue
diff --git a/src/views/mes/wm/warehouse/components/index.ts b/src/views/warehouse/components/index.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/components/index.ts
rename to src/views/warehouse/components/index.ts
diff --git a/src/views/mes/wm/warehouse/components/location-select.vue b/src/views/warehouse/components/location-select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/location-select.vue
rename to src/views/warehouse/components/location-select.vue
diff --git a/src/views/mes/wm/warehouse/components/select.vue b/src/views/warehouse/components/select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/select.vue
rename to src/views/warehouse/components/select.vue
diff --git a/src/views/mes/wm/warehouse/data.ts b/src/views/warehouse/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/data.ts
rename to src/views/warehouse/data.ts
diff --git a/src/views/mes/wm/warehouse/index.vue b/src/views/warehouse/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/index.vue
rename to src/views/warehouse/index.vue
diff --git a/src/views/mes/wm/warehouse/location/data.ts b/src/views/warehouse/location/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/location/data.ts
rename to src/views/warehouse/location/data.ts
diff --git a/src/views/mes/wm/warehouse/location/index.vue b/src/views/warehouse/location/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/location/index.vue
rename to src/views/warehouse/location/index.vue
diff --git a/src/views/mes/wm/warehouse/location/modules/form.vue b/src/views/warehouse/location/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/location/modules/form.vue
rename to src/views/warehouse/location/modules/form.vue
diff --git a/src/views/mes/wm/warehouse/modules/form.vue b/src/views/warehouse/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/modules/form.vue
rename to src/views/warehouse/modules/form.vue
diff --git a/src/views/wls/approval/config/data.ts b/src/views/wls/approval/config/data.ts
new file mode 100644
index 0000000..49ecc5b
--- /dev/null
+++ b/src/views/wls/approval/config/data.ts
@@ -0,0 +1,209 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { h } from 'vue';
+
+import { Switch, Tag } from 'ant-design-vue';
+
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 涓氬姟绫诲瀷閫夐」 */
+export const BIZ_TYPE_OPTIONS = [
+  { label: '鍏ュ簱瀹℃壒', value: 'INBOUND' },
+  { label: '鍑哄簱瀹℃壒', value: 'OUTBOUND' },
+  { label: '璋冩嫧瀹℃壒', value: 'TRANSFER' },
+  { label: '鐩樼偣瀹℃壒', value: 'CHECK' },
+];
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+  handleChange: (row: any, bizType: string, enabled: boolean) => void,
+): VxeTableGridOptions['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'warehouseCode',
+      title: '浠撳簱缂栫爜',
+      minWidth: 120,
+    },
+    {
+      title: '鍏ュ簱瀹℃壒',
+      width: 100,
+      align: 'center',
+      slots: {
+        default: ({ row }) => {
+          const config = row.configs?.find(
+            (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'INBOUND',
+          );
+          return h(Switch, {
+            checked: config?.approvalEnabled ?? false,
+            checkedChildren: '寮�',
+            unCheckedChildren: '鍏�',
+            onChange: (checked: boolean) => {
+              handleChange(row, 'INBOUND', checked);
+            },
+          });
+        },
+      },
+    },
+    {
+      title: '鍑哄簱瀹℃壒',
+      width: 100,
+      align: 'center',
+      slots: {
+        default: ({ row }) => {
+          const config = row.configs?.find(
+            (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'OUTBOUND',
+          );
+          return h(Switch, {
+            checked: config?.approvalEnabled ?? false,
+            checkedChildren: '寮�',
+            unCheckedChildren: '鍏�',
+            onChange: (checked: boolean) => {
+              handleChange(row, 'OUTBOUND', checked);
+            },
+          });
+        },
+      },
+    },
+    {
+      title: '璋冩嫧瀹℃壒',
+      width: 100,
+      align: 'center',
+      slots: {
+        default: ({ row }) => {
+          const config = row.configs?.find(
+            (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'TRANSFER',
+          );
+          return h(Switch, {
+            checked: config?.approvalEnabled ?? false,
+            checkedChildren: '寮�',
+            unCheckedChildren: '鍏�',
+            onChange: (checked: boolean) => {
+              handleChange(row, 'TRANSFER', checked);
+            },
+          });
+        },
+      },
+    },
+    {
+      title: '鐩樼偣瀹℃壒',
+      width: 100,
+      align: 'center',
+      slots: {
+        default: ({ row }) => {
+          const config = row.configs?.find(
+            (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'CHECK',
+          );
+          return h(Switch, {
+            checked: config?.approvalEnabled ?? false,
+            checkedChildren: '寮�',
+            unCheckedChildren: '鍏�',
+            onChange: (checked: boolean) => {
+              handleChange(row, 'CHECK', checked);
+            },
+          });
+        },
+      },
+    },
+    {
+      title: '娴佺▼閰嶇疆',
+      minWidth: 200,
+      slots: {
+        default: ({ row }) => {
+          const configs = row.configs || [];
+          if (configs.length === 0) {
+            return h(Tag, { color: 'default' }, () => '鏈厤缃�');
+          }
+          const enabledConfigs = configs.filter(
+            (c: WmsApprovalApi.ApprovalConfig) => c.approvalEnabled,
+          );
+          if (enabledConfigs.length === 0) {
+            return h(Tag, { color: 'default' }, () => '鏈厤缃�');
+          }
+          return h(
+            'div',
+            { class: 'flex flex-wrap gap-1' },
+            enabledConfigs.map((config: WmsApprovalApi.ApprovalConfig) => {
+              const option = BIZ_TYPE_OPTIONS.find(
+                (o) => o.value === config.bizType,
+              );
+              return h(
+                Tag,
+                {
+                  color: config.processDefinitionKey ? 'green' : 'orange',
+                },
+                () => option?.label || config.bizType,
+              );
+            }),
+          );
+        },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 100,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 娴佺▼閰嶇疆琛ㄥ崟 */
+export function useProcessFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: BIZ_TYPE_OPTIONS,
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'processDefinitionKey',
+      label: '瀹℃壒娴佺▼',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        labelField: 'name',
+        valueField: 'key',
+        placeholder: '璇烽�夋嫨瀹℃壒娴佺▼',
+      },
+      rules: 'required',
+    },
+  ];
+}
\ No newline at end of file
diff --git a/src/views/wls/approval/config/index.vue b/src/views/wls/approval/config/index.vue
new file mode 100644
index 0000000..667edfd
--- /dev/null
+++ b/src/views/wls/approval/config/index.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { saveApprovalConfig } from '#/api/mes/wm/approval';
+
+import ProcessConfigForm from './modules/process-config-form.vue';
+import { useGridColumns, useGridFormSchema } from './data';
+
+defineOptions({ name: 'WlsApprovalConfig' });
+
+const [ProcessConfigModal, processConfigModalApi] = useVbenModal({
+  connectedComponent: ProcessConfigForm,
+  destroyOnClose: true,
+});
+
+/** 浠撳簱閰嶇疆鍒楄〃 */
+const warehouseConfigList = ref<any[]>([]);
+
+/** 瀹℃壒寮�鍏冲彉鏇� */
+async function handleApprovalChange(
+  row: any,
+  bizType: string,
+  enabled: boolean,
+) {
+  try {
+    await saveApprovalConfig({
+      warehouseId: row.warehouseId,
+      bizType: bizType as WmsApprovalApi.BizType,
+      approvalEnabled: enabled,
+    });
+    // 鏇存柊鏈湴鏁版嵁
+    const config = row.configs?.find(
+      (c: WmsApprovalApi.ApprovalConfig) => c.bizType === bizType,
+    );
+    if (config) {
+      config.approvalEnabled = enabled;
+    } else {
+      row.configs = row.configs || [];
+      row.configs.push({
+        warehouseId: row.warehouseId,
+        bizType,
+        approvalEnabled: enabled,
+      });
+    }
+    message.success('閰嶇疆淇濆瓨鎴愬姛');
+  } catch {
+    gridApi.query();
+  }
+}
+
+/** 閰嶇疆娴佺▼ */
+function handleConfigProcess(row: any) {
+  processConfigModalApi.setData({ warehouseId: row.warehouseId, warehouseName: row.warehouseName }).open();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(handleApprovalChange),
+    height: 'auto',
+    keepSource: true,
+    data: warehouseConfigList,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          // TODO: 璋冪敤鍚庣鎺ュ彛鑾峰彇鎵�鏈変粨搴撳強鍏跺鎵归厤缃�
+          return { list: [], total: 0 };
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'warehouseId',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <ProcessConfigModal @success="gridApi.query()" />
+
+    <Grid table-title="瀹℃壒閰嶇疆">
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '閰嶇疆娴佺▼',
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-approval:save'],
+              onClick: handleConfigProcess.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/approval/config/modules/process-config-form.vue b/src/views/wls/approval/config/modules/process-config-form.vue
new file mode 100644
index 0000000..d041fec
--- /dev/null
+++ b/src/views/wls/approval/config/modules/process-config-form.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { computed, ref, watch } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  getAvailableProcessList,
+  getApprovalConfig,
+  saveApprovalConfig,
+} from '#/api/mes/wm/approval';
+
+import { BIZ_TYPE_OPTIONS, useProcessFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [] }>();
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  layout: 'horizontal',
+  schema: useProcessFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const values = await formApi.getValues();
+    if (!values.bizType) {
+      message.error('璇烽�夋嫨涓氬姟绫诲瀷');
+      return;
+    }
+    if (!values.processDefinitionKey) {
+      message.error('璇烽�夋嫨瀹℃壒娴佺▼');
+      return;
+    }
+
+    await saveApprovalConfig({
+      warehouseId: values.warehouseId,
+      bizType: values.bizType,
+      approvalEnabled: true,
+      processDefinitionKey: values.processDefinitionKey,
+    });
+    message.success('閰嶇疆淇濆瓨鎴愬姛');
+    emit('success');
+    modalApi.close();
+  },
+  onOpenChange(isOpen) {
+    if (isOpen) {
+      const data = modalApi.getData() as {
+        warehouseId: number;
+        warehouseName: string;
+      };
+      if (data) {
+        formApi.setValues({
+          warehouseId: data.warehouseId,
+          warehouseName: data.warehouseName,
+          bizType: undefined,
+          processDefinitionKey: undefined,
+        });
+      }
+    }
+  },
+});
+
+/** 褰撳墠閫変腑鐨勪笟鍔$被鍨� */
+const currentBizType = computed(() => {
+  const values = formApi.getValues();
+  return values.bizType;
+});
+
+/** 鐩戝惉涓氬姟绫诲瀷鍙樺寲锛屽姞杞藉搴旂殑娴佺▼鍒楄〃 */
+watch(currentBizType, async (bizType) => {
+  if (bizType) {
+    const data = modalApi.getData() as { warehouseId: number };
+    if (data?.warehouseId) {
+      try {
+        const config = await getApprovalConfig(data.warehouseId, bizType);
+        if (config) {
+          formApi.setValues({
+            processDefinitionKey: config.processDefinitionKey,
+          });
+        }
+      } catch {
+        // 蹇界暐閿欒
+      }
+
+      formApi.updateSchema([
+        {
+          fieldName: 'processDefinitionKey',
+          componentProps: {
+            api: async () => {
+              return await getAvailableProcessList(data.warehouseId, bizType);
+            },
+          },
+        },
+      ]);
+    }
+  }
+});
+</script>
+
+<template>
+  <Modal title="閰嶇疆瀹℃壒娴佺▼" class="w-[500px]">
+    <Form />
+  </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/mes/wm/arrivalnotice/components/index.ts b/src/views/wls/arrivalnotice/components/index.ts
similarity index 100%
copy from src/views/mes/wm/arrivalnotice/components/index.ts
copy to src/views/wls/arrivalnotice/components/index.ts
diff --git a/src/views/wls/arrivalnotice/components/line-select-dialog.vue b/src/views/wls/arrivalnotice/components/line-select-dialog.vue
new file mode 100644
index 0000000..350c4f4
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/line-select-dialog.vue
@@ -0,0 +1,203 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getArrivalNoticeLinePage } from '#/api/mes/wm/arrivalnotice/line';
+
+const emit = defineEmits<{
+  selected: [rows: MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+const syncingSingleSelection = ref(false); // 鏄惁鍚屾鍗曢�夊嬀閫夌姸鎬�
+const selectedRows = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]>([]); // 宸查�夎鍒楄〃
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎缂栧彿鍒楄〃
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(): VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>['columns'] {
+  return [
+    {
+      type: 'checkbox',
+      width: 50,
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'arrivalQuantity',
+      title: '鍒拌揣鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'iqcCheckFlag',
+      title: '鏄惁妫�楠�',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'qualifiedQuantity',
+      title: '鍚堟牸鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 120,
+    },
+  ];
+}
+
+/** 鍗曢�夋ā寮忎笅鍚屾 VXE 鍕鹃�夌姸鎬� */
+async function syncSingleSelection(
+  row?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+  syncingSingleSelection.value = true;
+  await nextTick();
+  await gridApi.grid.clearCheckboxRow();
+  if (row) {
+    await gridApi.grid.setCheckboxRow(row, true);
+  }
+  await nextTick();
+  syncingSingleSelection.value = false;
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖栵紝鍙繚鐣欐渶鍚庝竴鏉� */
+async function handleCheckboxChange({
+  checked,
+  row,
+}: {
+  checked: boolean;
+  row?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine;
+}) {
+  if (syncingSingleSelection.value) {
+    return;
+  }
+  const selected = checked && row ? [row] : [];
+  selectedRows.value = selected;
+  await syncSingleSelection(selected[0]);
+}
+
+/** 鍥炴樉棰勯�夎 */
+function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows =
+    gridApi.grid.getData() as MesWmArrivalNoticeLineApi.ArrivalNoticeLine[];
+  for (const row of rows) {
+    if (row.id && preSelectedIds.value.includes(row.id)) {
+      gridApi.grid.setCheckboxRow(row, true);
+      selectedRows.value = [row];
+    }
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 460,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      reserve: true,
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!noticeId.value) {
+            return { list: [], total: 0 };
+          }
+          return await getArrivalNoticeLinePage({
+            noticeId: noticeId.value,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>,
+  gridEvents: {
+    checkboxChange: handleCheckboxChange,
+  },
+});
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+async function openModal(id: number | undefined, selectedIds?: number[]) {
+  open.value = true;
+  noticeId.value = id;
+  preSelectedIds.value = selectedIds || [];
+  selectedRows.value = [];
+  await nextTick();
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.query();
+  await nextTick();
+  applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+}
+
+/** 纭閫夋嫨琛� */
+function handleConfirm() {
+  if (selectedRows.value.length === 0) {
+    message.warning('璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', [selectedRows.value[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="鍒拌揣閫氱煡鍗曡閫夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鍒拌揣閫氱煡鍗曡鍒楄〃" />
+  </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/components/line-select.vue b/src/views/wls/arrivalnotice/components/line-select.vue
new file mode 100644
index 0000000..bd7296b
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/line-select.vue
@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getArrivalNoticeLine } from '#/api/mes/wm/arrivalnotice/line';
+
+import WmArrivalNoticeLineSelectDialog from './line-select-dialog.vue';
+
+defineOptions({ name: 'WmArrivalNoticeLineSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    noticeId?: number; // 鎵�灞炲埌璐ч�氱煡鍗曠紪鍙�
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    noticeId: undefined,
+    placeholder: '璇烽�夋嫨鍒拌揣閫氱煡鍗曡',
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmArrivalNoticeLineApi.ArrivalNoticeLine | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmArrivalNoticeLineSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>();
+
+const displayLabel = computed(() => {
+  const item = selectedItem.value;
+  if (!item) {
+    return '';
+  }
+  return `${item.itemCode ?? ''} - ${item.itemName ?? ''}`;
+});
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ琛屼俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getArrivalNoticeLine(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** noticeId 鍙樺寲鏃舵竻绌洪�変腑锛堝叧鑱旂殑琛屽凡澶辨晥锛� */
+watch(
+  () => props.noticeId,
+  () => {
+    selectedItem.value = undefined;
+    emit('update:modelValue', undefined);
+    emit('change', undefined);
+  },
+);
+
+/** 娓呯┖宸查�夎 */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled || !props.noticeId) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(props.noticeId, selectedIds);
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+          <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+          <div>瑙勬牸鍨嬪彿锛歿{ selectedItem.specification || '-' }}</div>
+          <div>鍒拌揣鏁伴噺锛歿{ selectedItem.arrivalQuantity ?? '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmArrivalNoticeLineSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/arrivalnotice/components/select-dialog.vue b/src/views/wls/arrivalnotice/components/select-dialog.vue
new file mode 100644
index 0000000..944301b
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/select-dialog.vue
@@ -0,0 +1,288 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getArrivalNoticePage } from '#/api/mes/wm/arrivalnotice';
+
+const emit = defineEmits<{
+  selected: [rows: MesWmArrivalNoticeApi.ArrivalNotice[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(false); // 鏄惁澶氶��
+const fixedStatus = ref<number>(); // 鍥哄畾鐘舵�佺瓫閫�
+const selectedRows = ref<MesWmArrivalNoticeApi.ArrivalNotice[]>([]); // 宸查�夐�氱煡鍗曞垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夐�氱煡鍗曠紪鍙峰垪琛�
+
+/** 鎼滅储琛ㄥ崟 */
+function useSearchSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+    },
+    {
+      fieldName: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+      },
+    },
+  ];
+}
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(
+  multipleSelect = false,
+): VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>['columns'] {
+  return [
+    {
+      type: multipleSelect ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'code',
+      title: '閫氱煡鍗曠紪鍙�',
+      minWidth: 160,
+    },
+    {
+      field: 'name',
+      title: '閫氱煡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'purchaseOrderCode',
+      title: '閲囪喘璁㈠崟缂栧彿',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 120,
+    },
+    {
+      field: 'arrivalDate',
+      title: '鍒拌揣鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_ARRIVAL_NOTICE_STATUS },
+      },
+    },
+  ];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, MesWmArrivalNoticeApi.ArrivalNotice>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as MesWmArrivalNoticeApi.ArrivalNotice[];
+  records.forEach((row) => {
+    const rowId = row.id;
+    if (rowId !== undefined) {
+      selectedMap.set(rowId, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊澶氶�夊嬀閫夊彉鍖� */
+function handleCheckboxSelectChange() {
+  if (!multiple.value) {
+    return;
+  }
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊垏鎹� */
+function handleRadioChange(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+  selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+  await gridApi.grid.setCheckboxRow(row, !selected);
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紝澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+  row,
+}: {
+  row: MesWmArrivalNoticeApi.ArrivalNotice;
+}) {
+  if (multiple.value) {
+    await toggleMultipleRow(row);
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐�氱煡鍗� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = gridApi.grid.getData() as MesWmArrivalNoticeApi.ArrivalNotice[];
+  for (const row of rows) {
+    if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
+      continue;
+    }
+    if (multiple.value) {
+      await gridApi.grid.setCheckboxRow(row, true);
+    } else {
+      await gridApi.grid.setRadioRow(row);
+      selectedRows.value = [row];
+      return;
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useSearchSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(false),
+    height: 520,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      range: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getArrivalNoticePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            status: fixedStatus.value,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: MesWmArrivalNoticeApi.ArrivalNotice }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean; status?: number },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? false;
+  fixedStatus.value = options?.status;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴閫氱煡鍗曢�夋嫨寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  await resetQueryState();
+}
+
+/** 纭閫夋嫨閫氱煡鍗� */
+function handleConfirm() {
+  if (selectedRows.value.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit(
+    'selected',
+    multiple.value ? selectedRows.value : [selectedRows.value[0]!],
+  );
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="鍒拌揣閫氱煡鍗曢�夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鍒拌揣閫氱煡鍗曞垪琛�" />
+  </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/components/select.vue b/src/views/wls/arrivalnotice/components/select.vue
new file mode 100644
index 0000000..51b0876
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/select.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getArrivalNotice } from '#/api/mes/wm/arrivalnotice';
+
+import WmArrivalNoticeSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmArrivalNoticeSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+    status?: number; // 鍥哄畾鐘舵�佺瓫閫�
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨鍒拌揣閫氱煡鍗�',
+    status: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmArrivalNoticeApi.ArrivalNotice | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmArrivalNoticeSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmArrivalNoticeApi.ArrivalNotice>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== undefined &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ閫氱煡鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getArrivalNotice(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夐�氱煡鍗� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, {
+    multiple: false,
+    status: props.status,
+  });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmArrivalNoticeApi.ArrivalNotice[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+          <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+          <div>渚涘簲鍟嗭細{{ selectedItem.vendorName || '-' }}</div>
+          <div>閲囪喘璁㈠崟锛歿{ selectedItem.purchaseOrderCode || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmArrivalNoticeSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/arrivalnotice/data.ts b/src/views/wls/arrivalnotice/data.ts
new file mode 100644
index 0000000..bbed3bb
--- /dev/null
+++ b/src/views/wls/arrivalnotice/data.ts
@@ -0,0 +1,354 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ErpPurchaseOrderSelect } from '#/views/erp/purchase/order/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_ARRIVAL_NOTICE_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'purchaseOrderId',
+      label: '閲囪喘璁㈠崟',
+      component: markRaw(ErpPurchaseOrderSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨閲囪喘璁㈠崟',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'arrivalDate',
+      label: '鍒拌揣鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍒拌揣鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'contactName',
+      label: '鑱旂郴浜�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ仈绯讳汉',
+      },
+    },
+    {
+      fieldName: 'contactTelephone',
+      label: '鑱旂郴鏂瑰紡',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+    },
+    {
+      fieldName: 'purchaseOrderNo',
+      label: '閲囪喘璁㈠崟鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'vendorName',
+      label: '渚涘簲鍟�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'arrivalDate',
+      label: '鍒拌揣鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '閫氱煡鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '閫氱煡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'purchaseOrderNo',
+      title: '閲囪喘璁㈠崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 120,
+    },
+    {
+      field: 'contactName',
+      title: '鑱旂郴浜�',
+      minWidth: 100,
+    },
+    {
+      field: 'contactTelephone',
+      title: '鑱旂郴鏂瑰紡',
+      minWidth: 120,
+    },
+    {
+      field: 'arrivalDate',
+      title: '鍒拌揣鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_ARRIVAL_NOTICE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 180,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫氱煡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+): VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'arrivalQuantity',
+      title: '鍒拌揣鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'iqcCheckFlag',
+      title: '鏄惁妫�楠�',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'qualifiedQuantity',
+      title: '鍚堟牸鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'iqcCode',
+      title: '妫�楠屽崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 120,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 閫氱煡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'arrivalQuantity',
+      label: '鍒拌揣鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0.01,
+        placeholder: '璇疯緭鍏ュ埌璐ф暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'iqcCheckFlag',
+      label: '鏄惁妫�楠�',
+      component: 'Switch',
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/arrivalnotice/index.vue b/src/views/wls/arrivalnotice/index.vue
new file mode 100644
index 0000000..df90182
--- /dev/null
+++ b/src/views/wls/arrivalnotice/index.vue
@@ -0,0 +1,179 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmArrivalNoticeStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteArrivalNotice,
+  exportArrivalNotice,
+  getArrivalNoticePage,
+} from '#/api/mes/wm/arrivalnotice';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鍒拌揣閫氱煡鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鍒拌揣閫氱煡鍗� */
+function handleDetail(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鍒拌揣閫氱煡鍗� */
+function handleEdit(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗� */
+async function handleDelete(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteArrivalNotice(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+  message.info('璇峰墠寰�銆愯川閲忕鐞� - 寰呮浠诲姟銆戜腑杩涜鏉ユ枡妫�楠屾搷浣�');
+}
+
+/** 鎻愮ず鍓嶅線閲囪喘鍏ュ簱妯″潡 */
+function handleReceipt() {
+  message.info('璇峰墠寰�銆愪粨搴撶鐞� - 閲囪喘鍏ュ簱銆戜腑杩涜鍏ュ簱鎿嶄綔');
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportArrivalNotice(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鍒拌揣閫氱煡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getArrivalNoticePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鍒拌揣閫氱煡鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-arrival-notice:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-arrival-notice:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-arrival-notice:update'],
+              ifShow: row.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-arrival-notice:delete'],
+              ifShow: row.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц璐ㄦ',
+              type: 'link',
+              ifShow: row.status === MesWmArrivalNoticeStatusEnum.PENDING_QC,
+              onClick: handleQc,
+            },
+            {
+              label: '鎵ц鍏ュ簱',
+              type: 'link',
+              ifShow:
+                row.status === MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT,
+              onClick: handleReceipt,
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/form.vue b/src/views/wls/arrivalnotice/modules/form.vue
new file mode 100644
index 0000000..a736294
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/form.vue
@@ -0,0 +1,171 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmArrivalNoticeStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createArrivalNotice,
+  getArrivalNotice,
+  submitArrivalNotice,
+  updateArrivalNotice,
+} from '#/api/mes/wm/arrivalnotice';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmArrivalNoticeApi.ArrivalNotice>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鍒拌揣閫氱煡鍗�']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鍒拌揣閫氱煡鍗�'])
+    : $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫氱煡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmArrivalNoticeApi.ArrivalNotice;
+      await updateArrivalNotice({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitArrivalNotice(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmArrivalNoticeApi.ArrivalNotice;
+    try {
+      if (formData.value?.id) {
+        await updateArrivalNotice({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createArrivalNotice(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmArrivalNoticeStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getArrivalNotice(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :notice-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ュ埌璐ч�氱煡鍗曪紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/line-form.vue b/src/views/wls/arrivalnotice/modules/line-form.vue
new file mode 100644
index 0000000..518d16a
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createArrivalNoticeLine,
+  getArrivalNoticeLine,
+  updateArrivalNoticeLine,
+} from '#/api/mes/wm/arrivalnotice/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>();
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鍒拌揣閫氱煡鍗曡'])
+    : $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmArrivalNoticeLineApi.ArrivalNoticeLine;
+    data.noticeId = noticeId.value;
+    try {
+      await (formData.value?.id
+        ? updateArrivalNoticeLine({ ...data, id: formData.value.id })
+        : createArrivalNoticeLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; noticeId: number }>();
+    noticeId.value = data.noticeId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getArrivalNoticeLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/line-list.vue b/src/views/wls/arrivalnotice/modules/line-list.vue
new file mode 100644
index 0000000..cea373f
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteArrivalNoticeLine,
+  getArrivalNoticeLinePage,
+} from '#/api/mes/wm/arrivalnotice/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  noticeId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ noticeId: props.noticeId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmArrivalNoticeLineApi.ArrivalNoticeLine) {
+  lineFormModalApi.setData({ id: row.id, noticeId: props.noticeId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmArrivalNoticeLineApi.ArrivalNoticeLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteArrivalNoticeLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value),
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.noticeId) {
+            return { list: [], total: 0 };
+          }
+          return await getArrivalNoticeLinePage({
+            noticeId: props.noticeId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>,
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/barcode/components/barcode.vue b/src/views/wls/barcode/components/barcode.vue
new file mode 100644
index 0000000..441415f
--- /dev/null
+++ b/src/views/wls/barcode/components/barcode.vue
@@ -0,0 +1,51 @@
+<script lang="ts" setup>
+import type { BarcodeFormat } from '../../../../packages/effects/common-ui/src';
+
+import { computed, ref } from 'vue';
+
+import { BarcodeFormatEnum, Barcode as CommonBarcode } from '../../../../packages/effects/common-ui/src';
+
+defineOptions({ name: 'WlsBarcode' });
+
+const props = withDefaults(
+  defineProps<{
+    content?: string;
+    displayValue?: boolean;
+    format?: number;
+    height?: number;
+    width?: number;
+  }>(),
+  {
+    content: '',
+    displayValue: true,
+    format: BarcodeFormatEnum.QR_CODE,
+    height: 100,
+    width: 200,
+  },
+);
+
+const emit = defineEmits<{ done: [value: string] }>();
+
+const barcodeRef = ref<InstanceType<typeof CommonBarcode>>();
+const commonFormat = computed<BarcodeFormat>(
+  () => (props.format || BarcodeFormatEnum.QR_CODE) as BarcodeFormat,
+);
+
+function getImageBase64() {
+  return barcodeRef.value?.getImageBase64() || '';
+}
+
+defineExpose({ getImageBase64 });
+</script>
+
+<template>
+  <CommonBarcode
+    ref="barcodeRef"
+    :content="content"
+    :display-value="displayValue"
+    :format="commonFormat"
+    :height="height"
+    :width="width"
+    @done="emit('done', $event)"
+  />
+</template>
diff --git a/src/views/wls/barcode/components/detail.vue b/src/views/wls/barcode/components/detail.vue
new file mode 100644
index 0000000..17a9f37
--- /dev/null
+++ b/src/views/wls/barcode/components/detail.vue
@@ -0,0 +1,169 @@
+<script lang="ts" setup>
+import type Barcode from './barcode.vue';
+
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { ref } from 'vue';
+
+import { Button, Empty, message, Modal, Tooltip } from 'ant-design-vue';
+
+import { createBarcode, getBarcodeByBusiness } from '#/api/mes/wm/barcode';
+import { useDescription } from '#/components/description';
+
+import { useBarcodeDetailSchema } from '../data';
+import WlsBarcode from './barcode.vue';
+
+defineOptions({ name: 'WlsBarcodeDetail' });
+
+const open = ref(false);
+const barcodeRef = ref<InstanceType<typeof Barcode>>();
+const barcodeData = ref<Partial<MesWmBarcodeApi.Barcode>>({});
+
+const [Descriptions] = useDescription({
+  bordered: true,
+  column: 1,
+  schema: useBarcodeDetailSchema(),
+  useCard: false,
+});
+
+function openModal(row: Partial<MesWmBarcodeApi.Barcode>) {
+  open.value = true;
+  barcodeData.value = { ...row };
+}
+
+async function openByBusiness(
+  bizId: number,
+  bizType: number,
+  bizCode?: string,
+  bizName?: string,
+) {
+  open.value = true;
+  try {
+    const data = await getBarcodeByBusiness(bizType, bizId);
+    barcodeData.value = data || {
+      bizCode,
+      bizId,
+      bizName,
+      bizType,
+      content: '',
+    };
+    if (!data) {
+      message.warning('鏈壘鍒板搴旀潯鐮佹暟鎹�');
+    }
+  } catch {
+    barcodeData.value = { bizCode, bizId, bizName, bizType, content: '' };
+    message.error('鍔犺浇鏉$爜鏁版嵁澶辫触');
+  }
+}
+
+defineExpose({ open: openModal, openByBusiness });
+
+function escapeHtml(value: string) {
+  return value
+    .replaceAll('&', '&amp;')
+    .replaceAll('<', '&lt;')
+    .replaceAll('>', '&gt;');
+}
+
+function handlePrint() {
+  const base64 = barcodeRef.value?.getImageBase64();
+  if (!base64) {
+    message.warning('鏉$爜鐢熸垚澶辫触锛屾棤娉曟墦鍗�');
+    return;
+  }
+  const printWindow = window.open('', '_blank');
+  if (!printWindow) {
+    message.error('鏃犳硶鎵撳紑鎵撳嵃绐楀彛锛岃妫�鏌ユ祻瑙堝櫒璁剧疆');
+    return;
+  }
+  printWindow.document.write(`<!DOCTYPE html>
+<html><head><meta charset="UTF-8"><title>鎵撳嵃鏉$爜</title>
+<style>*{margin:0;padding:0}body{font-family:Arial,sans-serif;padding:20px}.print-container{text-align:center}.barcode-img{max-width:100%;margin:20px 0}.info{margin-top:20px;text-align:left;font-size:12px}.info p{margin:5px 0}@media print{body{padding:0}.print-container{padding:20px}}</style>
+</head><body><div class="print-container">
+<img src="${base64}" class="barcode-img" alt="鏉$爜" />
+<div class="info">
+<p><strong>涓氬姟缂栫爜:</strong> ${escapeHtml(barcodeData.value.bizCode || '')}</p>
+<p><strong>涓氬姟鍚嶇О:</strong> ${escapeHtml(barcodeData.value.bizName || '')}</p>
+<p><strong>鏉$爜鍐呭:</strong> ${escapeHtml(barcodeData.value.content || '')}</p>
+</div></div></body></html>`);
+  printWindow.document.close();
+  printWindow.addEventListener('load', () => {
+    setTimeout(() => printWindow.print(), 500);
+  });
+}
+
+function handleDownload() {
+  const base64 = barcodeRef.value?.getImageBase64();
+  if (!base64) {
+    message.warning('鏉$爜鐢熸垚澶辫触锛屾棤娉曚笅杞�');
+    return;
+  }
+  const link = document.createElement('a');
+  link.href = base64;
+  link.download = `barcode_${barcodeData.value.bizCode || 'unknown'}_${Date.now()}.png`;
+  link.click();
+  message.success('涓嬭浇鎴愬姛');
+}
+
+async function handleGenerate() {
+  const { bizCode, bizId, bizName, bizType } = barcodeData.value;
+  if (!bizType || !bizId) {
+    message.warning('缂哄皯涓氬姟绫诲瀷鎴栦笟鍔$紪鍙凤紝鏃犳硶鐢熸垚鏉$爜');
+    return;
+  }
+  await createBarcode({
+    bizCode: bizCode || '',
+    bizId,
+    bizName: bizName || '',
+    bizType,
+  });
+  message.success('鏉$爜鐢熸垚鎴愬姛');
+  const data = await getBarcodeByBusiness(bizType, bizId);
+  if (data) {
+    barcodeData.value = { ...data };
+  }
+}
+</script>
+
+<template>
+  <Modal v-model:open="open" title="鏌ョ湅鏉$爜" width="500px">
+    <div>
+      <div
+        class="mb-5 flex min-h-50 items-center justify-center rounded bg-gray-100 p-5"
+      >
+        <div
+          v-if="barcodeData.content"
+          class="flex items-center justify-center"
+        >
+          <WlsBarcode
+            ref="barcodeRef"
+            :content="barcodeData.content"
+            :format="barcodeData.format"
+            :height="150"
+            :width="400"
+          />
+        </div>
+        <Empty v-else description="鏆傛棤鏉$爜鏁版嵁" />
+      </div>
+      <Descriptions :data="barcodeData">
+        <template #content>
+          <Tooltip :title="barcodeData.content">
+            <span
+              class="inline-block max-w-75 overflow-hidden text-ellipsis whitespace-nowrap"
+            >
+              {{ barcodeData.content }}
+            </span>
+          </Tooltip>
+        </template>
+      </Descriptions>
+    </div>
+    <template #footer>
+      <Button v-if="!barcodeData.content" @click="handleGenerate">
+        鐢熸垚
+      </Button>
+      <Button type="primary" @click="handlePrint">鎵撳嵃</Button>
+      <Button @click="handleDownload">涓嬭浇</Button>
+      <Button @click="open = false">鍏抽棴</Button>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/barcode/components/index.ts b/src/views/wls/barcode/components/index.ts
similarity index 100%
copy from src/views/mes/wm/barcode/components/index.ts
copy to src/views/wls/barcode/components/index.ts
diff --git a/src/views/wls/barcode/components/printer-label.vue b/src/views/wls/barcode/components/printer-label.vue
new file mode 100644
index 0000000..7fbeee5
--- /dev/null
+++ b/src/views/wls/barcode/components/printer-label.vue
@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import { Button, message } from 'ant-design-vue';
+
+defineOptions({ name: 'WlsPrinterLabel' });
+
+withDefaults(
+  defineProps<{
+    bizCode?: string;
+    bizId?: number;
+    bizType?: string;
+    labelText?: string;
+  }>(),
+  {
+    bizCode: undefined,
+    bizId: undefined,
+    bizType: undefined,
+    labelText: '鏍囩鎵撳嵃',
+  },
+);
+
+function handlePrint() {
+  message.warning('鏍囩鎵撳嵃鍔熻兘鏆傛湭瀹炵幇锛屾暚璇锋湡寰�');
+}
+</script>
+
+<template>
+  <Button type="link" @click="handlePrint">
+    {{ labelText }}
+  </Button>
+</template>
diff --git a/src/views/wls/barcode/config/data.ts b/src/views/wls/barcode/config/data.ts
new file mode 100644
index 0000000..08d8dee
--- /dev/null
+++ b/src/views/wls/barcode/config/data.ts
@@ -0,0 +1,114 @@
+import type { VbenFormSchema } from '#/adapter/form';
+
+import { h } from 'vue';
+
+import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+
+/** 琛ㄥ崟 */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'format',
+      label: '鏉$爜鏍煎紡',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
+        placeholder: '璇烽�夋嫨鏉$爜鏍煎紡',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+      dependencies: {
+        triggerFields: ['id'],
+        componentProps: (values) => ({
+          disabled: !!values.id, // 缂栬緫鏃朵笟鍔$被鍨嬩笉鍏佽鍙樻洿
+        }),
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'contentFormat',
+      label: '鍐呭鏍煎紡妯℃澘',
+      component: 'Input',
+      componentProps: {
+        placeholder: '鏀寔{BUSINESSCODE}鍗犱綅绗︼紝濡傦細WH-{BUSINESSCODE}',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'contentExample',
+      label: '鍐呭鏍蜂緥',
+      component: 'Input',
+      componentProps: {
+        placeholder: '濡傦細WH-WH001',
+      },
+    },
+    {
+      fieldName: 'autoGenerateFlag',
+      label: '鑷姩鐢熸垚',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(true),
+    },
+    {
+      fieldName: 'defaultTemplate',
+      label: '榛樿鎵撳嵃妯℃澘',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鎵撳嵃妯℃澘',
+        readonly: true,
+      },
+      // TODO @鑺嬭壙锛氬悗缁鎺� UReport 鎶ヨ〃閫夋嫨鍣紝瀹炵幇鎵撳嵃妯℃澘閫夋嫨鍔熻兘
+      suffix: () =>
+        h(
+          Button,
+          {
+            onClick: () =>
+              message.warning('鎵撳嵃妯℃澘閫夋嫨鍔熻兘鏆傛湭瀹炵幇锛屾暚璇锋湡寰�'),
+          },
+          { default: () => '璁剧疆' },
+        ),
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'RadioGroup',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+      },
+      rules: z.number().default(CommonStatusEnum.ENABLE),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 2,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/barcode/config/index.vue b/src/views/wls/barcode/config/index.vue
new file mode 100644
index 0000000..5e12679
--- /dev/null
+++ b/src/views/wls/barcode/config/index.vue
@@ -0,0 +1,150 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteBarcodeConfig,
+  getBarcodeConfigPage,
+  updateBarcodeConfig,
+} from '#/api/mes/wm/barcode/config';
+import { $t } from '#/locales';
+
+import { useConfigGridColumns, useConfigGridFormSchema } from '../data';
+import ConfigForm from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: ConfigForm,
+  destroyOnClose: true,
+});
+
+/** 鑷姩鐢熸垚寮�鍏冲彉鏇� */
+async function handleAutoGenerateChange(
+  row: MesWmBarcodeConfigApi.BarcodeConfig,
+): Promise<boolean> {
+  const text = row.autoGenerateFlag ? '鍚敤' : '鍋滅敤';
+  try {
+    await confirm(`纭瑕�${text}鑷姩鐢熸垚鍚楋紵`);
+  } catch {
+    return false;
+  }
+  await updateBarcodeConfig(row);
+  message.success(`${text}鎴愬姛`);
+  return true;
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鏉$爜閰嶇疆 */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鏉$爜閰嶇疆 */
+function handleEdit(row: MesWmBarcodeConfigApi.BarcodeConfig) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鏉$爜閰嶇疆 */
+async function handleDelete(row: MesWmBarcodeConfigApi.BarcodeConfig) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [
+      row.contentFormat || `#${row.id}`,
+    ]),
+    duration: 0,
+  });
+  try {
+    await deleteBarcodeConfig(row.id!);
+    message.success(
+      $t('ui.actionMessage.deleteSuccess', [row.contentFormat || `#${row.id}`]),
+    );
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useConfigGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useConfigGridColumns(handleAutoGenerateChange),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getBarcodeConfigPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鏉$爜閰嶇疆鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鏉$爜閰嶇疆']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-barcode-config:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-barcode-config:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-barcode-config:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [
+                  row.contentFormat || `#${row.id}`,
+                ]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/barcode/config/modules/form.vue b/src/views/wls/barcode/config/modules/form.vue
new file mode 100644
index 0000000..cf50143
--- /dev/null
+++ b/src/views/wls/barcode/config/modules/form.vue
@@ -0,0 +1,89 @@
+<script lang="ts" setup>
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createBarcodeConfig,
+  getBarcodeConfig,
+  updateBarcodeConfig,
+} from '#/api/mes/wm/barcode/config';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmBarcodeConfigApi.BarcodeConfig>();
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鏉$爜閰嶇疆'])
+    : $t('ui.actionTitle.create', ['鏉$爜閰嶇疆']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 130,
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmBarcodeConfigApi.BarcodeConfig;
+    try {
+      await (formData.value?.id
+        ? updateBarcodeConfig(data)
+        : createBarcodeConfig(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<MesWmBarcodeConfigApi.BarcodeConfig>();
+    if (!data || !data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getBarcodeConfig(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-2/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/barcode/data.ts b/src/views/wls/barcode/data.ts
new file mode 100644
index 0000000..abd401c
--- /dev/null
+++ b/src/views/wls/barcode/data.ts
@@ -0,0 +1,735 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, markRaw } from 'vue';
+
+import {
+  BarcodeBizTypeEnum,
+  CommonStatusEnum,
+  DICT_TYPE,
+  MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+import { formatDateTime } from '../../../packages/utils/src';
+
+import { z } from '#/adapter/form';
+import { generateBarcodeContent } from '#/api/mes/wm/barcode';
+import { DictTag } from '#/components/dict-tag';
+import { DvMachinerySelect } from '#/views/mes/dv/machinery/components';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import {
+  MdWorkshopSelect,
+  MdWorkstationSelect,
+} from '#/views/mes/md/workstation/components';
+import { ProCardSelect } from '#/views/mes/pro/card/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { TmToolSelect } from '#/views/mes/tm/tool/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import { UserSelect } from '#/views/system/user/components';
+
+import { WmMaterialStockSelect } from '../materialstock/components';
+import { WmPackageSelect } from '../packages/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '../warehouse/components';
+
+/** 涓氬姟瀵硅薄閫変腑鍚庡洖濉笟鍔$紪鐮併�佷笟鍔″悕绉般�佹潯鐮佸唴瀹� */
+async function syncBizDetail(formApi: undefined | VbenFormApi, item: any) {
+  if (!formApi) {
+    return;
+  }
+  const values = await formApi.getValues();
+  const bizType = values.bizType as number | undefined;
+  if (!item) {
+    await formApi.setValues({
+      bizCode: undefined,
+      bizName: undefined,
+      content: undefined,
+    });
+    return;
+  }
+  let bizCode: string | undefined;
+  let bizName: string | undefined;
+  switch (bizType) {
+    case BarcodeBizTypeEnum.BATCH: {
+      bizCode = item.code;
+      bizName = item.itemName || item.code;
+      break;
+    }
+    case BarcodeBizTypeEnum.PACKAGE: {
+      bizCode = item.code;
+      bizName = item.clientName || item.code;
+      break;
+    }
+    case BarcodeBizTypeEnum.PROCARD: {
+      bizCode = item.code;
+      bizName = item.workOrderName || item.code;
+      break;
+    }
+    case BarcodeBizTypeEnum.STOCK: {
+      bizCode = item.itemCode;
+      bizName = item.itemName;
+      break;
+    }
+    default: {
+      bizCode = item.code || item.username;
+      bizName = item.name || item.nickname;
+    }
+  }
+  // 鍏堝洖濉笟鍔$紪鐮併�佸悕绉板苟娓呯┖鏃ф潯鐮佸唴瀹�
+  await formApi.setValues({ bizCode, bizName, content: undefined });
+  // 鍐嶆牴鎹笟鍔$被鍨� + 涓氬姟缂栫爜鐢熸垚鏉$爜鍐呭
+  if (bizType && bizCode) {
+    const content = await generateBarcodeContent(bizType, bizCode);
+    await formApi.setFieldValue('content', content);
+  }
+}
+
+/** 鏂板/淇敼鏉$爜鐨勮〃鍗� */
+export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        // 涓氬姟绫诲瀷鍙樻洿鏃舵竻绌轰笟鍔″瓧娈�
+        onChange: () =>
+          formApi?.setValues({
+            bizId: undefined,
+            bizCode: undefined,
+            bizName: undefined,
+            content: undefined,
+            locationWarehouseId: undefined,
+            areaWarehouseId: undefined,
+            areaLocationId: undefined,
+          }),
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+      rules: 'required',
+    },
+    // ==================== 涓氬姟瀵硅薄閫夋嫨鍣紙鎸� bizType 鍒囨崲锛� ====================
+    {
+      fieldName: 'bizId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.WAREHOUSE,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'locationWarehouseId',
+      label: '搴撳尯路浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        onChange: () =>
+          formApi?.setValues({
+            bizId: undefined,
+            bizCode: undefined,
+            bizName: undefined,
+            content: undefined,
+          }),
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
+      },
+    },
+    {
+      fieldName: 'bizId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      dependencies: {
+        triggerFields: ['bizType', 'locationWarehouseId'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
+        componentProps: (values) => ({
+          onChange: (item: any) => syncBizDetail(formApi, item),
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.locationWarehouseId,
+        }),
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'areaWarehouseId',
+      label: '搴撲綅路浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        onChange: () =>
+          formApi?.setValues({
+            areaLocationId: undefined,
+            bizId: undefined,
+            bizCode: undefined,
+            bizName: undefined,
+            content: undefined,
+          }),
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+      },
+    },
+    {
+      fieldName: 'areaLocationId',
+      label: '搴撲綅路搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      dependencies: {
+        triggerFields: ['bizType', 'areaWarehouseId'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+        componentProps: (values) => ({
+          onChange: () =>
+            formApi?.setValues({
+              bizId: undefined,
+              bizCode: undefined,
+              bizName: undefined,
+              content: undefined,
+            }),
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.areaWarehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'bizId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      dependencies: {
+        triggerFields: ['bizType', 'areaLocationId'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+        componentProps: (values) => ({
+          locationId: values.areaLocationId,
+          onChange: (item: any) => syncBizDetail(formApi, item),
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.WORKORDER,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '璁惧',
+      component: markRaw(DvMachinerySelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.MACHINERY,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '浜у搧鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.ITEM,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.VENDOR,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSTATION,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '杞﹂棿',
+      component: markRaw(MdWorkshopSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSHOP,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.CLIENT,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '宸ュ叿',
+      component: markRaw(TmToolSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.TOOL,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '搴撳瓨',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.STOCK,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '瑁呯鍗�',
+      component: markRaw(WmPackageSelect),
+      componentProps: {
+        childableOnly: true,
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.PACKAGE,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '鎵规',
+      component: markRaw(WmBatchSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.BATCH,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '娴佽浆鍗�',
+      component: markRaw(ProCardSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.PROCARD,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '浜哄憳',
+      component: markRaw(UserSelect),
+      componentProps: {
+        onChange: (item: any) => syncBizDetail(formApi, item),
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) => values.bizType === BarcodeBizTypeEnum.USER,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizId',
+      label: '涓氬姟缂栧彿',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 1,
+        placeholder: '璇疯緭鍏ヤ笟鍔$紪鍙�',
+      },
+      dependencies: {
+        triggerFields: ['bizType'],
+        show: (values) =>
+          values.bizType !== undefined &&
+          ![
+            BarcodeBizTypeEnum.AREA,
+            BarcodeBizTypeEnum.BATCH,
+            BarcodeBizTypeEnum.CLIENT,
+            BarcodeBizTypeEnum.ITEM,
+            BarcodeBizTypeEnum.LOCATION,
+            BarcodeBizTypeEnum.MACHINERY,
+            BarcodeBizTypeEnum.PACKAGE,
+            BarcodeBizTypeEnum.PROCARD,
+            BarcodeBizTypeEnum.STOCK,
+            BarcodeBizTypeEnum.TOOL,
+            BarcodeBizTypeEnum.USER,
+            BarcodeBizTypeEnum.VENDOR,
+            BarcodeBizTypeEnum.WAREHOUSE,
+            BarcodeBizTypeEnum.WORKORDER,
+            BarcodeBizTypeEnum.WORKSHOP,
+            BarcodeBizTypeEnum.WORKSTATION,
+          ].includes(values.bizType),
+      },
+    },
+    {
+      fieldName: 'bizCode',
+      label: '涓氬姟缂栫爜',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '鑷姩濉厖',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'bizName',
+      label: '涓氬姟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '鑷姩濉厖',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'content',
+      label: '鏉$爜鍐呭',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潯鐮佸唴瀹规垨鑷姩鐢熸垚',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'RadioGroup',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+      },
+      rules: z.number().default(CommonStatusEnum.ENABLE),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 2,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'bizCode',
+      label: '涓氬姟缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔$紪鐮�',
+      },
+    },
+    {
+      fieldName: 'bizName',
+      label: '涓氬姟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ笟鍔″悕绉�',
+      },
+    },
+    {
+      fieldName: 'content',
+      label: '鏉$爜鍐呭',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潯鐮佸唴瀹�',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmBarcodeApi.Barcode>['columns'] {
+  return [
+    {
+      type: 'checkbox',
+      width: 50,
+    },
+    {
+      field: 'content',
+      title: '鏉$爜',
+      width: 180,
+      slots: { default: 'barcode' },
+    },
+    {
+      field: 'format',
+      title: '鏉$爜鏍煎紡',
+      width: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
+      },
+    },
+    {
+      field: 'bizType',
+      title: '涓氬姟绫诲瀷',
+      width: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
+      },
+    },
+    {
+      field: 'content',
+      title: '鏉$爜鍐呭',
+      minWidth: 160,
+    },
+    {
+      field: 'bizCode',
+      title: '涓氬姟缂栫爜',
+      width: 130,
+    },
+    {
+      field: 'bizName',
+      title: '涓氬姟鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.COMMON_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閰嶇疆鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useConfigGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'format',
+      label: '鏉$爜鏍煎紡',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
+        placeholder: '璇烽�夋嫨鏉$爜鏍煎紡',
+      },
+    },
+    {
+      fieldName: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+    },
+  ];
+}
+
+/** 閰嶇疆鍒楄〃鐨勫瓧娈� */
+export function useConfigGridColumns(
+  onAutoGenerateChange: (
+    row: MesWmBarcodeConfigApi.BarcodeConfig,
+  ) => Promise<boolean>,
+): VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>['columns'] {
+  return [
+    {
+      field: 'id',
+      title: '缂栧彿',
+      width: 100,
+    },
+    {
+      field: 'format',
+      title: '鏉$爜鏍煎紡',
+      width: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
+      },
+    },
+    {
+      field: 'bizType',
+      title: '涓氬姟绫诲瀷',
+      width: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
+      },
+    },
+    {
+      field: 'contentFormat',
+      title: '鍐呭鏍煎紡',
+      minWidth: 160,
+    },
+    {
+      field: 'contentExample',
+      title: '鍐呭鏍蜂緥',
+      minWidth: 160,
+    },
+    {
+      field: 'defaultTemplate',
+      title: '榛樿鎵撳嵃妯℃澘',
+      minWidth: 140,
+    },
+    {
+      field: 'autoGenerateFlag',
+      title: '鑷姩鐢熸垚',
+      width: 100,
+      cellRender: {
+        name: 'CellSwitch',
+        attrs: { beforeChange: onAutoGenerateChange },
+      },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.COMMON_STATUS },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 160,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鏉$爜璇︽儏鐨勬弿杩板瓧娈� */
+export function useBarcodeDetailSchema(): DescriptionItemSchema[] {
+  return [
+    {
+      field: 'format',
+      label: '鏉$爜鏍煎紡',
+      render: (value) =>
+        value === null
+          ? '-'
+          : h(DictTag, { type: DICT_TYPE.MES_WM_BARCODE_FORMAT, value }),
+    },
+    {
+      field: 'bizType',
+      label: '涓氬姟绫诲瀷',
+      render: (value) =>
+        value === null
+          ? '-'
+          : h(DictTag, { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, value }),
+    },
+    {
+      field: 'content',
+      label: '鏉$爜鍐呭',
+      slot: 'content',
+    },
+    {
+      field: 'bizCode',
+      label: '涓氬姟缂栫爜',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'bizName',
+      label: '涓氬姟鍚嶇О',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'status',
+      label: '鐘舵��',
+      render: (value) =>
+        value === null
+          ? '-'
+          : h(DictTag, { type: DICT_TYPE.COMMON_STATUS, value }),
+    },
+    {
+      field: 'createTime',
+      label: '鍒涘缓鏃堕棿',
+      render: (value) => formatDateTime(value) || '-',
+    },
+  ];
+}
diff --git a/src/views/wls/barcode/index.vue b/src/views/wls/barcode/index.vue
new file mode 100644
index 0000000..6dfe978
--- /dev/null
+++ b/src/views/wls/barcode/index.vue
@@ -0,0 +1,234 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteBarcode,
+  exportBarcode,
+  getBarcodePage,
+} from '#/api/mes/wm/barcode';
+import { $t } from '#/locales';
+
+import { Barcode, BarcodeDetail } from './components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const checkedIds = ref<number[]>([]);
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: MesWmBarcodeApi.Barcode[];
+}) {
+  checkedIds.value = records.map((item) => item.id!);
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鏉$爜 */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鏉$爜 */
+function handleEdit(row: MesWmBarcodeApi.Barcode) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鏉$爜 */
+async function handleDelete(row: MesWmBarcodeApi.Barcode) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [
+      row.bizName || row.bizCode || '',
+    ]),
+    duration: 0,
+  });
+  try {
+    await deleteBarcode(row.id!);
+    message.success(
+      $t('ui.actionMessage.deleteSuccess', [row.bizName || row.bizCode || '']),
+    );
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎵归噺鍒犻櫎鏉$爜 */
+async function handleDeleteBatch() {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [`${checkedIds.value.length} 鏉]),
+    duration: 0,
+  });
+  try {
+    await Promise.all(checkedIds.value.map((id) => deleteBarcode(id)));
+    message.success($t('ui.actionMessage.deleteSuccess', ['']));
+    checkedIds.value = [];
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鏌ョ湅鏉$爜 */
+function handleView(row: MesWmBarcodeApi.Barcode) {
+  barcodeDetailRef.value?.open(row);
+}
+
+/** 璺宠浆鏉$爜閰嶇疆 */
+function handleConfig() {
+  router.push({ name: 'WlsBarcodeConfig' });
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportBarcode(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鏉$爜娓呭崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getBarcodePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+      height: 80,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmBarcodeApi.Barcode>,
+  gridEvents: {
+    checkboxAll: handleRowCheckboxChange,
+    checkboxChange: handleRowCheckboxChange,
+  },
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+    <BarcodeDetail ref="barcodeDetailRef" />
+
+    <Grid table-title="鏉$爜鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鏉$爜']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-barcode:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.deleteBatch'),
+              type: 'primary',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-barcode:delete'],
+              disabled: checkedIds.length === 0,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [
+                  `${checkedIds.length} 鏉,
+                ]),
+                confirm: handleDeleteBatch,
+              },
+            },
+            {
+              label: '鏉$爜璁剧疆',
+              type: 'primary',
+              auth: ['mes:wm-barcode-config:query'],
+              onClick: handleConfig,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-barcode:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #barcode="{ row }">
+        <div v-if="row.content" class="flex justify-center">
+          <Barcode
+            :content="row.content"
+            :format="row.format"
+            :height="60"
+            :width="140"
+          />
+        </div>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.detail'),
+              type: 'link',
+              auth: ['mes:wm-barcode:query'],
+              onClick: handleView.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-barcode:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-barcode:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [
+                  row.bizName || row.bizCode || '',
+                ]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/barcode/modules/form.vue b/src/views/wls/barcode/modules/form.vue
new file mode 100644
index 0000000..6a8f10f
--- /dev/null
+++ b/src/views/wls/barcode/modules/form.vue
@@ -0,0 +1,111 @@
+<script lang="ts" setup>
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum, CommonStatusEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createBarcode, getBarcode, updateBarcode } from '#/api/mes/wm/barcode';
+import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const formData = ref<MesWmBarcodeApi.Barcode>();
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鏉$爜'])
+    : $t('ui.actionTitle.create', ['鏉$爜']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鍔犺浇绾ц仈閫夋嫨鍣ㄧ殑鏁版嵁锛堢紪杈戞椂鍥炲~涓婄骇浠撳簱/搴撳尯锛� */
+async function loadCascadeData(data: MesWmBarcodeApi.Barcode) {
+  if (!data.bizType || !data.bizId) {
+    return;
+  }
+  if (data.bizType === BarcodeBizTypeEnum.LOCATION) {
+    const location = await getWarehouseLocation(data.bizId);
+    if (location?.warehouseId) {
+      await formApi.setFieldValue('locationWarehouseId', location.warehouseId);
+    }
+  } else if (data.bizType === BarcodeBizTypeEnum.AREA) {
+    const area = await getWarehouseArea(data.bizId);
+    await formApi.setValues({
+      areaWarehouseId: area?.warehouseId,
+      areaLocationId: area?.locationId,
+    });
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmBarcodeApi.Barcode;
+    try {
+      await (formData.value?.id
+        ? updateBarcode({ ...data, id: formData.value.id })
+        : createBarcode(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useFormSchema(formApi) });
+    await formApi.setValues({ status: CommonStatusEnum.ENABLE });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<MesWmBarcodeApi.Barcode>();
+    if (!data || !data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getBarcode(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+      await loadCascadeData(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-1/2">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/batch/components/detail.vue b/src/views/wls/batch/components/detail.vue
new file mode 100644
index 0000000..6308076
--- /dev/null
+++ b/src/views/wls/batch/components/detail.vue
@@ -0,0 +1,52 @@
+<script lang="ts" setup>
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { getBatch } from '#/api/mes/wm/batch';
+import { useDescription } from '#/components/description';
+
+import { useDetailSchema } from '../data';
+
+const detailData = ref<MesWmBatchApi.Batch>(); // 褰撳墠鎵规璇︽儏
+
+const [Descriptions] = useDescription({
+  bordered: true,
+  column: 3,
+  schema: useDetailSchema(),
+  useCard: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  showCancelButton: false,
+  showConfirmButton: false,
+  async onOpenChange(isOpen) {
+    if (!isOpen) {
+      detailData.value = undefined;
+      return;
+    }
+    const data = modalApi.getData<{ id?: number }>();
+    if (!data?.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      detailData.value = await getBatch(data.id);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+
+defineExpose({
+  open: (id: number) => modalApi.setData({ id }).open(),
+});
+</script>
+
+<template>
+  <Modal title="鎵规璇︽儏" class="w-3/5">
+    <Descriptions class="mx-4" :data="detailData" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/batch/components/index.ts b/src/views/wls/batch/components/index.ts
similarity index 100%
copy from src/views/mes/wm/batch/components/index.ts
copy to src/views/wls/batch/components/index.ts
diff --git a/src/views/mes/wm/batch/components/select-dialog.vue b/src/views/wls/batch/components/select-dialog.vue
similarity index 100%
copy from src/views/mes/wm/batch/components/select-dialog.vue
copy to src/views/wls/batch/components/select-dialog.vue
diff --git a/src/views/wls/batch/components/select.vue b/src/views/wls/batch/components/select.vue
new file mode 100644
index 0000000..1243ff9
--- /dev/null
+++ b/src/views/wls/batch/components/select.vue
@@ -0,0 +1,149 @@
+<script lang="ts" setup>
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getBatch } from '#/api/mes/wm/batch';
+
+import WmBatchSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmBatchSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    clientId?: number; // 榛樿杩囨护鐨勫鎴� ID
+    disabled?: boolean;
+    itemId?: number; // 榛樿杩囨护鐨勭墿鏂� ID
+    modelValue?: number;
+    placeholder?: string;
+    salesOrderCode?: string; // 榛樿杩囨护鐨勯攢鍞鍗曠紪鍙�
+    vendorId?: number; // 榛樿杩囨护鐨勪緵搴斿晢 ID
+  }>(),
+  {
+    allowClear: true,
+    clientId: undefined,
+    disabled: false,
+    itemId: undefined,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨鎵规',
+    salesOrderCode: undefined,
+    vendorId: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmBatchApi.Batch | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmBatchSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmBatchApi.Batch>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ鎵规淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getBatch(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夋壒娆� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑鎵规閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, {
+    clientId: props.clientId,
+    itemId: props.itemId,
+    multiple: false,
+    salesOrderCode: props.salesOrderCode,
+    vendorId: props.vendorId,
+  });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmBatchApi.Batch[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>鎵规缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+          <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+          <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+          <div>鐢熶骇鎵瑰彿锛歿{ selectedItem.lotNumber || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmBatchSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/batch/data.ts b/src/views/wls/batch/data.ts
new file mode 100644
index 0000000..62111dd
--- /dev/null
+++ b/src/views/wls/batch/data.ts
@@ -0,0 +1,372 @@
+锘縤mport type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+import { formatDate } from '../../../packages/utils/src';
+
+import { DictTag } from '#/components/dict-tag';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProTaskSelect } from '#/views/mes/pro/task/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { TmToolSelect } from '#/views/mes/tm/tool/components';
+
+/** 鎵规閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useBatchSelectGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鎵规缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ壒娆$紪鍙�',
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '浜у搧鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+      },
+    },
+    {
+      fieldName: 'workstationId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+      },
+    },
+    {
+      fieldName: 'taskId',
+      label: '鐢熶骇浠诲姟',
+      component: markRaw(ProTaskSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇浠诲姟',
+      },
+    },
+    {
+      fieldName: 'toolId',
+      label: '宸ュ叿',
+      component: markRaw(TmToolSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨宸ュ叿',
+      },
+    },
+    {
+      fieldName: 'moldId',
+      label: '妯″叿缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユā鍏风紪鍙�',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'lotNumber',
+      label: '鐢熶骇鎵瑰彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�',
+      },
+    },
+    {
+      fieldName: 'qualityStatus',
+      label: '璐ㄩ噺鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_QUALITY_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨璐ㄩ噺鐘舵��',
+      },
+    },
+    {
+      fieldName: 'produceDate',
+      label: '鐢熶骇鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'expireDate',
+      label: '鏈夋晥鏈�',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鎵规閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useBatchSelectGridColumns(
+  multiple = false,
+): VxeTableGridOptions<MesWmBatchApi.Batch>['columns'] {
+  return [
+    {
+      type: multiple ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'code',
+      title: '鎵规缂栧彿',
+      width: 150,
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      width: 150,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'itemSpecification',
+      title: '瑙勬牸鍨嬪彿',
+      width: 120,
+    },
+    {
+      field: 'unitName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'vendorCode',
+      title: '渚涘簲鍟嗙紪鐮�',
+      width: 120,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      width: 120,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      width: 110,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      width: 110,
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      width: 140,
+    },
+    {
+      field: 'purchaseOrderCode',
+      title: '閲囪喘璁㈠崟缂栧彿',
+      width: 140,
+    },
+    {
+      field: 'workOrderCode',
+      title: '璁㈠崟缂栫爜',
+      width: 140,
+    },
+    {
+      field: 'workstationCode',
+      title: '宸ヤ綔绔欑紪鐮�',
+      width: 120,
+    },
+    {
+      field: 'taskCode',
+      title: '鐢熶骇浠诲姟缂栧彿',
+      width: 140,
+    },
+    {
+      field: 'toolCode',
+      title: '宸ュ叿缂栧彿',
+      width: 120,
+    },
+    {
+      field: 'lotNumber',
+      title: '鐢熶骇鎵瑰彿',
+      width: 120,
+    },
+    {
+      field: 'qualityStatus',
+      title: '璐ㄩ噺鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+      },
+    },
+    {
+      field: 'produceDate',
+      title: '鐢熶骇鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'expireDate',
+      title: '鏈夋晥鏈�',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'receiptDate',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+  ];
+}
+
+/** 鎵规璇︽儏鐨勬弿杩板瓧娈� */
+export function useDetailSchema(): DescriptionItemSchema[] {
+  return [
+    {
+      field: 'code',
+      label: '鎵规缂栧彿',
+    },
+    {
+      field: 'itemCode',
+      label: '鐗╂枡缂栫爜',
+    },
+    {
+      field: 'itemName',
+      label: '鐗╂枡鍚嶇О',
+    },
+    {
+      field: 'itemSpecification',
+      label: '瑙勬牸鍨嬪彿',
+    },
+    {
+      field: 'unitName',
+      label: '鍗曚綅',
+    },
+    {
+      field: 'lotNumber',
+      label: '鐢熶骇鎵瑰彿',
+    },
+    {
+      field: 'produceDate',
+      label: '鐢熶骇鏃ユ湡',
+      render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+    },
+    {
+      field: 'expireDate',
+      label: '鏈夋晥鏈�',
+      render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+    },
+    {
+      field: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+    },
+    {
+      field: 'vendorName',
+      label: '渚涘簲鍟�',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'clientName',
+      label: '瀹㈡埛',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'workstationCode',
+      label: '宸ヤ綔绔�',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟缂栧彿',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'workOrderCode',
+      label: '鐢熶骇璁㈠崟',
+      render: (value) => value || '-',
+    },
+    {
+      field: 'qualityStatus',
+      label: '璐ㄩ噺鐘舵��',
+      render: (value) =>
+        value === null
+          ? '-'
+          : h(DictTag, { type: DICT_TYPE.MES_WM_QUALITY_STATUS, value }),
+    },
+    {
+      field: 'remark',
+      label: '澶囨敞',
+      span: 3,
+      render: (value) => value || '-',
+    },
+  ];
+}
diff --git a/src/views/wls/itemreceipt/data.ts b/src/views/wls/itemreceipt/data.ts
new file mode 100644
index 0000000..da9708b
--- /dev/null
+++ b/src/views/wls/itemreceipt/data.ts
@@ -0,0 +1,527 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesWmArrivalNoticeStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import {
+  WmArrivalNoticeLineSelect,
+  WmArrivalNoticeSelect,
+} from '#/views/wls/arrivalnotice/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_ITEM_RECEIPT_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'noticeId',
+      label: '鍒拌揣閫氱煡鍗�',
+      component: markRaw(WmArrivalNoticeSelect),
+      componentProps: {
+        // 閫夋嫨鍒拌揣閫氱煡鍗曞悗锛岃嚜鍔ㄥ洖濉緵搴斿晢鍜岄噰璐鍗曞彿
+        onChange: async (notice?: MesWmArrivalNoticeApi.ArrivalNotice) => {
+          await formApi?.setValues({
+            purchaseOrderCode: notice?.purchaseOrderCode,
+            vendorId: notice?.vendorId,
+          });
+        },
+        status: MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT,
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmItemReceiptApi.ItemReceipt>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍏ュ簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍏ュ簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'purchaseOrderCode',
+      title: '閲囪喘璁㈠崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 120,
+    },
+    {
+      field: 'receiptDate',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_ITEM_RECEIPT_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmItemReceiptLineApi.ItemReceiptLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'receivedQuantity',
+      title: '鍏ュ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 200,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+  hasNotice: boolean,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'noticeId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'arrivalNoticeLineId',
+      label: '鍒拌揣閫氱煡鍗曡',
+      component: markRaw(WmArrivalNoticeLineSelect),
+      componentProps: {
+        // 閫夋嫨鍒拌揣閫氱煡鍗曡鍚庯紝鑷姩鍥炲~鐗╂枡鍜屽叆搴撴暟閲�
+        onChange: async (
+          line?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
+        ) => {
+          await formApi?.setValues({
+            itemId: line?.itemId,
+            receivedQuantity: line?.arrivalQuantity,
+          });
+        },
+      },
+      dependencies: {
+        triggerFields: ['noticeId'],
+        if: () => hasNotice,
+        componentProps: (values) => ({
+          noticeId: values.noticeId,
+        }),
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['arrivalNoticeLineId'],
+        componentProps: (values) => ({
+          disabled: !!values.arrivalNoticeLineId,
+          placeholder: '璇烽�夋嫨鐗╂枡',
+        }),
+      },
+    },
+    {
+      fieldName: 'receivedQuantity',
+      label: '鍏ュ簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'productionDate',
+      label: '鐢熶骇鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'expireDate',
+      label: '鏈夋晥鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'lotNumber',
+      label: '鐢熶骇鎵瑰彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�',
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '鐢卞~鍐欎俊鎭嚜鍔ㄧ敓鎴�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmItemReceiptDetailApi.ItemReceiptDetail>['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍏ュ簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+        onChange: async () => {
+          await formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          });
+        },
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+          onChange: async () => {
+            await formApi?.setFieldValue('areaId', undefined);
+          },
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+  ];
+}
diff --git a/src/views/wls/itemreceipt/index.vue b/src/views/wls/itemreceipt/index.vue
new file mode 100644
index 0000000..9e3f25c
--- /dev/null
+++ b/src/views/wls/itemreceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmItemReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelItemReceipt,
+  deleteItemReceipt,
+  exportItemReceipt,
+  getItemReceiptPage,
+} from '#/api/mes/wm/itemreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓閲囪喘鍏ュ簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閲囪喘鍏ュ簱鍗� */
+function handleDetail(row: MesWmItemReceiptApi.ItemReceipt) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閲囪喘鍏ュ簱鍗� */
+function handleEdit(row: MesWmItemReceiptApi.ItemReceipt) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmItemReceiptApi.ItemReceipt) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmItemReceiptApi.ItemReceipt) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗� */
+async function handleDelete(row: MesWmItemReceiptApi.ItemReceipt) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteItemReceipt(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷閲囪喘鍏ュ簱鍗� */
+async function handleCancel(row: MesWmItemReceiptApi.ItemReceipt) {
+  await cancelItemReceipt(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportItemReceipt(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '閲囪喘鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getItemReceiptPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmItemReceiptApi.ItemReceipt>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="閲囪喘鍏ュ簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['閲囪喘鍏ュ簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-item-receipt:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-item-receipt:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-item-receipt:update'],
+              ifShow: row.status === MesWmItemReceiptStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-item-receipt:delete'],
+              ifShow: row.status === MesWmItemReceiptStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-item-receipt:update'],
+              ifShow: row.status === MesWmItemReceiptStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鎵ц鍏ュ簱',
+              type: 'link',
+              auth: ['mes:wm-item-receipt:finish'],
+              ifShow: row.status === MesWmItemReceiptStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-item-receipt:update'],
+              ifShow:
+                row.status === MesWmItemReceiptStatusEnum.APPROVING ||
+                row.status === MesWmItemReceiptStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ラ噰璐叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/detail-form.vue b/src/views/wls/itemreceipt/modules/detail-form.vue
new file mode 100644
index 0000000..e1c6dc4
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createItemReceiptDetail,
+  getItemReceiptDetail,
+  updateItemReceiptDetail,
+} from '#/api/mes/wm/itemreceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmItemReceiptDetailApi.ItemReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+    : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmItemReceiptDetailApi.ItemReceiptDetail;
+    data.receiptId = receiptId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateItemReceiptDetail({ ...data, id: formData.value.id })
+        : createItemReceiptDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getItemReceiptDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-list.vue b/src/views/wls/itemreceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/itemreceipt/modules/detail-list.vue
copy to src/views/wls/itemreceipt/modules/detail-list.vue
diff --git a/src/views/wls/itemreceipt/modules/form.vue b/src/views/wls/itemreceipt/modules/form.vue
new file mode 100644
index 0000000..a0fae09
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/form.vue
@@ -0,0 +1,242 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmItemReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createItemReceipt,
+  finishItemReceipt,
+  getItemReceipt,
+  stockItemReceipt,
+  submitItemReceipt,
+  updateItemReceipt,
+} from '#/api/mes/wm/itemreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmItemReceiptApi.ItemReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmItemReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['閲囪喘鍏ュ簱鍗�']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц涓婃灦';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍏ュ簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['閲囪喘鍏ュ簱鍗�'])
+    : $t('ui.actionTitle.create', ['閲囪喘鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmItemReceiptApi.ItemReceipt;
+      await updateItemReceipt({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitItemReceipt(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await stockItemReceipt(formData.value.id);
+    message.success('涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishItemReceipt(formData.value.id);
+    message.success('鍏ュ簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmItemReceiptApi.ItemReceipt;
+    try {
+      if (formData.value?.id) {
+        await updateItemReceipt({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createItemReceipt(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmItemReceiptStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getItemReceipt(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+
+/** 涓婃灦鍓嶇‘璁� */
+async function confirmStock() {
+  try {
+    await confirm('纭鎵ц涓婃灦锛�');
+  } catch {
+    return;
+  }
+  await handleStock();
+}
+
+/** 鍏ュ簱鍓嶇‘璁� */
+async function confirmFinish() {
+  try {
+    await confirm('纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��');
+  } catch {
+    return;
+  }
+  await handleFinish();
+}
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList
+          :form-type="formType"
+          :notice-id="formData.noticeId"
+          :receipt-id="formData.id"
+        />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ噰璐叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="confirmStock">
+          鎵ц涓婃灦
+        </Button>
+        <Button v-if="isFinish" type="primary" @click="confirmFinish">
+          鎵ц鍏ュ簱
+        </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/line-form.vue b/src/views/wls/itemreceipt/modules/line-form.vue
new file mode 100644
index 0000000..a34a436
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/line-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createItemReceiptLine,
+  getItemReceiptLine,
+  updateItemReceiptLine,
+} from '#/api/mes/wm/itemreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmItemReceiptLineApi.ItemReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const noticeId = ref<number>(); // 鎵�灞炲埌璐ч�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡鍏ュ簱鍗曡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmItemReceiptLineApi.ItemReceiptLine;
+    data.receiptId = receiptId.value;
+    try {
+      await (formData.value?.id
+        ? updateItemReceiptLine({ ...data, id: formData.value.id })
+        : createItemReceiptLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      noticeId?: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    noticeId.value = data.noticeId;
+    formApi.setState({ schema: useLineFormSchema(!!data.noticeId, formApi) });
+    if (data.noticeId) {
+      await formApi.setFieldValue('noticeId', data.noticeId);
+    }
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getItemReceiptLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/line-list.vue b/src/views/wls/itemreceipt/modules/line-list.vue
new file mode 100644
index 0000000..a30f441
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/line-list.vue
@@ -0,0 +1,241 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getItemReceiptDetailListByLineId } from '#/api/mes/wm/itemreceipt/detail';
+import {
+  deleteItemReceiptLine,
+  getItemReceiptLinePage,
+} from '#/api/mes/wm/itemreceipt/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  noticeId?: number;
+  receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmItemReceiptDetailApi.ItemReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi
+    .setData({ noticeId: props.noticeId, receiptId: props.receiptId })
+    .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+  lineFormModalApi
+    .setData({
+      id: row.id,
+      noticeId: props.noticeId,
+      receiptId: props.receiptId,
+    })
+    .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteItemReceiptLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getItemReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+  row: MesWmItemReceiptLineApi.ItemReceiptLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.receiptId) {
+            return { list: [], total: 0 };
+          }
+          return await getItemReceiptLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            receiptId: props.receiptId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmItemReceiptLineApi.ItemReceiptLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmItemReceiptLineApi.ItemReceiptLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '涓婃灦',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handleStock.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="ITEM_BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/mes/wm/materialstock/components/index.ts b/src/views/wls/materialstock/components/index.ts
similarity index 100%
copy from src/views/mes/wm/materialstock/components/index.ts
copy to src/views/wls/materialstock/components/index.ts
diff --git a/src/views/mes/wm/materialstock/components/select-dialog.vue b/src/views/wls/materialstock/components/select-dialog.vue
similarity index 100%
copy from src/views/mes/wm/materialstock/components/select-dialog.vue
copy to src/views/wls/materialstock/components/select-dialog.vue
diff --git a/src/views/wls/materialstock/components/select.vue b/src/views/wls/materialstock/components/select.vue
new file mode 100644
index 0000000..e605056
--- /dev/null
+++ b/src/views/wls/materialstock/components/select.vue
@@ -0,0 +1,158 @@
+<script lang="ts" setup>
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getMaterialStock } from '#/api/mes/wm/materialstock';
+
+import WmMaterialStockSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmMaterialStockSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    batchId?: number;
+    disabled?: boolean;
+    itemId?: number;
+    modelValue?: number;
+    placeholder?: string;
+    virtualFilter?: 'all' | 'exclude' | 'only';
+    warehouseId?: number;
+  }>(),
+  {
+    allowClear: true,
+    batchId: undefined,
+    disabled: false,
+    itemId: undefined,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨搴撳瓨',
+    virtualFilter: 'exclude',
+    warehouseId: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmMaterialStockApi.MaterialStock | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmMaterialStockApi.MaterialStock>();
+
+const displayLabel = computed(() => {
+  const item = selectedItem.value;
+  if (!item) {
+    return '';
+  }
+  return `${item.warehouseName || '-'} / ${item.batchCode || '-'} / 鏁伴噺:${item.quantity}`;
+});
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁 ID 鍗曟潯鏌ヨ搴撳瓨淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getMaterialStock(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夊簱瀛� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑搴撳瓨閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmMaterialStockApi.MaterialStock[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>鐗╂枡锛歿{ selectedItem.itemName || '-' }}</div>
+          <div>鎵规锛歿{ selectedItem.batchCode || '-' }}</div>
+          <div>鏁伴噺锛歿{ selectedItem.quantity ?? '-' }}</div>
+          <div>浠撳簱锛歿{ selectedItem.warehouseName || '-' }}</div>
+          <div>搴撳尯锛歿{ selectedItem.locationName || '-' }}</div>
+          <div>搴撲綅锛歿{ selectedItem.areaName || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmMaterialStockSelectDialog
+    ref="dialogRef"
+    :batch-id="batchId"
+    :item-id="itemId"
+    :virtual-filter="virtualFilter"
+    :warehouse-id="warehouseId"
+    @selected="handleSelected"
+  />
+</template>
diff --git a/src/views/wls/materialstock/data.ts b/src/views/wls/materialstock/data.ts
new file mode 100644
index 0000000..85f864a
--- /dev/null
+++ b/src/views/wls/materialstock/data.ts
@@ -0,0 +1,306 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { markRaw } from 'vue';
+
+import { useAccess } from '../../../packages/effects/access/src';
+import { DICT_TYPE } from '../../../packages/constants/src';
+
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '../warehouse/components';
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          warehouseId: values.warehouseId,
+          placeholder: '璇烽�夋嫨搴撳尯',
+        }),
+        // 浠撳簱鍒囨崲鏃舵竻绌哄簱鍖猴紝閬垮厤鏃у簱鍖烘潯浠舵畫鐣�
+        trigger: (values, formApi) => {
+          if (values.locationId !== undefined) {
+            void formApi.setFieldValue('locationId', undefined);
+          }
+        },
+      },
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          { label: '鏄�', value: true },
+          { label: '鍚�', value: false },
+        ],
+        placeholder: '璇烽�夋嫨',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+  onFrozenChange: (
+    newFrozen: boolean,
+    row: MesWmMaterialStockApi.MaterialStock,
+  ) => Promise<boolean | undefined>,
+): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
+  const { hasAccessByCodes } = useAccess();
+  return [
+    {
+      field: 'itemCode',
+      title: '浜у搧鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'quantity',
+      title: '鍦ㄥ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 140,
+      slots: { default: 'batchCode' },
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 100,
+      slots: { default: 'areaName' },
+    },
+    {
+      field: 'receiptTime',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'frozen',
+      title: '鍐荤粨',
+      width: 90,
+      cellRender: {
+        name: 'CellSwitch',
+        attrs: { beforeChange: onFrozenChange },
+        props: {
+          checkedValue: true,
+          // 鍐荤粨寮�鍏冲彈鏇存柊鏉冮檺鎺у埗锛屾棤鏉冮檺鏃剁鐢紝淇濇寔涓庢簮椤圭洰 v-hasPermi 涓�鑷�
+          disabled: !hasAccessByCodes(['mes:wm-material-stock:update']),
+          unCheckedValue: false,
+        },
+      },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          warehouseId: values.warehouseId,
+          placeholder: '璇烽�夋嫨搴撳尯',
+        }),
+        // 浠撳簱鍒囨崲鏃舵竻绌哄簱鍖�
+        trigger: (values, formApi) => {
+          if (values.locationId !== undefined) {
+            void formApi.setFieldValue('locationId', undefined);
+          }
+        },
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      dependencies: {
+        triggerFields: ['warehouseId', 'locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+        // 浠撳簱鎴栧簱鍖哄垏鎹㈡椂娓呯┖搴撲綅
+        trigger: (values, formApi) => {
+          if (values.areaId !== undefined) {
+            void formApi.setFieldValue('areaId', undefined);
+          }
+        },
+      },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+  multiple = false,
+): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
+  return [
+    {
+      type: multiple ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'itemCode',
+      title: '浜у搧鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'batchCode',
+      title: '鍏ュ簱鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鍦ㄥ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'receiptTime',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'frozen',
+      title: '鍐荤粨',
+      width: 80,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/materialstock/index.vue b/src/views/wls/materialstock/index.vue
new file mode 100644
index 0000000..40cb43c
--- /dev/null
+++ b/src/views/wls/materialstock/index.vue
@@ -0,0 +1,175 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { ref } from 'vue';
+
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, Card, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  exportMaterialStock,
+  getMaterialStockPage,
+  updateMaterialStockFrozen,
+} from '#/api/mes/wm/materialstock';
+import { $t } from '#/locales';
+import { MdItemTypeTree } from '#/views/mes/md/item/type/components';
+import { WmBatchDetail } from '#/views/wls/batch/components';
+import AreaForm from '#/views/wls/warehouse/area/modules/form.vue';
+
+import { useGridColumns, useGridFormSchema } from './data';
+
+const [AreaModal, areaModalApi] = useVbenModal({
+  connectedComponent: AreaForm,
+  destroyOnClose: true,
+});
+
+const batchDetailRef = ref<InstanceType<typeof WmBatchDetail>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportMaterialStock({
+    ...(await gridApi.formApi.getValues()),
+    itemTypeId: searchItemTypeId.value,
+  });
+  downloadFileFromBlobPart({ fileName: '搴撳瓨鍙拌处.xls', source: data });
+}
+
+/** 閫夋嫨鐗╂枡鍒嗙被 */
+const searchItemTypeId = ref<number | undefined>(undefined);
+function handleTypeNodeClick(row: undefined | { id?: number }) {
+  searchItemTypeId.value = row?.id;
+  handleRefresh();
+}
+
+/** 鎵撳紑搴撲綅璇︽儏寮圭獥 */
+function handleOpenAreaDetail(row: MesWmMaterialStockApi.MaterialStock) {
+  if (!row.areaId) {
+    return;
+  }
+  areaModalApi.setData({ formType: 'detail', id: row.areaId }).open();
+}
+
+/** 鎵撳紑鎵规璇︽儏寮圭獥 */
+function handleOpenBatchDetail(row: MesWmMaterialStockApi.MaterialStock) {
+  if (!row.batchId) {
+    return;
+  }
+  batchDetailRef.value?.open(row.batchId);
+}
+
+/** 澶勭悊鍐荤粨鐘舵�佸垏鎹� */
+async function handleFrozenChange(
+  newFrozen: boolean,
+  row: MesWmMaterialStockApi.MaterialStock,
+): Promise<boolean | undefined> {
+  const text = newFrozen ? '鍐荤粨' : '瑙e喕';
+  try {
+    await confirm(`纭瑕�"${text}"璇ュ簱瀛樿褰曞悧锛焋);
+  } catch {
+    return false;
+  }
+  // 鏇存柊鍐荤粨鐘舵��
+  await updateMaterialStockFrozen({
+    id: row.id!,
+    frozen: newFrozen,
+  });
+  // 鎻愮ず骞惰繑鍥炴垚鍔�
+  message.success(`${text}鎴愬姛`);
+  return true;
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(handleFrozenChange),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getMaterialStockPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+            itemTypeId: searchItemTypeId.value,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><AreaModal />
+    <WmBatchDetail ref="batchDetailRef" />
+
+    <div class="flex h-full w-full">
+      <!-- 宸︿晶鐗╂枡鍒嗙被鏍� -->
+      <Card class="mr-4 h-full w-1/6">
+        <MdItemTypeTree @node-click="handleTypeNodeClick" />
+      </Card>
+      <!-- 鍙充晶搴撳瓨鍙拌处鍒楄〃 -->
+      <div class="w-5/6">
+        <Grid table-title="搴撳瓨鍙拌处鍒楄〃">
+          <template #toolbar-tools>
+            <TableAction
+              :actions="[
+                {
+                  label: $t('ui.actionTitle.export'),
+                  type: 'primary',
+                  icon: ACTION_ICON.DOWNLOAD,
+                  auth: ['mes:wm-material-stock:export'],
+                  onClick: handleExport,
+                },
+              ]"
+            />
+          </template>
+          <template #batchCode="{ row }">
+            <Button
+              v-if="row.batchId"
+              :title="row.batchCode"
+              size="small"
+              type="link"
+              @click="handleOpenBatchDetail(row)"
+            >
+              {{ row.batchCode }}
+            </Button>
+            <span v-else>-</span>
+          </template>
+          <template #areaName="{ row }">
+            <Button
+              v-if="row.areaId"
+              :title="row.areaName"
+              size="small"
+              type="link"
+              @click="handleOpenAreaDetail(row)"
+            >
+              {{ row.areaName }}
+            </Button>
+            <span v-else>-</span>
+          </template>
+        </Grid>
+      </div>
+    </div>
+  </Page>
+</template>
diff --git a/src/views/wls/miscissue/data.ts b/src/views/wls/miscissue/data.ts
new file mode 100644
index 0000000..03c9a89
--- /dev/null
+++ b/src/views/wls/miscissue/data.ts
@@ -0,0 +1,452 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽嚭搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍑哄簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_MISC_ISSUE_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍑哄簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'issueDate',
+      label: '鍑哄簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'sourceDocType',
+      label: '鏉ユ簮鍗曟嵁绫诲瀷',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+      },
+    },
+    {
+      fieldName: 'sourceDocCode',
+      label: '鏉ユ簮鍗曟嵁缂栧彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍑哄簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍑哄簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '涓氬姟绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'sourceDocType',
+      label: '鏉ユ簮鍗曟嵁绫诲瀷',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+      },
+    },
+    {
+      fieldName: 'sourceDocCode',
+      label: '鏉ユ簮鍗曟嵁缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+      },
+    },
+    {
+      fieldName: 'issueDate',
+      label: '鍑哄簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmMiscIssueApi.MiscIssue>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍑哄簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍑哄簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'type',
+      title: '涓氬姟绫诲瀷',
+      minWidth: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_MISC_ISSUE_TYPE },
+      },
+    },
+    {
+      field: 'sourceDocType',
+      title: '鏉ユ簮鍗曟嵁绫诲瀷',
+      minWidth: 120,
+    },
+    {
+      field: 'sourceDocCode',
+      title: '鏉ユ簮鍗曟嵁缂栧彿',
+      minWidth: 150,
+    },
+    {
+      field: 'issueDate',
+      title: '鍑哄簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_MISC_ISSUE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍑哄簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable = true,
+): VxeTableGridOptions<MesWmMiscIssueLineApi.MiscIssueLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍑哄簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 120,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 120,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 120,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍑哄簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨鐗╄祫',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨鐗╄祫鍚庯紝鑷姩鍥炲~鐗╂枡銆佹壒娆″彿銆佷粨搴撲綅缃拰鏈�澶у彲鍑哄簱鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            itemId: stock?.itemId,
+            locationId: stock?.locationId,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍑哄簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0.01,
+        placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0.01,
+          placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '鎵规鍙�',
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/miscissue/index.vue b/src/views/wls/miscissue/index.vue
new file mode 100644
index 0000000..b7f6ec0
--- /dev/null
+++ b/src/views/wls/miscissue/index.vue
@@ -0,0 +1,187 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmMiscIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelMiscIssue,
+  deleteMiscIssue,
+  exportMiscIssue,
+  getMiscIssuePage,
+} from '#/api/mes/wm/miscissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鏉傞」鍑哄簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鏉傞」鍑哄簱鍗� */
+function handleDetail(row: MesWmMiscIssueApi.MiscIssue) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鏉傞」鍑哄簱鍗� */
+function handleEdit(row: MesWmMiscIssueApi.MiscIssue) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmMiscIssueApi.MiscIssue) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗� */
+async function handleDelete(row: MesWmMiscIssueApi.MiscIssue) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteMiscIssue(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷鏉傞」鍑哄簱鍗� */
+async function handleCancel(row: MesWmMiscIssueApi.MiscIssue) {
+  await cancelMiscIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportMiscIssue(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鏉傞」鍑哄簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getMiscIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmMiscIssueApi.MiscIssue>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鏉傞」鍑哄簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鏉傞」鍑哄簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-misc-issue:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-misc-issue:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-misc-issue:update'],
+              ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-misc-issue:delete'],
+              ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鍑哄簱',
+              type: 'link',
+              auth: ['mes:wm-misc-issue:finish'],
+              ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-misc-issue:update'],
+              ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ユ潅椤瑰嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/miscissue/modules/form.vue b/src/views/wls/miscissue/modules/form.vue
new file mode 100644
index 0000000..ac9ab5f
--- /dev/null
+++ b/src/views/wls/miscissue/modules/form.vue
@@ -0,0 +1,199 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmMiscIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createMiscIssue,
+  finishMiscIssue,
+  getMiscIssue,
+  submitMiscIssue,
+  updateMiscIssue,
+} from '#/api/mes/wm/miscissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmMiscIssueApi.MiscIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmMiscIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鏉傞」鍑哄簱鍗�']);
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍑哄簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鏉傞」鍑哄簱鍗�'])
+    : $t('ui.actionTitle.create', ['鏉傞」鍑哄簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍑哄簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data = (await formApi.getValues()) as MesWmMiscIssueApi.MiscIssue;
+      await updateMiscIssue({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitMiscIssue(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍑哄簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishMiscIssue(formData.value.id);
+    message.success('鍑哄簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmMiscIssueApi.MiscIssue;
+    try {
+      if (formData.value?.id) {
+        await updateMiscIssue({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createMiscIssue(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmMiscIssueStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(
+      formType.value === 'detail' || formType.value === 'finish',
+    );
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getMiscIssue(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :issue-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ユ潅椤瑰嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц鍑哄簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц鍑哄簱</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/miscissue/modules/line-form.vue b/src/views/wls/miscissue/modules/line-form.vue
new file mode 100644
index 0000000..b20795a
--- /dev/null
+++ b/src/views/wls/miscissue/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createMiscIssueLine,
+  getMiscIssueLine,
+  updateMiscIssueLine,
+} from '#/api/mes/wm/miscissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmMiscIssueLineApi.MiscIssueLine>();
+const issueId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡鍑哄簱鍗曡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡鍑哄簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmMiscIssueLineApi.MiscIssueLine;
+    data.issueId = issueId.value;
+    try {
+      await (formData.value?.id
+        ? updateMiscIssueLine({ ...data, id: formData.value.id })
+        : createMiscIssueLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useLineFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      issueId: number;
+    }>();
+    issueId.value = data.issueId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getMiscIssueLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/miscissue/modules/line-list.vue b/src/views/wls/miscissue/modules/line-list.vue
new file mode 100644
index 0000000..4d47403
--- /dev/null
+++ b/src/views/wls/miscissue/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteMiscIssueLine,
+  getMiscIssueLinePage,
+} from '#/api/mes/wm/miscissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  issueId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmMiscIssueLineApi.MiscIssueLine) {
+  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmMiscIssueLineApi.MiscIssueLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteMiscIssueLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value),
+    height: 360,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.issueId) {
+            return { list: [], total: 0 };
+          }
+          return await getMiscIssueLinePage({
+            issueId: props.issueId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmMiscIssueLineApi.MiscIssueLine>,
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/miscreceipt/data.ts b/src/views/wls/miscreceipt/data.ts
new file mode 100644
index 0000000..fa4491a
--- /dev/null
+++ b/src/views/wls/miscreceipt/data.ts
@@ -0,0 +1,422 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_MISC_RECEIPT_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '鏉傞」绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鏉傞」绫诲瀷',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'sourceDocType',
+      label: '鏉ユ簮鍗曟嵁绫诲瀷',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+      },
+    },
+    {
+      fieldName: 'sourceDocCode',
+      label: '鏉ユ簮鍗曟嵁缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鐮�',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '鏉傞」绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鏉傞」绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'sourceDocType',
+      label: '鏉ユ簮鍗曟嵁绫诲瀷',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+      },
+    },
+    {
+      fieldName: 'sourceDocCode',
+      label: '鏉ユ簮鍗曟嵁缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmMiscReceiptApi.MiscReceipt>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍏ュ簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍏ュ簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'type',
+      title: '鏉傞」绫诲瀷',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE },
+      },
+    },
+    {
+      field: 'sourceDocType',
+      title: '鏉ユ簮鍗曟嵁绫诲瀷',
+      minWidth: 120,
+    },
+    {
+      field: 'sourceDocCode',
+      title: '鏉ユ簮鍗曟嵁缂栧彿',
+      minWidth: 150,
+    },
+    {
+      field: 'receiptDate',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_MISC_RECEIPT_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable = true,
+): VxeTableGridOptions<MesWmMiscReceiptLineApi.MiscReceiptLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍏ュ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 100,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍏ュ簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0.01,
+        placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+        onChange: async () => {
+          await formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          });
+        },
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+          onChange: async () => {
+            await formApi?.setFieldValue('areaId', undefined);
+          },
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/miscreceipt/index.vue b/src/views/wls/miscreceipt/index.vue
new file mode 100644
index 0000000..5039d2e
--- /dev/null
+++ b/src/views/wls/miscreceipt/index.vue
@@ -0,0 +1,187 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmMiscReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelMiscReceipt,
+  deleteMiscReceipt,
+  exportMiscReceipt,
+  getMiscReceiptPage,
+} from '#/api/mes/wm/miscreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鏉傞」鍏ュ簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鏉傞」鍏ュ簱鍗� */
+function handleDetail(row: MesWmMiscReceiptApi.MiscReceipt) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鏉傞」鍏ュ簱鍗� */
+function handleEdit(row: MesWmMiscReceiptApi.MiscReceipt) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmMiscReceiptApi.MiscReceipt) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗� */
+async function handleDelete(row: MesWmMiscReceiptApi.MiscReceipt) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteMiscReceipt(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷鏉傞」鍏ュ簱鍗� */
+async function handleCancel(row: MesWmMiscReceiptApi.MiscReceipt) {
+  await cancelMiscReceipt(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportMiscReceipt(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鏉傞」鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getMiscReceiptPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmMiscReceiptApi.MiscReceipt>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鏉傞」鍏ュ簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鏉傞」鍏ュ簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-misc-receipt:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-misc-receipt:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-misc-receipt:update'],
+              ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-misc-receipt:delete'],
+              ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鍏ュ簱',
+              type: 'link',
+              auth: ['mes:wm-misc-receipt:finish'],
+              ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-misc-receipt:cancel'],
+              ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ユ潅椤瑰叆搴撳崟鍚楋紵',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/form.vue b/src/views/wls/miscreceipt/modules/form.vue
new file mode 100644
index 0000000..1f38b6c
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/form.vue
@@ -0,0 +1,200 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmMiscReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createMiscReceipt,
+  finishMiscReceipt,
+  getMiscReceipt,
+  submitMiscReceipt,
+  updateMiscReceipt,
+} from '#/api/mes/wm/miscreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmMiscReceiptApi.MiscReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmMiscReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鏉傞」鍏ュ簱鍗�']);
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍏ュ簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鏉傞」鍏ュ簱鍗�'])
+    : $t('ui.actionTitle.create', ['鏉傞」鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmMiscReceiptApi.MiscReceipt;
+      await updateMiscReceipt({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitMiscReceipt(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishMiscReceipt(formData.value.id);
+    message.success('鍏ュ簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmMiscReceiptApi.MiscReceipt;
+    try {
+      if (formData.value?.id) {
+        await updateMiscReceipt({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createMiscReceipt(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmMiscReceiptStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(
+      formType.value === 'detail' || formType.value === 'finish',
+    );
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getMiscReceipt(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :receipt-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ユ潅椤瑰叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц鍏ュ簱</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/line-form.vue b/src/views/wls/miscreceipt/modules/line-form.vue
new file mode 100644
index 0000000..b3e42c5
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createMiscReceiptLine,
+  getMiscReceiptLine,
+  updateMiscReceiptLine,
+} from '#/api/mes/wm/miscreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmMiscReceiptLineApi.MiscReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmMiscReceiptLineApi.MiscReceiptLine;
+    data.receiptId = receiptId.value;
+    try {
+      await (formData.value?.id
+        ? updateMiscReceiptLine({ ...data, id: formData.value.id })
+        : createMiscReceiptLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useLineFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getMiscReceiptLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/line-list.vue b/src/views/wls/miscreceipt/modules/line-list.vue
new file mode 100644
index 0000000..72f1e24
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteMiscReceiptLine,
+  getMiscReceiptLineListByReceiptId,
+} from '#/api/mes/wm/miscreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmMiscReceiptLineApi.MiscReceiptLine) {
+  lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmMiscReceiptLineApi.MiscReceiptLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteMiscReceiptLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value),
+    height: 360,
+    keepSource: true,
+    pagerConfig: {
+      enabled: false,
+    },
+    proxyConfig: {
+      ajax: {
+        query: async () => {
+          if (!props.receiptId) {
+            return { list: [], total: 0 };
+          }
+          const list = await getMiscReceiptLineListByReceiptId(props.receiptId);
+          return { list, total: list.length };
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmMiscReceiptLineApi.MiscReceiptLine>,
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/outsourceissue/data.ts b/src/views/wls/outsourceissue/data.ts
new file mode 100644
index 0000000..7cc6ae2
--- /dev/null
+++ b/src/views/wls/outsourceissue/data.ts
@@ -0,0 +1,487 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+  MesProWorkOrderTypeEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�侀鍑烘�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍙戞枡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ彂鏂欏崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_OUTSOURCE_ISSUE_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍙戞枡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ彂鏂欏崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'issueDate',
+      label: '鍙戞枡鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍙戞枡鏃ユ湡',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '澶栧崗璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        // 閫夋嫨澶栧崗璁㈠崟鍚庯紝鑷姩鍥炲~渚涘簲鍟�
+        onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
+          await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
+        },
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+        type: MesProWorkOrderTypeEnum.OUTSOURCE,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍙戞枡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ彂鏂欏崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍙戞枡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ彂鏂欏崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'issueDate',
+      label: '鍙戞枡鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmOutsourceIssueApi.OutsourceIssue>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍙戞枡鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍙戞枡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'workOrderCode',
+      title: '鐢熶骇璁㈠崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 120,
+    },
+    {
+      field: 'issueDate',
+      title: '鍙戞枡鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_OUTSOURCE_ISSUE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍙戞枡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceIssueLineApi.OutsourceIssueLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '棰嗘枡鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 150,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍙戞枡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍙戞枡鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ彂鏂欐暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍙戞枡鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍙戞枡鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            locationId: stock?.locationId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+        }),
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ユ暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍙戞枡浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/outsourceissue/index.vue b/src/views/wls/outsourceissue/index.vue
new file mode 100644
index 0000000..5784037
--- /dev/null
+++ b/src/views/wls/outsourceissue/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmOutsourceIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelOutsourceIssue,
+  deleteOutsourceIssue,
+  exportOutsourceIssue,
+  getOutsourceIssuePage,
+} from '#/api/mes/wm/outsourceissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓澶栧崗鍙戞枡鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅澶栧崗鍙戞枡鍗� */
+function handleDetail(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫澶栧崗鍙戞枡鍗� */
+function handleEdit(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц棰嗗嚭 */
+function handleFinish(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗� */
+async function handleDelete(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteOutsourceIssue(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷澶栧崗鍙戞枡鍗� */
+async function handleCancel(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+  await cancelOutsourceIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportOutsourceIssue(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '澶栧崗鍙戞枡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getOutsourceIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmOutsourceIssueApi.OutsourceIssue>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="澶栧崗鍙戞枡鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['澶栧崗鍙戞枡鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-outsource-issue:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-outsource-issue:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-outsource-issue:update'],
+              ifShow: row.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-outsource-issue:delete'],
+              ifShow: row.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鎷h揣',
+              type: 'link',
+              auth: ['mes:wm-outsource-issue:update'],
+              ifShow: row.status === MesWmOutsourceIssueStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鎵ц棰嗗嚭',
+              type: 'link',
+              auth: ['mes:wm-outsource-issue:finish'],
+              ifShow: row.status === MesWmOutsourceIssueStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-outsource-issue:update'],
+              ifShow:
+                row.status === MesWmOutsourceIssueStatusEnum.APPROVING ||
+                row.status === MesWmOutsourceIssueStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ュ鍗忓彂鏂欏崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/detail-form.vue b/src/views/wls/outsourceissue/modules/detail-form.vue
new file mode 100644
index 0000000..278c904
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOutsourceIssueDetail,
+  getOutsourceIssueDetail,
+  updateOutsourceIssueDetail,
+} from '#/api/mes/wm/outsourceissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞炲彂鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲彂鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鍙戞枡鏄庣粏'])
+    : $t('ui.actionTitle.create', ['鍙戞枡鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceIssueDetailApi.OutsourceIssueDetail;
+    data.issueId = issueId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateOutsourceIssueDetail({ ...data, id: formData.value.id })
+        : createOutsourceIssueDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      issueId: number;
+      itemId?: number;
+      lineId: number;
+    }>();
+    issueId.value = data.issueId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getOutsourceIssueDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-list.vue b/src/views/wls/outsourceissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/outsourceissue/modules/detail-list.vue
copy to src/views/wls/outsourceissue/modules/detail-list.vue
diff --git a/src/views/wls/outsourceissue/modules/form.vue b/src/views/wls/outsourceissue/modules/form.vue
new file mode 100644
index 0000000..477d21a
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/form.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmOutsourceIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  checkOutsourceIssueQuantity,
+  createOutsourceIssue,
+  finishOutsourceIssue,
+  getOutsourceIssue,
+  stockOutsourceIssue,
+  submitOutsourceIssue,
+  updateOutsourceIssue,
+} from '#/api/mes/wm/outsourceissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmOutsourceIssueApi.OutsourceIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄鍑烘ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['澶栧崗鍙戞枡鍗�']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц鎷h揣';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц棰嗗嚭';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['澶栧崗鍙戞枡鍗�'])
+    : $t('ui.actionTitle.create', ['澶栧崗鍙戞枡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍙戞枡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmOutsourceIssueApi.OutsourceIssue;
+      await updateOutsourceIssue({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitOutsourceIssue(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鎷h揣锛氭嫞璐ф暟閲忎笌鍙戞枡鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  const quantityMatch = await checkOutsourceIssueQuantity(formData.value.id);
+  if (!quantityMatch) {
+    try {
+      await confirm('鍙戞枡鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+    } catch {
+      return;
+    }
+  }
+  modalApi.lock();
+  try {
+    await stockOutsourceIssue(formData.value.id);
+    message.success('鎷h揣鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц棰嗗嚭 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishOutsourceIssue(formData.value.id);
+    message.success('棰嗗嚭鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceIssueApi.OutsourceIssue;
+    try {
+      if (formData.value?.id) {
+        await updateOutsourceIssue({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createOutsourceIssue(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmOutsourceIssueStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getOutsourceIssue(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :issue-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ュ鍗忓彂鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isStock"
+          title="纭鎵ц鎷h揣锛�"
+          @confirm="handleStock"
+        >
+          <Button type="primary">鎵ц鎷h揣</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц棰嗗嚭锛熸墽琛屽悗灏嗘墸鍑忓簱瀛橈紝涓旀棤娉曟挙閿�銆�"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц棰嗗嚭</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/line-form.vue b/src/views/wls/outsourceissue/modules/line-form.vue
new file mode 100644
index 0000000..1ae1da1
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/line-form.vue
@@ -0,0 +1,97 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOutsourceIssueLine,
+  getOutsourceIssueLine,
+  updateOutsourceIssueLine,
+} from '#/api/mes/wm/outsourceissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmOutsourceIssueLineApi.OutsourceIssueLine>();
+const issueId = ref<number>(); // 鎵�灞炲彂鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡鍙戞枡鍗曡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡鍙戞枡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceIssueLineApi.OutsourceIssueLine;
+    data.issueId = issueId.value;
+    try {
+      await (formData.value?.id
+        ? updateOutsourceIssueLine({ ...data, id: formData.value.id })
+        : createOutsourceIssueLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      issueId: number;
+    }>();
+    issueId.value = data.issueId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getOutsourceIssueLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/line-list.vue b/src/views/wls/outsourceissue/modules/line-list.vue
new file mode 100644
index 0000000..4e88748
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/line-list.vue
@@ -0,0 +1,227 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getOutsourceIssueDetailListByLineId } from '#/api/mes/wm/outsourceissue/detail';
+import {
+  deleteOutsourceIssueLine,
+  getOutsourceIssueLinePage,
+} from '#/api/mes/wm/outsourceissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  issueId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+  Record<number, MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑鍙戞枡鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmOutsourceIssueLineApi.OutsourceIssueLine) {
+  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+  row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteOutsourceIssueLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmOutsourceIssueLineApi.OutsourceIssueLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鍙戞枡鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, issueId: props.issueId, itemId, lineId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鍙戞枡鏄庣粏 */
+function getExpandedDetails(
+  row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鍙戞枡鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getOutsourceIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞藉彂鏂欐槑缁� */
+async function handleExpandChange(
+  row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.issueId) {
+            return { list: [], total: 0 };
+          }
+          return await getOutsourceIssueLinePage({
+            issueId: props.issueId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmOutsourceIssueLineApi.OutsourceIssueLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmOutsourceIssueLineApi.OutsourceIssueLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎷h揣',
+              type: 'link',
+              ifShow: isStock,
+              onClick: handlePicking.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/outsourcereceipt/data.ts b/src/views/wls/outsourcereceipt/data.ts
new file mode 100644
index 0000000..a96d8ac
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/data.ts
@@ -0,0 +1,540 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+  MesProWorkOrderTypeEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_OUTSOURCE_RECEIPT_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '澶栧崗璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        // 閫夋嫨澶栧崗璁㈠崟鍚庯紝鑷姩鍥炲~渚涘簲鍟�
+        onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
+          await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
+        },
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+        type: MesProWorkOrderTypeEnum.OUTSOURCE,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'workOrderCode',
+      label: '澶栧崗璁㈠崟鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ鍗忚鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(
+          DICT_TYPE.MES_WM_OUTSOURCE_RECEIPT_STATUS,
+          'number',
+        ),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmOutsourceReceiptApi.OutsourceReceipt>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍏ュ簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍏ュ簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'workOrderCode',
+      title: '澶栧崗璁㈠崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 120,
+    },
+    {
+      field: 'receiptDate',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_OUTSOURCE_RECEIPT_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍏ュ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'iqcCheckFlag',
+      title: '鏄惁妫�楠�',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'qualityStatus',
+      title: '璐ㄩ噺鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+      },
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 180,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍏ュ簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '绯荤粺鑷姩鐢熸垚',
+      },
+    },
+    {
+      fieldName: 'productionDate',
+      label: '鐢熶骇鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'expireDate',
+      label: '鏈夋晥鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'lotNumber',
+      label: '鎵瑰彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒鍙�',
+      },
+    },
+    {
+      fieldName: 'iqcCheckFlag',
+      label: '鏄惁璐ㄦ',
+      component: 'Switch',
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍏ュ簱鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  editable: boolean,
+): VxeTableGridOptions<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍏ュ簱鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍏ュ簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+        onChange: async () => {
+          await formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          });
+        },
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+          onChange: async () => {
+            await formApi?.setFieldValue('areaId', undefined);
+          },
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/outsourcereceipt/index.vue b/src/views/wls/outsourcereceipt/index.vue
new file mode 100644
index 0000000..d5a5066
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmOutsourceReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelOutsourceReceipt,
+  deleteOutsourceReceipt,
+  exportOutsourceReceipt,
+  getOutsourceReceiptPage,
+} from '#/api/mes/wm/outsourcereceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓澶栧崗鍏ュ簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅澶栧崗鍏ュ簱鍗� */
+function handleDetail(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫澶栧崗鍏ュ簱鍗� */
+function handleEdit(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚鍏ュ簱 */
+function handleFinish(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗� */
+async function handleDelete(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteOutsourceReceipt(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷澶栧崗鍏ュ簱鍗� */
+async function handleCancel(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+  await cancelOutsourceReceipt(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportOutsourceReceipt(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '澶栧崗鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getOutsourceReceiptPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmOutsourceReceiptApi.OutsourceReceipt>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="澶栧崗鍏ュ簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-outsource-receipt:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-outsource-receipt:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-outsource-receipt:update'],
+              ifShow: row.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-outsource-receipt:delete'],
+              ifShow: row.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-outsource-receipt:update'],
+              ifShow: row.status === MesWmOutsourceReceiptStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '瀹屾垚鍏ュ簱',
+              type: 'link',
+              auth: ['mes:wm-outsource-receipt:finish'],
+              ifShow: row.status === MesWmOutsourceReceiptStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-outsource-receipt:update'],
+              ifShow:
+                row.status === MesWmOutsourceReceiptStatusEnum.APPROVING ||
+                row.status === MesWmOutsourceReceiptStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ュ鍗忓叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/detail-form.vue b/src/views/wls/outsourcereceipt/modules/detail-form.vue
new file mode 100644
index 0000000..a4ed921
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/detail-form.vue
@@ -0,0 +1,103 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOutsourceReceiptDetail,
+  getOutsourceReceiptDetail,
+  updateOutsourceReceiptDetail,
+} from '#/api/mes/wm/outsourcereceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鏀惰揣鏄庣粏'])
+    : $t('ui.actionTitle.create', ['鏀惰揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail;
+    data.receiptId = receiptId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateOutsourceReceiptDetail({ ...data, id: formData.value.id })
+        : createOutsourceReceiptDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getOutsourceReceiptDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-1/2">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-list.vue b/src/views/wls/outsourcereceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/outsourcereceipt/modules/detail-list.vue
copy to src/views/wls/outsourcereceipt/modules/detail-list.vue
diff --git a/src/views/wls/outsourcereceipt/modules/form.vue b/src/views/wls/outsourcereceipt/modules/form.vue
new file mode 100644
index 0000000..c538d37
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/form.vue
@@ -0,0 +1,227 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmOutsourceReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOutsourceReceipt,
+  finishOutsourceReceipt,
+  getOutsourceReceipt,
+  stockOutsourceReceipt,
+  submitOutsourceReceipt,
+  updateOutsourceReceipt,
+} from '#/api/mes/wm/outsourcereceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmOutsourceReceiptApi.OutsourceReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愬叆搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['澶栧崗鍏ュ簱鍗�']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц涓婃灦';
+  }
+  if (formType.value === 'finish') {
+    return '瀹屾垚鍏ュ簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['澶栧崗鍏ュ簱鍗�'])
+    : $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmOutsourceReceiptApi.OutsourceReceipt;
+      await updateOutsourceReceipt({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitOutsourceReceipt(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await stockOutsourceReceipt(formData.value.id);
+    message.success('涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 瀹屾垚鍏ュ簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishOutsourceReceipt(formData.value.id);
+    message.success('鍏ュ簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceReceiptApi.OutsourceReceipt;
+    try {
+      if (formData.value?.id) {
+        await updateOutsourceReceipt({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createOutsourceReceipt(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmOutsourceReceiptStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getOutsourceReceipt(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :receipt-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ュ鍗忓叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isStock"
+          title="纭鎵ц涓婃灦锛�"
+          @confirm="handleStock"
+        >
+          <Button type="primary">鎵ц涓婃灦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭瀹屾垚鍏ュ簱锛熷畬鎴愬悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">瀹屾垚鍏ュ簱</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/line-form.vue b/src/views/wls/outsourcereceipt/modules/line-form.vue
new file mode 100644
index 0000000..1b3d829
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createOutsourceReceiptLine,
+  getOutsourceReceiptLine,
+  updateOutsourceReceiptLine,
+} from '#/api/mes/wm/outsourcereceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['澶栧崗鍏ュ簱鍗曡'])
+    : $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟锛堟壒娆″彿鐢卞悗绔嚜鍔ㄧ敓鎴愶紝涓嶆彁浜わ級
+    const data =
+      (await formApi.getValues()) as MesWmOutsourceReceiptLineApi.OutsourceReceiptLine;
+    data.receiptId = receiptId.value;
+    delete data.batchCode;
+    try {
+      await (formData.value?.id
+        ? updateOutsourceReceiptLine({ ...data, id: formData.value.id })
+        : createOutsourceReceiptLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getOutsourceReceiptLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/line-list.vue b/src/views/wls/outsourcereceipt/modules/line-list.vue
new file mode 100644
index 0000000..8aa3ef4
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/line-list.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getOutsourceReceiptDetailListByLineId } from '#/api/mes/wm/outsourcereceipt/detail';
+import {
+  deleteOutsourceReceiptLine,
+  getOutsourceReceiptLinePage,
+} from '#/api/mes/wm/outsourcereceipt/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑鏀惰揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine) {
+  lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+  row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteOutsourceReceiptLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鏀惰揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鏀惰揣鏄庣粏 */
+function getExpandedDetails(
+  row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鏀惰揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getOutsourceReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥敹璐ф槑缁� */
+async function handleExpandChange(
+  row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.receiptId) {
+            return { list: [], total: 0 };
+          }
+          return await getOutsourceReceiptLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            receiptId: props.receiptId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '涓婃灦',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handlePicking.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/mes/wm/packages/components/index.ts b/src/views/wls/packages/components/index.ts
similarity index 100%
copy from src/views/mes/wm/packages/components/index.ts
copy to src/views/wls/packages/components/index.ts
diff --git a/src/views/wls/packages/components/select-dialog.vue b/src/views/wls/packages/components/select-dialog.vue
new file mode 100644
index 0000000..55f5da9
--- /dev/null
+++ b/src/views/wls/packages/components/select-dialog.vue
@@ -0,0 +1,231 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { nextTick, ref } from 'vue';
+
+import { MesWmPackageStatusEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getPackagePage } from '#/api/mes/wm/packages';
+
+import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
+
+const props = withDefaults(
+  defineProps<{
+    childableOnly?: boolean; // 鍙睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愮姸鎬侊級
+    excludeId?: number; // 鎺掗櫎鐨勭紪鍙凤紙閬垮厤閫夋嫨鑷繁浣滀负鐖剁锛�
+  }>(),
+  {
+    childableOnly: false,
+    excludeId: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  selected: [rows: MesWmPackageApi.Package[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(true); // 鏄惁澶氶��
+const selectedRows = ref<MesWmPackageApi.Package[]>([]); // 宸查�夎绠卞崟
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎绠卞崟缂栧彿
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, MesWmPackageApi.Package>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as MesWmPackageApi.Package[];
+  records.forEach((row) => {
+    if (row.id !== undefined && row.id !== null) {
+      selectedMap.set(row.id, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MesWmPackageApi.Package) {
+  selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmPackageApi.Package) {
+  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+  await gridApi.grid.setCheckboxRow(row, !selected);
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紱澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({ row }: { row: MesWmPackageApi.Package }) {
+  if (multiple.value) {
+    await toggleMultipleRow(row);
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夎绠卞崟 */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = gridApi.grid.getData() as MesWmPackageApi.Package[];
+  for (const row of rows) {
+    if (
+      row.id === undefined ||
+      row.id === null ||
+      !preSelectedIds.value.includes(row.id)
+    ) {
+      continue;
+    }
+    if (multiple.value) {
+      await gridApi.grid.setCheckboxRow(row, true);
+    } else {
+      await gridApi.grid.setRadioRow(row);
+      selectedRows.value = [row];
+      return;
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useSelectGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useSelectGridColumns(true),
+    height: 480,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      range: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          const data = await getPackagePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+            // childableOnly 妯″紡锛氬浐瀹氳繃婊ゆ棤鐖剁 + 宸插畬鎴愮姸鎬�
+            parentId: props.childableOnly ? 0 : undefined,
+            status: props.childableOnly
+              ? MesWmPackageStatusEnum.FINISHED
+              : undefined,
+          });
+          // 鎺掗櫎鎸囧畾缂栧彿锛岄伩鍏嶉�夋嫨鑷繁浣滀负鐖剁
+          const list = props.excludeId
+            ? (data.list || []).filter((item) => item.id !== props.excludeId)
+            : data.list || [];
+          return { list, total: data.total };
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmPackageApi.Package>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: MesWmPackageApi.Package }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑瑁呯鍗曢�夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? true;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useSelectGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+function closeModal() {
+  open.value = false;
+}
+
+/** 纭閫夋嫨 */
+function handleConfirm() {
+  const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+  if (rows.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', multiple.value ? rows : [rows[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    :destroy-on-close="true"
+    title="瑁呯鍗曢�夋嫨"
+    width="80%"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Alert
+      v-if="childableOnly"
+      class="mb-3"
+      message="浠呭睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愶級"
+      show-icon
+      type="info"
+    />
+    <Grid table-title="瑁呯鍗曞垪琛�" />
+    <template #footer>
+      <Button @click="closeModal">鍙栨秷</Button>
+      <Button type="primary" @click="handleConfirm">纭畾</Button>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/packages/components/select.vue b/src/views/wls/packages/components/select.vue
new file mode 100644
index 0000000..d4a8f37
--- /dev/null
+++ b/src/views/wls/packages/components/select.vue
@@ -0,0 +1,143 @@
+<script lang="ts" setup>
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getPackage } from '#/api/mes/wm/packages';
+
+import WmPackageSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmPackageSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    childableOnly?: boolean; // 鍙睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愮姸鎬侊級
+    disabled?: boolean;
+    excludeId?: number; // 鎺掗櫎鐨勭紪鍙凤紙閬垮厤閫夋嫨鑷繁浣滀负鐖剁锛�
+    modelValue?: number;
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    childableOnly: false,
+    disabled: false,
+    excludeId: undefined,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨瑁呯鍗�',
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmPackageApi.Package | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmPackageSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmPackageApi.Package>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ瑁呯鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getPackage(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夎绠卞崟 */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑瑁呯鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmPackageApi.Package[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+          <div>瀹㈡埛锛歿{ selectedItem.clientName || '-' }}</div>
+          <div>閿�鍞鍗曪細{{ selectedItem.salesOrderCode || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmPackageSelectDialog
+    ref="dialogRef"
+    :childable-only="childableOnly"
+    :exclude-id="excludeId"
+    @selected="handleSelected"
+  />
+</template>
diff --git a/src/views/wls/packages/data.ts b/src/views/wls/packages/data.ts
new file mode 100644
index 0000000..37e3967
--- /dev/null
+++ b/src/views/wls/packages/data.ts
@@ -0,0 +1,641 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdUnitMeasureSelect } from '#/views/mes/md/unitmeasure/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '瑁呯鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_PACKAGE_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'packageDate',
+      label: '瑁呯鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨瑁呯鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'inspectorUserId',
+      label: '妫�鏌ュ憳',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+        valueField: 'id',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'invoiceCode',
+      label: '鍙戠エ缂栧彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ彂绁ㄧ紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'sizeUnitId',
+      label: '灏哄鍗曚綅',
+      component: markRaw(MdUnitMeasureSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨灏哄鍗曚綅',
+      },
+    },
+    {
+      fieldName: 'length',
+      label: '绠遍暱搴�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ョ闀垮害',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'width',
+      label: '绠卞搴�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ョ瀹藉害',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'height',
+      label: '绠遍珮搴�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ョ楂樺害',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'weightUnitId',
+      label: '閲嶉噺鍗曚綅',
+      component: markRaw(MdUnitMeasureSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨閲嶉噺鍗曚綅',
+      },
+    },
+    {
+      fieldName: 'netWeight',
+      label: '鍑�閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ噣閲�',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'grossWeight',
+      label: '姣涢噸',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ瘺閲�',
+        precision: 2,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '瑁呯鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'inspectorUserId',
+      label: '妫�鏌ュ憳',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+        valueField: 'id',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '瑁呯鍗曠紪鍙�',
+      minWidth: 250,
+      fixed: 'left',
+      treeNode: true,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'packageDate',
+      title: '瑁呯鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'invoiceCode',
+      title: '鍙戠エ缂栧彿',
+      minWidth: 120,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      minWidth: 100,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'length',
+      title: '绠遍暱搴�',
+      width: 80,
+    },
+    {
+      field: 'width',
+      title: '绠卞搴�',
+      width: 80,
+    },
+    {
+      field: 'height',
+      title: '绠遍珮搴�',
+      width: 80,
+    },
+    {
+      field: 'sizeUnitName',
+      title: '灏哄鍗曚綅',
+      width: 90,
+    },
+    {
+      field: 'netWeight',
+      title: '鍑�閲�',
+      width: 80,
+    },
+    {
+      field: 'grossWeight',
+      title: '姣涢噸',
+      width: 80,
+    },
+    {
+      field: 'weightUnitName',
+      title: '閲嶉噺鍗曚綅',
+      width: 90,
+    },
+    {
+      field: 'inspectorName',
+      title: '妫�鏌ュ憳',
+      minWidth: 100,
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 160,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 瀛愮鍒楄〃鐨勫瓧娈� */
+export function useSubPackageGridColumns(): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '瑁呯鍗曠紪鍙�',
+      minWidth: 160,
+      fixed: 'left',
+    },
+    {
+      field: 'packageDate',
+      title: '瑁呯鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'invoiceCode',
+      title: '鍙戠エ缂栧彿',
+      minWidth: 120,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      minWidth: 100,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'netWeight',
+      title: '鍑�閲�',
+      width: 80,
+    },
+    {
+      field: 'grossWeight',
+      title: '姣涢噸',
+      width: 80,
+    },
+    {
+      field: 'weightUnitName',
+      title: '閲嶉噺鍗曚綅',
+      width: 90,
+    },
+    {
+      field: 'inspectorName',
+      title: '妫�鏌ュ憳',
+      minWidth: 100,
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 100,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 瑁呯鏄庣粏鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'itemId',
+      label: '浜у搧鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '瑁呯鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0.01,
+        placeholder: '璇疯緭鍏ヨ绠辨暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'expireDate',
+      label: '鏈夋晥鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 瑁呯鏄庣粏鍒楄〃鐨勫瓧娈� */
+export function useLineGridColumns(): VxeTableGridOptions<MesWmPackageLineApi.PackageLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '浜у搧鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '瑁呯鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'workOrderCode',
+      title: '鐢熶骇璁㈠崟缂栧彿',
+      minWidth: 140,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'expireDate',
+      title: '鏈夋晥鏈�',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 120,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '瑁呯鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'inspectorUserId',
+      label: '妫�鏌ュ憳',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+        valueField: 'id',
+      },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+  multiple = true,
+): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+  return [
+    {
+      type: multiple ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'code',
+      title: '瑁呯鍗曠紪鍙�',
+      minWidth: 160,
+    },
+    {
+      field: 'packageDate',
+      title: '瑁呯鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'invoiceCode',
+      title: '鍙戠エ缂栧彿',
+      minWidth: 120,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      minWidth: 100,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'netWeight',
+      title: '鍑�閲�',
+      width: 80,
+    },
+    {
+      field: 'grossWeight',
+      title: '姣涢噸',
+      width: 80,
+    },
+    {
+      field: 'weightUnitName',
+      title: '閲嶉噺鍗曚綅',
+      width: 90,
+    },
+    {
+      field: 'inspectorName',
+      title: '妫�鏌ュ憳',
+      minWidth: 100,
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/packages/index.vue b/src/views/wls/packages/index.vue
new file mode 100644
index 0000000..5f03e30
--- /dev/null
+++ b/src/views/wls/packages/index.vue
@@ -0,0 +1,160 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmPackageStatusEnum } from '../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { deletePackage, getPackagePage } from '#/api/mes/wm/packages';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const isExpanded = ref(true); // 鏍戝舰琛ㄦ牸鏄惁灞曞紑
+
+/** 鍒囨崲鏍戝舰灞曞紑/鏀剁缉鐘舵�� */
+function handleExpand() {
+  isExpanded.value = !isExpanded.value;
+  gridApi.grid.setAllTreeExpand(isExpanded.value);
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓瑁呯鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅瑁呯鍗� */
+function handleDetail(row: MesWmPackageApi.Package) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫瑁呯鍗� */
+function handleEdit(row: MesWmPackageApi.Package) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎瑁呯鍗� */
+async function handleDelete(row: MesWmPackageApi.Package) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deletePackage(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getPackagePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+    treeConfig: {
+      parentField: 'parentId',
+      rowField: 'id',
+      transform: true,
+      expandAll: true,
+      reserve: true,
+    },
+  } as VxeTableGridOptions<MesWmPackageApi.Package>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="瑁呯鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['瑁呯鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-package:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: isExpanded ? '鏀剁缉' : '灞曞紑',
+              type: 'primary',
+              onClick: handleExpand,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-package:update'],
+              ifShow: row.status === MesWmPackageStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-package:delete'],
+              ifShow: row.status === MesWmPackageStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/packages/modules/form.vue b/src/views/wls/packages/modules/form.vue
new file mode 100644
index 0000000..fee5a6f
--- /dev/null
+++ b/src/views/wls/packages/modules/form.vue
@@ -0,0 +1,193 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmPackageStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, message, Tabs } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createPackage,
+  finishPackage,
+  getPackage,
+  updatePackage,
+} from '#/api/mes/wm/packages';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import PackageLineList from './package-line-list.vue';
+import SubPackageList from './sub-package-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmPackageApi.Package>();
+const subTabsName = ref('subPackage'); // 瀛愯〃褰撳墠 tab
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜� finish 鏃惰烦杩囨湭鍙樻洿鐨勪繚瀛樿姹�
+
+const isEditable = computed(() =>
+  ['create', 'update'].includes(formType.value),
+);
+const canFinish = computed(
+  () =>
+    formType.value === 'update' &&
+    formData.value?.status === MesWmPackageStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['瑁呯鍗�']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['瑁呯鍗�'])
+    : $t('ui.actionTitle.create', ['瑁呯鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦琛ㄥ崟锛坈reate/update锛夛紝鏂板鎴愬姛鍚庡垏鎹负缂栬緫鎬佷互缁х画缁存姢瀛愯〃 */
+async function handleSubmit(): Promise<boolean> {
+  const { valid } = await formApi.validate();
+  if (!valid) {
+    return false;
+  }
+  const data = (await formApi.getValues()) as MesWmPackageApi.Package;
+  if (formData.value?.id) {
+    await updatePackage({ ...data, id: formData.value.id });
+    formData.value = { ...formData.value, ...data };
+  } else {
+    const id = await createPackage(data);
+    formData.value = {
+      ...data,
+      id,
+      status: MesWmPackageStatusEnum.PREPARE,
+    };
+    await formApi.setFieldValue('id', id);
+    await formApi.setFieldValue('status', MesWmPackageStatusEnum.PREPARE);
+    formType.value = 'update';
+  }
+  // 鍒锋柊蹇収锛屼笅娆� finish 鐢ㄤ簬鍒ゆ柇鏄惁闇�瑕佸啀淇濆瓨
+  originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  return true;
+}
+
+/** 瀹屾垚瑁呯鍗曪細缂栬緫鎬佹湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤瀹屾垚鎺ュ彛 */
+async function handleFinish() {
+  const id = formData.value?.id;
+  if (!id) {
+    return;
+  }
+  try {
+    await confirm('纭瀹屾垚璇ヨ绠卞崟锛熷畬鎴愬悗灏嗕笉鍙紪杈戙��');
+  } catch {
+    return;
+  }
+  const { valid } = await formApi.validate();
+  if (!valid) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data = (await formApi.getValues()) as MesWmPackageApi.Package;
+      await updatePackage({ ...data, id });
+      originalSnapshot.value = current;
+    }
+    await finishPackage(id);
+    message.success('瀹屾垚鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (formType.value === 'detail') {
+      await modalApi.close();
+      return;
+    }
+    modalApi.lock();
+    try {
+      // 鎻愪氦琛ㄥ崟
+      const ok = await handleSubmit();
+      if (!ok) {
+        return;
+      }
+      // 鍏抽棴骞舵彁绀�
+      message.success($t('ui.actionMessage.operationSuccess'));
+      emit('success');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    subTabsName.value = 'subPackage';
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(formType.value === 'detail');
+    modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getPackage(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    // 璁板綍鍒濆蹇収锛屽悗缁� finish 鐢ㄤ簬鍒ゆ柇鏄惁闇�瑕佸啀淇濆瓨
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-4/5">
+    <Form class="mx-4" />
+    <Tabs
+      v-if="formData?.id"
+      v-model:active-key="subTabsName"
+      class="mx-4 mt-4"
+    >
+      <Tabs.TabPane key="subPackage" tab="瀛愮">
+        <SubPackageList :editable="isEditable" :package-id="formData.id" />
+      </Tabs.TabPane>
+      <Tabs.TabPane key="packageLine" tab="瑁呯娓呭崟">
+        <PackageLineList :editable="isEditable" :package-id="formData.id" />
+      </Tabs.TabPane>
+    </Tabs>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Button v-if="canFinish" type="primary" @click="handleFinish">
+          瀹屾垚
+        </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/packages/modules/line-form.vue b/src/views/wls/packages/modules/line-form.vue
new file mode 100644
index 0000000..d81eadf
--- /dev/null
+++ b/src/views/wls/packages/modules/line-form.vue
@@ -0,0 +1,92 @@
+<script lang="ts" setup>
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createPackageLine,
+  getPackageLine,
+  updatePackageLine,
+} from '#/api/mes/wm/packages/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmPackageLineApi.PackageLine>();
+const packageId = ref<number>(); // 鎵�灞炶绠卞崟缂栧彿
+const getTitle = computed(() =>
+  formData.value?.id
+    ? $t('ui.actionTitle.edit', ['瑁呯鏄庣粏'])
+    : $t('ui.actionTitle.create', ['瑁呯鏄庣粏']),
+);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmPackageLineApi.PackageLine;
+    data.packageId = packageId.value;
+    try {
+      await (formData.value?.id
+        ? updatePackageLine({ ...data, id: formData.value.id })
+        : createPackageLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; packageId: number }>();
+    packageId.value = data.packageId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getPackageLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-2/5">
+    <Form />
+  </Modal>
+</template>
diff --git a/src/views/wls/packages/modules/package-line-list.vue b/src/views/wls/packages/modules/package-line-list.vue
new file mode 100644
index 0000000..cf4e063
--- /dev/null
+++ b/src/views/wls/packages/modules/package-line-list.vue
@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deletePackageLine,
+  getPackageLinePage,
+} from '#/api/mes/wm/packages/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  editable: boolean; // 鏄惁鍙紪杈戯紙鑽夌鎬侊級
+  packageId: number; // 鎵�灞炶绠卞崟缂栧彿
+}>();
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞瑁呯鏄庣粏 */
+function handleCreate() {
+  lineFormModalApi.setData({ packageId: props.packageId }).open();
+}
+
+/** 缂栬緫瑁呯鏄庣粏 */
+function handleEdit(row: MesWmPackageLineApi.PackageLine) {
+  lineFormModalApi.setData({ id: row.id, packageId: props.packageId }).open();
+}
+
+/** 鍒犻櫎瑁呯鏄庣粏 */
+async function handleDelete(row: MesWmPackageLineApi.PackageLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deletePackageLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(),
+    height: 360,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.packageId) {
+            return { list: [], total: 0 };
+          }
+          return await getPackageLinePage({
+            packageId: props.packageId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmPackageLineApi.PackageLine>,
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <Grid table-title="瑁呯娓呭崟">
+      <template #toolbar-tools>
+        <TableAction
+          v-if="editable"
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['瑁呯鏄庣粏']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: editable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: editable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/mes/wm/packages/modules/sub-package-list.vue b/src/views/wls/packages/modules/sub-package-list.vue
similarity index 100%
copy from src/views/mes/wm/packages/modules/sub-package-list.vue
copy to src/views/wls/packages/modules/sub-package-list.vue
diff --git a/src/views/wls/productissue/data.ts b/src/views/wls/productissue/data.ts
new file mode 100644
index 0000000..dbf7a60
--- /dev/null
+++ b/src/views/wls/productissue/data.ts
@@ -0,0 +1,475 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '棰嗘枡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ鏂欏崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_PRODUCT_ISSUE_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '棰嗘枡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ鏂欏崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'requiredTime',
+      label: '闇�姹傛椂闂�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'workstationId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '棰嗘枡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ鏂欏崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '棰嗘枡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ鏂欏崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'issueDate',
+      label: '棰嗘枡鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(
+          DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS,
+          'number',
+        ),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '棰嗘枡鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '棰嗘枡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'workOrderCode',
+      title: '鐢熶骇璁㈠崟',
+      minWidth: 140,
+    },
+    {
+      field: 'workstationName',
+      title: '宸ヤ綔绔�',
+      minWidth: 120,
+    },
+    {
+      field: 'requiredTime',
+      title: '闇�姹傛椂闂�',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 棰嗘枡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '棰嗘枡鏁伴噺',
+      width: 100,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 棰嗘枡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '浜у搧鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '棰嗘枡鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ラ鏂欐暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductIssueDetailApi.ProductIssueDetail>['columns'] {
+  return [
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            locationId: stock?.locationId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ユ暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍑哄簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/productissue/index.vue b/src/views/wls/productissue/index.vue
new file mode 100644
index 0000000..d627716
--- /dev/null
+++ b/src/views/wls/productissue/index.vue
@@ -0,0 +1,219 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelProductIssue,
+  deleteProductIssue,
+  exportProductIssue,
+  getProductIssuePage,
+  submitProductIssue,
+} from '#/api/mes/wm/productissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓棰嗘枡鍑哄簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅棰嗘枡鍑哄簱鍗� */
+function handleDetail(row: MesWmProductIssueApi.ProductIssue) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫棰嗘枡鍑哄簱鍗� */
+function handleEdit(row: MesWmProductIssueApi.ProductIssue) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmProductIssueApi.ProductIssue) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱 */
+function handleFinish(row: MesWmProductIssueApi.ProductIssue) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愪氦棰嗘枡鍑哄簱鍗� */
+async function handleSubmit(row: MesWmProductIssueApi.ProductIssue) {
+  await submitProductIssue(row.id!);
+  message.success('鎻愪氦鎴愬姛');
+  handleRefresh();
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗� */
+async function handleDelete(row: MesWmProductIssueApi.ProductIssue) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteProductIssue(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷棰嗘枡鍑哄簱鍗� */
+async function handleCancel(row: MesWmProductIssueApi.ProductIssue) {
+  await cancelProductIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportProductIssue(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '棰嗘枡鍑哄簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getProductIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="棰嗘枡鍑哄簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-product-issue:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-product-issue:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-product-issue:update'],
+              ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.submit'),
+              type: 'link',
+              auth: ['mes:wm-product-issue:update'],
+              ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+              popConfirm: {
+                title: '纭鎻愪氦璇ラ鏂欏嚭搴撳崟锛熸彁浜ゅ悗灏嗕笉鑳戒慨鏀广��',
+                confirm: handleSubmit.bind(null, row),
+              },
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-product-issue:delete'],
+              ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鎷h揣',
+              type: 'link',
+              auth: ['mes:wm-product-issue:update'],
+              ifShow: row.status === MesWmProductIssueStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '瀹屾垚',
+              type: 'link',
+              auth: ['mes:wm-product-issue:finish'],
+              ifShow: row.status === MesWmProductIssueStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-product-issue:update'],
+              ifShow:
+                row.status === MesWmProductIssueStatusEnum.APPROVING ||
+                row.status === MesWmProductIssueStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ラ鏂欏嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/productissue/modules/detail-form.vue b/src/views/wls/productissue/modules/detail-form.vue
new file mode 100644
index 0000000..191d9bd
--- /dev/null
+++ b/src/views/wls/productissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductIssueDetail,
+  getProductIssueDetail,
+  updateProductIssueDetail,
+} from '#/api/mes/wm/productissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductIssueDetailApi.ProductIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞為鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+    : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductIssueDetailApi.ProductIssueDetail;
+    data.issueId = issueId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductIssueDetail({ ...data, id: formData.value.id })
+        : createProductIssueDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      issueId: number;
+      itemId?: number;
+      lineId: number;
+    }>();
+    issueId.value = data.issueId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductIssueDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/productissue/modules/detail-list.vue b/src/views/wls/productissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productissue/modules/detail-list.vue
copy to src/views/wls/productissue/modules/detail-list.vue
diff --git a/src/views/wls/productissue/modules/form.vue b/src/views/wls/productissue/modules/form.vue
new file mode 100644
index 0000000..471ef95
--- /dev/null
+++ b/src/views/wls/productissue/modules/form.vue
@@ -0,0 +1,232 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  checkProductIssueQuantity,
+  createProductIssue,
+  finishProductIssue,
+  getProductIssue,
+  stockProductIssue,
+  submitProductIssue,
+  updateProductIssue,
+} from '#/api/mes/wm/productissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductIssueApi.ProductIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愬嚭搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmProductIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['棰嗘枡鍑哄簱鍗�']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц鎷h揣';
+  }
+  if (formType.value === 'finish') {
+    return '瀹屾垚棰嗘枡鍑哄簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['棰嗘枡鍑哄簱鍗�'])
+    : $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦棰嗘枡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+      await updateProductIssue({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitProductIssue(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鎷h揣锛氶鏂欐暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  const quantityMatch = await checkProductIssueQuantity(formData.value.id);
+  if (!quantityMatch) {
+    try {
+      await confirm('棰嗘枡鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+    } catch {
+      return;
+    }
+  }
+  modalApi.lock();
+  try {
+    await stockProductIssue(formData.value.id);
+    message.success('鎷h揣鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishProductIssue(formData.value.id);
+    message.success('瀹屾垚鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+    try {
+      if (formData.value?.id) {
+        await updateProductIssue({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createProductIssue(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmProductIssueStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductIssue(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :issue-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ鏂欏嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="handleStock">
+          鎵ц鎷h揣
+        </Button>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭瀹屾垚璇ラ鏂欏崟骞舵墽琛屽嚭搴撳悧锛�"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">瀹屾垚</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/productissue/modules/line-form.vue b/src/views/wls/productissue/modules/line-form.vue
new file mode 100644
index 0000000..f513551
--- /dev/null
+++ b/src/views/wls/productissue/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductIssueLine,
+  getProductIssueLine,
+  updateProductIssueLine,
+} from '#/api/mes/wm/productissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductIssueLineApi.ProductIssueLine>();
+const issueId = ref<number>(); // 鎵�灞為鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['棰嗘枡鍑哄簱鍗曡'])
+    : $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductIssueLineApi.ProductIssueLine;
+    data.issueId = issueId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductIssueLine({ ...data, id: formData.value.id })
+        : createProductIssueLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; issueId: number }>();
+    issueId.value = data.issueId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getProductIssueLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/productissue/modules/line-list.vue b/src/views/wls/productissue/modules/line-list.vue
new file mode 100644
index 0000000..49e5ded
--- /dev/null
+++ b/src/views/wls/productissue/modules/line-list.vue
@@ -0,0 +1,223 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductIssueDetailListByLineId } from '#/api/mes/wm/productissue/detail';
+import {
+  deleteProductIssueLine,
+  getProductIssueLinePage,
+} from '#/api/mes/wm/productissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  issueId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+  Record<number, MesWmProductIssueDetailApi.ProductIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductIssueLineApi.ProductIssueLine) {
+  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmProductIssueLineApi.ProductIssueLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteProductIssueLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmProductIssueLineApi.ProductIssueLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, issueId: props.issueId, itemId, lineId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmProductIssueLineApi.ProductIssueLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getProductIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+  row: MesWmProductIssueLineApi.ProductIssueLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.issueId) {
+            return { list: [], total: 0 };
+          }
+          return await getProductIssueLinePage({
+            issueId: props.issueId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmProductIssueLineApi.ProductIssueLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎷h揣',
+              type: 'link',
+              ifShow: isStock,
+              onClick: handlePicking.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/productreceipt/data.ts b/src/views/wls/productreceipt/data.ts
new file mode 100644
index 0000000..0f755ce
--- /dev/null
+++ b/src/views/wls/productreceipt/data.ts
@@ -0,0 +1,452 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.PRODUCTRECPT_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍏ュ簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍏ュ簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'receiptDate',
+      label: '鍏ュ簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductReceiptApi.ProductReceipt>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍏ュ簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍏ュ簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'workOrderCode',
+      title: '鐢熶骇璁㈠崟',
+      minWidth: 140,
+    },
+    {
+      field: 'receiptDate',
+      title: '鍏ュ簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PRODUCT_RECEIPT_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductReceiptLineApi.ProductReceiptLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '浜у搧鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍏ュ簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 200,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            itemId: stock?.itemId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+          });
+        },
+        virtualFilter: 'only',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍏ュ簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductReceiptDetailApi.ProductReceiptDetail>['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍏ュ簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+        onChange: async () => {
+          await formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          });
+        },
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+          onChange: async () => {
+            await formApi?.setFieldValue('areaId', undefined);
+          },
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+  ];
+}
diff --git a/src/views/wls/productreceipt/index.vue b/src/views/wls/productreceipt/index.vue
new file mode 100644
index 0000000..3fb50a4
--- /dev/null
+++ b/src/views/wls/productreceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelProductReceipt,
+  deleteProductReceipt,
+  exportProductReceipt,
+  getProductReceiptPage,
+} from '#/api/mes/wm/productreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓浜у搧鍏ュ簱鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅浜у搧鍏ュ簱鍗� */
+function handleDetail(row: MesWmProductReceiptApi.ProductReceipt) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫浜у搧鍏ュ簱鍗� */
+function handleEdit(row: MesWmProductReceiptApi.ProductReceipt) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmProductReceiptApi.ProductReceipt) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmProductReceiptApi.ProductReceipt) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗� */
+async function handleDelete(row: MesWmProductReceiptApi.ProductReceipt) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteProductReceipt(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷浜у搧鍏ュ簱鍗� */
+async function handleCancel(row: MesWmProductReceiptApi.ProductReceipt) {
+  await cancelProductReceipt(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportProductReceipt(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '浜у搧鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getProductReceiptPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmProductReceiptApi.ProductReceipt>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="浜у搧鍏ュ簱鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['浜у搧鍏ュ簱鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-product-receipt:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-product-receipt:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-product-receipt:update'],
+              ifShow: row.status === MesWmProductReceiptStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-product-receipt:delete'],
+              ifShow: row.status === MesWmProductReceiptStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-product-receipt:update'],
+              ifShow: row.status === MesWmProductReceiptStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鎵ц鍏ュ簱',
+              type: 'link',
+              auth: ['mes:wm-product-receipt:finish'],
+              ifShow: row.status === MesWmProductReceiptStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-product-receipt:update'],
+              ifShow:
+                row.status === MesWmProductReceiptStatusEnum.APPROVING ||
+                row.status === MesWmProductReceiptStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ヤ骇鍝佸叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/productreceipt/modules/detail-form.vue b/src/views/wls/productreceipt/modules/detail-form.vue
new file mode 100644
index 0000000..28e84b9
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductReceiptDetail,
+  getProductReceiptDetail,
+  updateProductReceiptDetail,
+} from '#/api/mes/wm/productreceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductReceiptDetailApi.ProductReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+    : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductReceiptDetailApi.ProductReceiptDetail;
+    data.receiptId = receiptId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductReceiptDetail({ ...data, id: formData.value.id })
+        : createProductReceiptDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      receiptId: number;
+    }>();
+    receiptId.value = data.receiptId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductReceiptDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/productreceipt/modules/detail-list.vue b/src/views/wls/productreceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productreceipt/modules/detail-list.vue
copy to src/views/wls/productreceipt/modules/detail-list.vue
diff --git a/src/views/wls/productreceipt/modules/form.vue b/src/views/wls/productreceipt/modules/form.vue
new file mode 100644
index 0000000..12e4c66
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/form.vue
@@ -0,0 +1,243 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  checkProductReceiptQuantity,
+  createProductReceipt,
+  finishProductReceipt,
+  getProductReceipt,
+  stockProductReceipt,
+  submitProductReceipt,
+  updateProductReceipt,
+} from '#/api/mes/wm/productreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductReceiptApi.ProductReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmProductReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['浜у搧鍏ュ簱鍗�']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц涓婃灦';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍏ュ簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['浜у搧鍏ュ簱鍗�'])
+    : $t('ui.actionTitle.create', ['浜у搧鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmProductReceiptApi.ProductReceipt;
+      await updateProductReceipt({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitProductReceipt(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц涓婃灦锛氬厛纭锛屽啀鍦ㄦ槑缁嗘暟閲忎笌琛屾敹璐ф暟閲忎笉涓�鑷存椂浜屾纭 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  try {
+    await confirm('纭鎵ц涓婃灦锛�');
+  } catch {
+    return;
+  }
+  const quantityMatch = await checkProductReceiptQuantity(formData.value.id);
+  if (!quantityMatch) {
+    try {
+      await confirm('鏄庣粏鏁伴噺涓庤鏀惰揣鏁伴噺涓嶄竴鑷达紝纭鎵ц涓婃灦锛�');
+    } catch {
+      return;
+    }
+  }
+  modalApi.lock();
+  try {
+    await stockProductReceipt(formData.value.id);
+    message.success('涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishProductReceipt(formData.value.id);
+    message.success('鍏ュ簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductReceiptApi.ProductReceipt;
+    try {
+      if (formData.value?.id) {
+        await updateProductReceipt({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createProductReceipt(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmProductReceiptStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductReceipt(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+
+/** 鍏ュ簱鍓嶇‘璁� */
+async function confirmFinish() {
+  try {
+    await confirm('纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��');
+  } catch {
+    return;
+  }
+  await handleFinish();
+}
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :receipt-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ヤ骇鍝佸叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="handleStock">
+          鎵ц涓婃灦
+        </Button>
+        <Button v-if="isFinish" type="primary" @click="confirmFinish">
+          鎵ц鍏ュ簱
+        </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/productreceipt/modules/line-form.vue b/src/views/wls/productreceipt/modules/line-form.vue
new file mode 100644
index 0000000..1838ca8
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/line-form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductReceiptLine,
+  getProductReceiptLine,
+  updateProductReceiptLine,
+} from '#/api/mes/wm/productreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductReceiptLineApi.ProductReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡鍏ュ簱鍗曡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductReceiptLineApi.ProductReceiptLine;
+    data.receiptId = receiptId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductReceiptLine({ ...data, id: formData.value.id })
+        : createProductReceiptLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useLineFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; receiptId: number }>();
+    receiptId.value = data.receiptId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getProductReceiptLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/productreceipt/modules/line-list.vue b/src/views/wls/productreceipt/modules/line-list.vue
new file mode 100644
index 0000000..56985b6
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/line-list.vue
@@ -0,0 +1,254 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { computed, reactive, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductReceiptDetailListByLineId } from '#/api/mes/wm/productreceipt/detail';
+import {
+  deleteProductReceiptLine,
+  getProductReceiptLinePage,
+} from '#/api/mes/wm/productreceipt/line';
+import { $t } from '#/locales';
+import { BarcodeDetail, PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmProductReceiptDetailApi.ProductReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+const barcodeDetailRef = ref(); // 鏉$爜璇︽儏寮圭獥瀹炰緥
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+  lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+  row: MesWmProductReceiptLineApi.ProductReceiptLine,
+) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteProductReceiptLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鏌ョ湅鐗╂枡鏉$爜 */
+function handleBarcode(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+  barcodeDetailRef.value?.openByBusiness(
+    row.itemId,
+    BarcodeBizTypeEnum.ITEM,
+    row.itemCode,
+    row.itemName,
+  );
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(
+  row: MesWmProductReceiptLineApi.ProductReceiptLine,
+) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getProductReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+  row: MesWmProductReceiptLineApi.ProductReceiptLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.receiptId) {
+            return { list: [], total: 0 };
+          }
+          return await getProductReceiptLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            receiptId: props.receiptId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmProductReceiptLineApi.ProductReceiptLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmProductReceiptLineApi.ProductReceiptLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '涓婃灦',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handleStock.bind(null, row),
+              },
+              {
+                label: '鏉$爜',
+                type: 'link',
+                onClick: handleBarcode.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </div>
+</template>
diff --git a/src/views/wls/productsales/data.ts b/src/views/wls/productsales/data.ts
new file mode 100644
index 0000000..6e9973f
--- /dev/null
+++ b/src/views/wls/productsales/data.ts
@@ -0,0 +1,701 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesWmSalesNoticeStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmSalesNoticeLineSelect,
+  WmSalesNoticeSelect,
+} from '#/views/wls/salesnotice/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType =
+  | 'create'
+  | 'detail'
+  | 'finish'
+  | 'shipping'
+  | 'stock'
+  | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佸~鍐欒繍鍗曘�佸嚭搴撱�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return (
+    formType === 'detail' ||
+    formType === 'finish' ||
+    formType === 'shipping' ||
+    formType === 'stock'
+  );
+}
+
+/** 鏄惁灞曠ず杩愯緭淇℃伅 */
+export function showShippingInfo(formType: FormType): boolean {
+  return (
+    formType === 'shipping' || formType === 'detail' || formType === 'finish'
+  );
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鍑哄簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_PRODUCT_SALES_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '鍑哄簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'noticeId',
+      label: '鍙戣揣閫氱煡鍗�',
+      component: markRaw(WmSalesNoticeSelect),
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        // 閫夋嫨鍙戣揣閫氱煡鍗曞悗锛岃嚜鍔ㄥ洖濉攢鍞鍗曞彿銆佸鎴枫�佹敹璐т汉淇℃伅
+        onChange: async (notice?: MesWmSalesNoticeApi.SalesNotice) => {
+          await formApi?.setValues({
+            clientId: notice?.clientId,
+            contactAddress: notice?.recipientAddress,
+            contactName: notice?.recipientName,
+            contactTelephone: notice?.recipientTelephone,
+            salesOrderCode: notice?.salesOrderCode,
+          });
+        },
+        status: MesWmSalesNoticeStatusEnum.APPROVED,
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'salesDate',
+      label: '鍑哄簱鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        disabled: isHeaderReadonly(formType),
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'contactName',
+      label: '鏀惰揣浜�',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ユ敹璐т汉',
+      },
+    },
+    {
+      fieldName: 'contactTelephone',
+      label: '鑱旂郴鏂瑰紡',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+      },
+    },
+    {
+      fieldName: 'contactAddress',
+      label: '鏀惰揣鍦板潃',
+      component: 'Input',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ユ敹璐у湴鍧�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        disabled: isHeaderReadonly(formType),
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+    {
+      fieldName: 'carrier',
+      label: '鎵胯繍鍟�',
+      component: 'Input',
+      componentProps: {
+        disabled: formType !== 'shipping',
+        placeholder: '璇疯緭鍏ユ壙杩愬晢',
+      },
+      dependencies: {
+        triggerFields: [''],
+        if: () => showShippingInfo(formType),
+      },
+    },
+    {
+      fieldName: 'shippingNumber',
+      label: '杩愯緭鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        disabled: formType !== 'shipping',
+        placeholder: '璇疯緭鍏ヨ繍杈撳崟鍙�',
+      },
+      dependencies: {
+        triggerFields: [''],
+        if: () => showShippingInfo(formType),
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鍑哄簱鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鍑哄簱鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'salesDate',
+      label: '鍑哄簱鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(
+          DICT_TYPE.MES_WM_PRODUCT_SALES_STATUS,
+          'number',
+        ),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductSalesApi.ProductSales>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鍑哄簱鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鍑哄簱鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'noticeCode',
+      title: '鍙戣揣閫氱煡鍗曞彿',
+      minWidth: 160,
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 160,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'contactName',
+      title: '鏀惰揣浜�',
+      minWidth: 100,
+    },
+    {
+      field: 'carrier',
+      title: '鎵胯繍鍟�',
+      minWidth: 120,
+    },
+    {
+      field: 'shippingNumber',
+      title: '杩愯緭鍗曞彿',
+      minWidth: 160,
+    },
+    {
+      field: 'salesDate',
+      title: '鍑哄簱鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_PRODUCT_SALES_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鍑哄簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductSalesLineApi.ProductSalesLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '浜у搧缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍑哄簱鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'oqcCheckFlag',
+      title: '鏄惁鏍¢獙',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 150,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鍑哄簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+  hasNotice: boolean,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'noticeId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'noticeLineId',
+      label: '鍙戣揣閫氱煡鍗曡',
+      component: markRaw(WmSalesNoticeLineSelect),
+      componentProps: {
+        // 閫夋嫨鍙戣揣閫氱煡鍗曡鍚庯紝鑷姩鍥炲~鐗╂枡銆佹暟閲忋�佹壒娆°�佹槸鍚︽楠�
+        onChange: async (line?: MesWmSalesNoticeLineApi.SalesNoticeLine) => {
+          await formApi?.setValues({
+            batchCode: line?.batchCode,
+            itemId: line?.itemId,
+            oqcCheckFlag: line?.oqcCheckFlag ?? false,
+            quantity: line?.quantity,
+          });
+        },
+      },
+      dependencies: {
+        triggerFields: ['noticeId'],
+        if: () => hasNotice,
+        componentProps: (values) => ({
+          noticeId: values.noticeId,
+        }),
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '浜у搧',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浜у搧',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['noticeLineId'],
+        componentProps: (values) => ({
+          disabled: !!values.noticeLineId,
+          placeholder: '璇烽�夋嫨浜у搧',
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍑哄簱鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'oqcCheckFlag',
+      label: '鏄惁鏍¢獙',
+      component: 'RadioGroup',
+      componentProps: {
+        buttonStyle: 'solid',
+        optionType: 'button',
+        options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmProductSalesDetailApi.ProductSalesDetail>['columns'] {
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'batchId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '閫夋嫨搴撳瓨',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            locationId: stock?.locationId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId', 'batchId'],
+        componentProps: (values) => ({
+          batchId: values.batchId,
+          itemId: values.itemId,
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ユ暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍑哄簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/productsales/index.vue b/src/views/wls/productsales/index.vue
new file mode 100644
index 0000000..39838a8
--- /dev/null
+++ b/src/views/wls/productsales/index.vue
@@ -0,0 +1,215 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductSalesStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelProductSales,
+  deleteProductSales,
+  exportProductSales,
+  getProductSalesPage,
+} from '#/api/mes/wm/productsales';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓閿�鍞嚭搴撳崟 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閿�鍞嚭搴撳崟 */
+function handleDetail(row: MesWmProductSalesApi.ProductSales) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閿�鍞嚭搴撳崟 */
+function handleEdit(row: MesWmProductSalesApi.ProductSales) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmProductSalesApi.ProductSales) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 濉啓杩愬崟 */
+function handleShipping(row: MesWmProductSalesApi.ProductSales) {
+  formModalApi.setData({ formType: 'shipping', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmProductSalesApi.ProductSales) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟 */
+async function handleDelete(row: MesWmProductSalesApi.ProductSales) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteProductSales(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷閿�鍞嚭搴撳崟 */
+async function handleCancel(row: MesWmProductSalesApi.ProductSales) {
+  await cancelProductSales(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportProductSales(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '閿�鍞嚭搴撳崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getProductSalesPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmProductSalesApi.ProductSales>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="閿�鍞嚭搴撳崟鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-product-sales:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-product-sales:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-product-sales:update'],
+              ifShow: row.status === MesWmProductSalesStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-product-sales:delete'],
+              ifShow: row.status === MesWmProductSalesStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎷h揣',
+              type: 'link',
+              auth: ['mes:wm-product-sales:stock'],
+              ifShow: row.status === MesWmProductSalesStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '濉啓杩愬崟',
+              type: 'link',
+              auth: ['mes:wm-product-sales:shipping'],
+              ifShow: row.status === MesWmProductSalesStatusEnum.SHIPPING,
+              onClick: handleShipping.bind(null, row),
+            },
+            {
+              label: '鎵ц鍑哄簱',
+              type: 'link',
+              auth: ['mes:wm-product-sales:finish'],
+              ifShow: row.status === MesWmProductSalesStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-product-sales:cancel'],
+              ifShow:
+                row.status === MesWmProductSalesStatusEnum.CONFIRMED ||
+                row.status === MesWmProductSalesStatusEnum.APPROVING ||
+                row.status === MesWmProductSalesStatusEnum.SHIPPING ||
+                row.status === MesWmProductSalesStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ラ攢鍞嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/productsales/modules/detail-form.vue b/src/views/wls/productsales/modules/detail-form.vue
new file mode 100644
index 0000000..53385dc
--- /dev/null
+++ b/src/views/wls/productsales/modules/detail-form.vue
@@ -0,0 +1,110 @@
+<script lang="ts" setup>
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductSalesDetail,
+  getProductSalesDetail,
+  updateProductSalesDetail,
+} from '#/api/mes/wm/productsales/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductSalesDetailApi.ProductSalesDetail>();
+const salesId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲嚭搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+    : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductSalesDetailApi.ProductSalesDetail;
+    data.salesId = salesId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductSalesDetail({ ...data, id: formData.value.id })
+        : createProductSalesDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      batchId?: number;
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      salesId: number;
+    }>();
+    salesId.value = data.salesId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductSalesDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else {
+      if (data.itemId) {
+        await formApi.setFieldValue('itemId', data.itemId);
+      }
+      if (data.batchId) {
+        await formApi.setFieldValue('batchId', data.batchId);
+      }
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/productsales/modules/detail-list.vue b/src/views/wls/productsales/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productsales/modules/detail-list.vue
copy to src/views/wls/productsales/modules/detail-list.vue
diff --git a/src/views/wls/productsales/modules/form.vue b/src/views/wls/productsales/modules/form.vue
new file mode 100644
index 0000000..853d7f4
--- /dev/null
+++ b/src/views/wls/productsales/modules/form.vue
@@ -0,0 +1,270 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductSalesStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  checkProductSalesQuantity,
+  createProductSales,
+  finishProductSales,
+  getProductSales,
+  shippingProductSales,
+  stockProductSales,
+  submitProductSales,
+  updateProductSales,
+} from '#/api/mes/wm/productsales';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductSalesApi.ProductSales>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isShipping = computed(() => formType.value === 'shipping'); // 鏄惁涓哄~鍐欒繍鍗曟ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmProductSalesStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['閿�鍞嚭搴撳崟']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц鎷h揣';
+  }
+  if (formType.value === 'shipping') {
+    return '濉啓杩愬崟';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍑哄簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['閿�鍞嚭搴撳崟'])
+    : $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍑哄簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+      await updateProductSales({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitProductSales(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鎷h揣锛氬嚭搴撴暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  const quantityMatch = await checkProductSalesQuantity(formData.value.id);
+  if (!quantityMatch) {
+    try {
+      await confirm('鍑哄簱鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+    } catch {
+      return;
+    }
+  }
+  modalApi.lock();
+  try {
+    await stockProductSales(formData.value.id);
+    message.success('鎷h揣鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 濉啓杩愬崟 */
+async function handleShipping() {
+  if (!formData.value?.id) {
+    return;
+  }
+  const values =
+    (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+  modalApi.lock();
+  try {
+    await shippingProductSales({
+      carrier: values.carrier,
+      id: formData.value.id,
+      shippingNumber: values.shippingNumber,
+    });
+    message.success('杩愬崟淇℃伅濉啓鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍑哄簱 */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishProductSales(formData.value.id);
+    message.success('鍑哄簱鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+    try {
+      if (formData.value?.id) {
+        await updateProductSales({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createProductSales(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmProductSalesStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(false);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getProductSales(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList
+          :form-type="formType"
+          :notice-id="formData.noticeId"
+          :sales-id="formData.id"
+        />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ攢鍞嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="handleStock">
+          鎵ц鎷h揣
+        </Button>
+        <Popconfirm
+          v-if="isShipping"
+          title="纭鎻愪氦杩愬崟淇℃伅锛�"
+          @confirm="handleShipping"
+        >
+          <Button type="primary">纭濉啓</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц鍑哄簱锛熸墽琛屽悗灏嗘墸鍑忓簱瀛樸��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">纭鍑哄簱</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/productsales/modules/line-form.vue b/src/views/wls/productsales/modules/line-form.vue
new file mode 100644
index 0000000..e28b2e6
--- /dev/null
+++ b/src/views/wls/productsales/modules/line-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createProductSalesLine,
+  getProductSalesLine,
+  updateProductSalesLine,
+} from '#/api/mes/wm/productsales/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductSalesLineApi.ProductSalesLine>();
+const salesId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+const noticeId = ref<number>(); // 鎵�灞炲彂璐ч�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['閿�鍞嚭搴撳崟琛�'])
+    : $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmProductSalesLineApi.ProductSalesLine;
+    data.salesId = salesId.value;
+    try {
+      await (formData.value?.id
+        ? updateProductSalesLine({ ...data, id: formData.value.id })
+        : createProductSalesLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      noticeId?: number;
+      salesId: number;
+    }>();
+    salesId.value = data.salesId;
+    noticeId.value = data.noticeId;
+    formApi.setState({ schema: useLineFormSchema(!!data.noticeId, formApi) });
+    if (data.noticeId) {
+      await formApi.setFieldValue('noticeId', data.noticeId);
+    }
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getProductSalesLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/productsales/modules/line-list.vue b/src/views/wls/productsales/modules/line-list.vue
new file mode 100644
index 0000000..5bc888a
--- /dev/null
+++ b/src/views/wls/productsales/modules/line-list.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductSalesDetailListByLineId } from '#/api/mes/wm/productsales/detail';
+import {
+  deleteProductSalesLine,
+  getProductSalesLinePage,
+} from '#/api/mes/wm/productsales/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  noticeId?: number;
+  salesId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+  Record<number, MesWmProductSalesDetailApi.ProductSalesDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi
+    .setData({ noticeId: props.noticeId, salesId: props.salesId })
+    .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductSalesLineApi.ProductSalesLine) {
+  lineFormModalApi
+    .setData({ id: row.id, noticeId: props.noticeId, salesId: props.salesId })
+    .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmProductSalesLineApi.ProductSalesLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteProductSalesLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmProductSalesLineApi.ProductSalesLine) {
+  openDetailForm(row.id!, row.itemId, row.batchId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(
+  lineId: number,
+  itemId?: number,
+  batchId?: number,
+  detailId?: number,
+) {
+  detailFormModalApi
+    .setData({ batchId, detailId, itemId, lineId, salesId: props.salesId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmProductSalesLineApi.ProductSalesLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getProductSalesDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+  row: MesWmProductSalesLineApi.ProductSalesLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.salesId) {
+            return { list: [], total: 0 };
+          }
+          return await getProductSalesLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            salesId: props.salesId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmProductSalesLineApi.ProductSalesLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmProductSalesLineApi.ProductSalesLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="
+            (detailId) =>
+              openDetailForm(row.id!, row.itemId, row.batchId, detailId)
+          "
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎷h揣',
+              type: 'link',
+              ifShow: isStock,
+              onClick: handlePicking.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/returnissue/data.ts b/src/views/wls/returnissue/data.ts
new file mode 100644
index 0000000..6dfc81f
--- /dev/null
+++ b/src/views/wls/returnissue/data.ts
@@ -0,0 +1,567 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佹墽琛岄��鏂欐�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '閫�鏂欏崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��鏂欏崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_RETURN_ISSUE_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫�鏂欏崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��鏂欏崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '閫�鏂欑被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨閫�鏂欑被鍨�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        status: MesProWorkOrderStatusEnum.CONFIRMED,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'workstationId',
+      label: '宸ヤ綔绔�',
+      component: markRaw(MdWorkstationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+      },
+    },
+    {
+      fieldName: 'returnDate',
+      label: '閫�鏂欐棩鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫�鏂欏崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��鏂欏崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫�鏂欏崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��鏂欏崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'workOrderId',
+      label: '鐢熶骇璁㈠崟',
+      component: markRaw(ProWorkOrderSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '閫�鏂欑被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨閫�鏂欑被鍨�',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '閫�鏂欏崟缂栧彿',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '閫�鏂欏崟鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'type',
+      title: '閫�鏂欑被鍨�',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE },
+      },
+    },
+    {
+      field: 'workOrderCode',
+      title: '鐢熶骇璁㈠崟',
+      minWidth: 140,
+    },
+    {
+      field: 'workstationName',
+      title: '宸ヤ綔绔�',
+      minWidth: 120,
+    },
+    {
+      field: 'returnDate',
+      title: '閫�鏂欐棩鏈�',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫�鏂欏崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '閫�鏂欐暟閲�',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'rqcCheckFlag',
+      title: '鏄惁妫�娴�',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'qualityStatus',
+      title: '璐ㄩ噺鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+      },
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 200,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 閫�鏂欏崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            itemId: stock?.itemId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+          });
+        },
+        virtualFilter: 'only',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '閫�鏂欐暟閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'rqcCheckFlag',
+      label: '闇�瑕佽川妫�',
+      component: 'Switch',
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+        placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnIssueDetailApi.ReturnIssueDetail>['columns'] {
+  return [
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            locationId: stock?.locationId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+        virtualFilter: 'only',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+          virtualFilter: 'only',
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ユ暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍏ュ簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/returnissue/index.vue b/src/views/wls/returnissue/index.vue
new file mode 100644
index 0000000..29463eb
--- /dev/null
+++ b/src/views/wls/returnissue/index.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelReturnIssue,
+  deleteReturnIssue,
+  exportReturnIssue,
+  getReturnIssuePage,
+} from '#/api/mes/wm/returnissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鐢熶骇閫�鏂欏崟 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐢熶骇閫�鏂欏崟 */
+function handleDetail(row: MesWmReturnIssueApi.ReturnIssue) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐢熶骇閫�鏂欏崟 */
+function handleEdit(row: MesWmReturnIssueApi.ReturnIssue) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmReturnIssueApi.ReturnIssue) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц閫�鏂� */
+function handleFinish(row: MesWmReturnIssueApi.ReturnIssue) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+  message.info('璇峰墠寰�銆愯川閲忕鐞� - 閫�璐ф楠岋紙RQC锛夈�戜腑杩涜閫�鏂欐楠屾搷浣�');
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟 */
+async function handleDelete(row: MesWmReturnIssueApi.ReturnIssue) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnIssue(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷鐢熶骇閫�鏂欏崟 */
+async function handleCancel(row: MesWmReturnIssueApi.ReturnIssue) {
+  await cancelReturnIssue(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportReturnIssue(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鐢熶骇閫�鏂欏崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getReturnIssuePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鐢熶骇閫�鏂欏崟鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-return-issue:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-return-issue:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-return-issue:update'],
+              ifShow: row.status === MesWmReturnIssueStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-return-issue:delete'],
+              ifShow: row.status === MesWmReturnIssueStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц璐ㄦ',
+              type: 'link',
+              ifShow: row.status === MesWmReturnIssueStatusEnum.CONFIRMED,
+              onClick: handleQc,
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-return-issue:update'],
+              ifShow: row.status === MesWmReturnIssueStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鎵ц閫�鏂�',
+              type: 'link',
+              auth: ['mes:wm-return-issue:finish'],
+              ifShow: row.status === MesWmReturnIssueStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-return-issue:update'],
+              ifShow:
+                row.status === MesWmReturnIssueStatusEnum.CONFIRMED ||
+                row.status === MesWmReturnIssueStatusEnum.APPROVING ||
+                row.status === MesWmReturnIssueStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ョ敓浜ч��鏂欏崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/returnissue/modules/detail-form.vue b/src/views/wls/returnissue/modules/detail-form.vue
new file mode 100644
index 0000000..7539d65
--- /dev/null
+++ b/src/views/wls/returnissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnIssueDetail,
+  getReturnIssueDetail,
+  updateReturnIssueDetail,
+} from '#/api/mes/wm/returnissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnIssueDetailApi.ReturnIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞為��鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+    : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnIssueDetailApi.ReturnIssueDetail;
+    data.issueId = issueId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnIssueDetail({ ...data, id: formData.value.id })
+        : createReturnIssueDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      issueId: number;
+      itemId?: number;
+      lineId: number;
+    }>();
+    issueId.value = data.issueId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnIssueDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/returnissue/modules/detail-list.vue b/src/views/wls/returnissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnissue/modules/detail-list.vue
copy to src/views/wls/returnissue/modules/detail-list.vue
diff --git a/src/views/wls/returnissue/modules/form.vue b/src/views/wls/returnissue/modules/form.vue
new file mode 100644
index 0000000..341d637
--- /dev/null
+++ b/src/views/wls/returnissue/modules/form.vue
@@ -0,0 +1,222 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnIssue,
+  finishReturnIssue,
+  getReturnIssue,
+  stockReturnIssue,
+  submitReturnIssue,
+  updateReturnIssue,
+} from '#/api/mes/wm/returnissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnIssueApi.ReturnIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓哄叆搴撲笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄��鏂欐ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmReturnIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鐢熶骇閫�鏂欏崟']);
+  }
+  if (formType.value === 'stock') {
+    return '鍏ュ簱涓婃灦';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц閫�鏂�';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鐢熶骇閫�鏂欏崟'])
+    : $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�鏂欏崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+      await updateReturnIssue({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitReturnIssue(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鍏ュ簱涓婃灦 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await stockReturnIssue(formData.value.id);
+    message.success('鍏ュ簱涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц閫�鏂� */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishReturnIssue(formData.value.id);
+    message.success('瀹屾垚鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+    try {
+      if (formData.value?.id) {
+        await updateReturnIssue({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createReturnIssue(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmReturnIssueStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnIssue(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :issue-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ��鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="handleStock">
+          鍏ュ簱涓婃灦
+        </Button>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭瀹屾垚璇ラ��鏂欏崟骞舵墽琛屽叆搴撳悧锛�"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц閫�鏂�</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/returnissue/modules/line-form.vue b/src/views/wls/returnissue/modules/line-form.vue
new file mode 100644
index 0000000..35f2d8e
--- /dev/null
+++ b/src/views/wls/returnissue/modules/line-form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnIssueLine,
+  getReturnIssueLine,
+  updateReturnIssueLine,
+} from '#/api/mes/wm/returnissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnIssueLineApi.ReturnIssueLine>();
+const issueId = ref<number>(); // 鎵�灞為��鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐢熶骇閫�鏂欏崟琛�'])
+    : $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnIssueLineApi.ReturnIssueLine;
+    data.issueId = issueId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnIssueLine({ ...data, id: formData.value.id })
+        : createReturnIssueLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useLineFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; issueId: number }>();
+    issueId.value = data.issueId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getReturnIssueLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/returnissue/modules/line-list.vue b/src/views/wls/returnissue/modules/line-list.vue
new file mode 100644
index 0000000..455e03a
--- /dev/null
+++ b/src/views/wls/returnissue/modules/line-list.vue
@@ -0,0 +1,250 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, reactive, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnIssueDetailListByLineId } from '#/api/mes/wm/returnissue/detail';
+import {
+  deleteReturnIssueLine,
+  getReturnIssueLinePage,
+} from '#/api/mes/wm/returnissue/line';
+import { $t } from '#/locales';
+import { BarcodeDetail, PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  issueId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓哄叆搴撲笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmReturnIssueDetailApi.ReturnIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+const barcodeDetailRef = ref(); // 鏉$爜璇︽儏寮圭獥瀹炰緥
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+  lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnIssueLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鏌ョ湅鐗╂枡鏉$爜 */
+function handleBarcode(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+  barcodeDetailRef.value?.openByBusiness(
+    row.itemId,
+    BarcodeBizTypeEnum.ITEM,
+    row.itemCode,
+    row.itemName,
+  );
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, issueId: props.issueId, itemId, lineId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getReturnIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+  row: MesWmReturnIssueLineApi.ReturnIssueLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.issueId) {
+            return { list: [], total: 0 };
+          }
+          return await getReturnIssueLinePage({
+            issueId: props.issueId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmReturnIssueLineApi.ReturnIssueLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '涓婃灦',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handlePicking.bind(null, row),
+              },
+              {
+                label: '鏉$爜',
+                type: 'link',
+                onClick: handleBarcode.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </div>
+</template>
diff --git a/src/views/wls/returnsales/data.ts b/src/views/wls/returnsales/data.ts
new file mode 100644
index 0000000..b7b99db
--- /dev/null
+++ b/src/views/wls/returnsales/data.ts
@@ -0,0 +1,515 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佹墽琛岄��璐ф�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '閫�璐у崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_RETURN_SALES_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫�璐у崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'returnDate',
+      label: '閫�璐ф棩鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'returnReason',
+      label: '閫�璐у師鍥�',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у師鍥�',
+        rows: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫�璐у崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫�璐у崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_RETURN_SALES_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnSalesApi.ReturnSales>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '閫�璐у崟缂栧彿',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '閫�璐у崟鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曞彿',
+      minWidth: 140,
+    },
+    {
+      field: 'clientCode',
+      title: '瀹㈡埛缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'returnReason',
+      title: '閫�璐у師鍥�',
+      minWidth: 150,
+    },
+    {
+      field: 'returnDate',
+      title: '閫�璐ф棩鏈�',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_RETURN_SALES_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫�璐у崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnSalesLineApi.ReturnSalesLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '閫�璐ф暟閲�',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'rqcCheckFlag',
+      title: '鏄惁闇�瑕佽川妫�',
+      width: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'qualityStatus',
+      title: '璐ㄩ噺鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+      },
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 閫�璐у崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+  clientId?: number,
+  salesOrderCode?: string,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '閫�璐ф暟閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ラ��璐ф暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchId',
+      label: '鎵规',
+      component: markRaw(WmBatchSelect),
+      componentProps: {
+        clientId,
+        placeholder: '璇烽�夋嫨鎵规',
+        salesOrderCode,
+      },
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          clientId,
+          itemId: values.itemId,
+          placeholder: '璇烽�夋嫨鎵规',
+          salesOrderCode,
+        }),
+      },
+    },
+    {
+      fieldName: 'rqcCheckFlag',
+      label: '闇�瑕佽川妫�',
+      component: 'Switch',
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnSalesDetailApi.ReturnSalesDetail>['columns'] {
+  return [
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鍏ュ簱浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+        onChange: async () => {
+          await formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          });
+        },
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+          onChange: async () => {
+            await formApi?.setFieldValue('areaId', undefined);
+          },
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+  ];
+}
diff --git a/src/views/wls/returnsales/index.vue b/src/views/wls/returnsales/index.vue
new file mode 100644
index 0000000..128d5b8
--- /dev/null
+++ b/src/views/wls/returnsales/index.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnSalesStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelReturnSales,
+  deleteReturnSales,
+  exportReturnSales,
+  getReturnSalesPage,
+} from '#/api/mes/wm/returnsales';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓閿�鍞��璐у崟 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閿�鍞��璐у崟 */
+function handleDetail(row: MesWmReturnSalesApi.ReturnSales) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閿�鍞��璐у崟 */
+function handleEdit(row: MesWmReturnSalesApi.ReturnSales) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmReturnSalesApi.ReturnSales) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц閫�璐� */
+function handleFinish(row: MesWmReturnSalesApi.ReturnSales) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+  message.info('璇峰墠寰�銆愯川閲忕鐞� - 閫�璐ф楠岋紙RQC锛夈�戜腑杩涜閫�璐ф楠屾搷浣�');
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟 */
+async function handleDelete(row: MesWmReturnSalesApi.ReturnSales) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnSales(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷閿�鍞��璐у崟 */
+async function handleCancel(row: MesWmReturnSalesApi.ReturnSales) {
+  await cancelReturnSales(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportReturnSales(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '閿�鍞��璐у崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getReturnSalesPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnSalesApi.ReturnSales>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="閿�鍞��璐у崟鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['閿�鍞��璐у崟']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-return-sales:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-return-sales:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-return-sales:update'],
+              ifShow: row.status === MesWmReturnSalesStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-return-sales:delete'],
+              ifShow: row.status === MesWmReturnSalesStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц璐ㄦ',
+              type: 'link',
+              ifShow: row.status === MesWmReturnSalesStatusEnum.CONFIRMED,
+              onClick: handleQc,
+            },
+            {
+              label: '鎵ц閫�璐�',
+              type: 'link',
+              auth: ['mes:wm-return-sales:finish'],
+              ifShow: row.status === MesWmReturnSalesStatusEnum.APPROVING,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-return-sales:stock'],
+              ifShow: row.status === MesWmReturnSalesStatusEnum.APPROVED,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-return-sales:cancel'],
+              ifShow:
+                row.status === MesWmReturnSalesStatusEnum.CONFIRMED ||
+                row.status === MesWmReturnSalesStatusEnum.APPROVING ||
+                row.status === MesWmReturnSalesStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ラ攢鍞��璐у崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/returnsales/modules/detail-form.vue b/src/views/wls/returnsales/modules/detail-form.vue
new file mode 100644
index 0000000..ec451a2
--- /dev/null
+++ b/src/views/wls/returnsales/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnSalesDetail,
+  getReturnSalesDetail,
+  updateReturnSalesDetail,
+} from '#/api/mes/wm/returnsales/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnSalesDetailApi.ReturnSalesDetail>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��璐у崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+    : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnSalesDetailApi.ReturnSalesDetail;
+    data.returnId = returnId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnSalesDetail({ ...data, id: formData.value.id })
+        : createReturnSalesDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      returnId: number;
+    }>();
+    returnId.value = data.returnId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnSalesDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/returnsales/modules/detail-list.vue b/src/views/wls/returnsales/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnsales/modules/detail-list.vue
copy to src/views/wls/returnsales/modules/detail-list.vue
diff --git a/src/views/wls/returnsales/modules/form.vue b/src/views/wls/returnsales/modules/form.vue
new file mode 100644
index 0000000..f43296b
--- /dev/null
+++ b/src/views/wls/returnsales/modules/form.vue
@@ -0,0 +1,231 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnSalesStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnSales,
+  finishReturnSales,
+  getReturnSales,
+  stockReturnSales,
+  submitReturnSales,
+  updateReturnSales,
+} from '#/api/mes/wm/returnsales';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnSalesApi.ReturnSales>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄��璐фā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmReturnSalesStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['閿�鍞��璐у崟']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц涓婃灦';
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц閫�璐�';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['閿�鍞��璐у崟'])
+    : $t('ui.actionTitle.create', ['閿�鍞��璐у崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�璐у崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmReturnSalesApi.ReturnSales;
+      await updateReturnSales({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitReturnSales(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await stockReturnSales(formData.value.id);
+    message.success('涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц閫�璐� */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishReturnSales(formData.value.id);
+    message.success('鎵ц閫�璐ф垚鍔�');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmReturnSalesApi.ReturnSales;
+    try {
+      if (formData.value?.id) {
+        await updateReturnSales({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createReturnSales(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmReturnSalesStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnSales(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList
+          :client-id="formData.clientId"
+          :form-type="formType"
+          :return-id="formData.id"
+          :sales-order-code="formData.salesOrderCode"
+        />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ攢鍞��璐у崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isStock"
+          title="纭鎵ц涓婃灦锛�"
+          @confirm="handleStock"
+        >
+          <Button type="primary">鎵ц涓婃灦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц閫�璐э紵鎵ц鍚庡皢杩涘叆寰呬笂鏋剁姸鎬併��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц閫�璐�</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/returnsales/modules/line-form.vue b/src/views/wls/returnsales/modules/line-form.vue
new file mode 100644
index 0000000..bde39cb
--- /dev/null
+++ b/src/views/wls/returnsales/modules/line-form.vue
@@ -0,0 +1,102 @@
+<script lang="ts" setup>
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnSalesLine,
+  getReturnSalesLine,
+  updateReturnSalesLine,
+} from '#/api/mes/wm/returnsales/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnSalesLineApi.ReturnSalesLine>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['閿�鍞��璐у崟琛�'])
+    : $t('ui.actionTitle.create', ['閿�鍞��璐у崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnSalesLineApi.ReturnSalesLine;
+    data.returnId = returnId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnSalesLine({ ...data, id: formData.value.id })
+        : createReturnSalesLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      clientId?: number;
+      id?: number;
+      returnId: number;
+      salesOrderCode?: string;
+    }>();
+    returnId.value = data.returnId;
+    formApi.setState({
+      schema: useLineFormSchema(data.clientId, data.salesOrderCode),
+    });
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getReturnSalesLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/returnsales/modules/line-list.vue b/src/views/wls/returnsales/modules/line-list.vue
new file mode 100644
index 0000000..e9b9476
--- /dev/null
+++ b/src/views/wls/returnsales/modules/line-list.vue
@@ -0,0 +1,247 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnSalesDetailListByLineId } from '#/api/mes/wm/returnsales/detail';
+import {
+  deleteReturnSalesLine,
+  getReturnSalesLinePage,
+} from '#/api/mes/wm/returnsales/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  clientId?: number;
+  formType: FormType;
+  returnId: number;
+  salesOrderCode?: string;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmReturnSalesDetailApi.ReturnSalesDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi
+    .setData({
+      clientId: props.clientId,
+      returnId: props.returnId,
+      salesOrderCode: props.salesOrderCode,
+    })
+    .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+  lineFormModalApi
+    .setData({
+      clientId: props.clientId,
+      id: row.id,
+      returnId: props.returnId,
+      salesOrderCode: props.salesOrderCode,
+    })
+    .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnSalesLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, returnId: props.returnId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getReturnSalesDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+  row: MesWmReturnSalesLineApi.ReturnSalesLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.returnId) {
+            return { list: [], total: 0 };
+          }
+          return await getReturnSalesLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            returnId: props.returnId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnSalesLineApi.ReturnSalesLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmReturnSalesLineApi.ReturnSalesLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '涓婃灦',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handleStock.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/returnvendor/data.ts b/src/views/wls/returnvendor/data.ts
new file mode 100644
index 0000000..6844970
--- /dev/null
+++ b/src/views/wls/returnvendor/data.ts
@@ -0,0 +1,530 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '閫�璐у崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_RETURN_VENDOR_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫�璐у崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'returnDate',
+      label: '閫�璐ф棩鏈�',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD HH:mm:ss',
+        placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'returnReason',
+      label: '閫�璐у師鍥�',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ��璐у師鍥�',
+        rows: 2,
+      },
+    },
+    {
+      fieldName: 'transportCode',
+      label: '杩愬崟鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ繍鍗曞彿',
+      },
+    },
+    {
+      fieldName: 'transportTelephone',
+      label: '鑱旂郴鐢佃瘽',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫�璐у崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫�璐у崟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'purchaseOrderCode',
+      label: '閲囪喘璁㈠崟缂栧彿',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnVendorApi.ReturnVendor>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '閫�璐у崟缂栧彿',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '閫�璐у崟鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'purchaseOrderCode',
+      title: '閲囪喘璁㈠崟鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'vendorCode',
+      title: '渚涘簲鍟嗙紪鐮�',
+      minWidth: 120,
+    },
+    {
+      field: 'vendorName',
+      title: '渚涘簲鍟嗗悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'returnDate',
+      title: '閫�璐ф棩鏈�',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_RETURN_VENDOR_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫�璐у崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnVendorLineApi.ReturnVendorLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '閫�璐ф暟閲�',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 閫�璐у崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+  vendorId?: number,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '浜у搧鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '閫�璐ф暟閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ラ��璐ф暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchId',
+      label: '鎵规鍙�',
+      component: markRaw(WmBatchSelect),
+      componentProps: {
+        // 閫夋嫨鎵规鍚庡悓姝ユ壒娆″彿
+        onChange: async (batch?: { code?: string }) => {
+          await formApi?.setFieldValue('batchCode', batch?.code);
+        },
+        vendorId,
+      },
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+          vendorId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmReturnVendorDetailApi.ReturnVendorDetail>['columns'] {
+  return [
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '搴撳瓨璁板綍',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            areaId: stock?.areaId,
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            locationId: stock?.locationId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+            warehouseId: stock?.warehouseId,
+          });
+        },
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['itemId'],
+        componentProps: (values) => ({
+          itemId: values.itemId,
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ユ暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.locationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/returnvendor/index.vue b/src/views/wls/returnvendor/index.vue
new file mode 100644
index 0000000..01b9dcb
--- /dev/null
+++ b/src/views/wls/returnvendor/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnVendorStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelReturnVendor,
+  deleteReturnVendor,
+  exportReturnVendor,
+  getReturnVendorPage,
+} from '#/api/mes/wm/returnvendor';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓渚涘簲鍟嗛��璐у崟 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅渚涘簲鍟嗛��璐у崟 */
+function handleDetail(row: MesWmReturnVendorApi.ReturnVendor) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫渚涘簲鍟嗛��璐у崟 */
+function handleEdit(row: MesWmReturnVendorApi.ReturnVendor) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmReturnVendorApi.ReturnVendor) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚閫�璐� */
+function handleFinish(row: MesWmReturnVendorApi.ReturnVendor) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟 */
+async function handleDelete(row: MesWmReturnVendorApi.ReturnVendor) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnVendor(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷渚涘簲鍟嗛��璐у崟 */
+async function handleCancel(row: MesWmReturnVendorApi.ReturnVendor) {
+  await cancelReturnVendor(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportReturnVendor(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '渚涘簲鍟嗛��璐у崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getReturnVendorPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnVendorApi.ReturnVendor>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="渚涘簲鍟嗛��璐у崟鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-return-vendor:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-return-vendor:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-return-vendor:update'],
+              ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-return-vendor:delete'],
+              ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鎷h揣',
+              type: 'link',
+              auth: ['mes:wm-return-vendor:update'],
+              ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '瀹屾垚閫�璐�',
+              type: 'link',
+              auth: ['mes:wm-return-vendor:update-status'],
+              ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-return-vendor:update'],
+              ifShow:
+                row.status === MesWmReturnVendorStatusEnum.APPROVING ||
+                row.status === MesWmReturnVendorStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ヤ緵搴斿晢閫�璐у崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/returnvendor/modules/detail-form.vue b/src/views/wls/returnvendor/modules/detail-form.vue
new file mode 100644
index 0000000..9e07274
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnVendorDetail,
+  getReturnVendorDetail,
+  updateReturnVendorDetail,
+} from '#/api/mes/wm/returnvendor/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnVendorDetailApi.ReturnVendorDetail>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��璐у崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+    : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnVendorDetailApi.ReturnVendorDetail;
+    data.returnId = returnId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnVendorDetail({ ...data, id: formData.value.id })
+        : createReturnVendorDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useDetailFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      returnId: number;
+    }>();
+    returnId.value = data.returnId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnVendorDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/returnvendor/modules/detail-list.vue b/src/views/wls/returnvendor/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnvendor/modules/detail-list.vue
copy to src/views/wls/returnvendor/modules/detail-list.vue
diff --git a/src/views/wls/returnvendor/modules/form.vue b/src/views/wls/returnvendor/modules/form.vue
new file mode 100644
index 0000000..ace9da0
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/form.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnVendorStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  checkReturnVendorQuantity,
+  createReturnVendor,
+  finishReturnVendor,
+  getReturnVendor,
+  stockReturnVendor,
+  submitReturnVendor,
+  updateReturnVendor,
+} from '#/api/mes/wm/returnvendor';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnVendorApi.ReturnVendor>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愰��璐фā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmReturnVendorStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['渚涘簲鍟嗛��璐у崟']);
+  }
+  if (formType.value === 'stock') {
+    return '鎵ц鎷h揣';
+  }
+  if (formType.value === 'finish') {
+    return '瀹屾垚閫�璐�';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['渚涘簲鍟嗛��璐у崟'])
+    : $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�璐у崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmReturnVendorApi.ReturnVendor;
+      await updateReturnVendor({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitReturnVendor(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鎷h揣锛氭嫞璐ф暟閲忎笌閫�璐ф暟閲忎笉涓�鑷存椂浜屾纭 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  const quantityMatch = await checkReturnVendorQuantity(formData.value.id);
+  if (!quantityMatch) {
+    try {
+      await confirm('閫�璐ф暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷达紝纭鎵ц鎷h揣锛�');
+    } catch {
+      return;
+    }
+  }
+  modalApi.lock();
+  try {
+    await stockReturnVendor(formData.value.id);
+    message.success('鎷h揣鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 瀹屾垚閫�璐� */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishReturnVendor(formData.value.id);
+    message.success('瀹屾垚鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnVendorApi.ReturnVendor;
+    try {
+      if (formData.value?.id) {
+        await updateReturnVendor({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createReturnVendor(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmReturnVendorStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getReturnVendor(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList
+          :form-type="formType"
+          :return-id="formData.id"
+          :vendor-id="formData.vendorId"
+        />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ラ��璐у崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isStock" type="primary" @click="handleStock">
+          鎵ц鎷h揣
+        </Button>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭瀹屾垚璇ラ��璐у崟骞舵墽琛岄��璐э紵"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">瀹屾垚閫�璐�</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/returnvendor/modules/line-form.vue b/src/views/wls/returnvendor/modules/line-form.vue
new file mode 100644
index 0000000..db2307a
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/line-form.vue
@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createReturnVendorLine,
+  getReturnVendorLine,
+  updateReturnVendorLine,
+} from '#/api/mes/wm/returnvendor/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnVendorLineApi.ReturnVendorLine>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['渚涘簲鍟嗛��璐у崟琛�'])
+    : $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmReturnVendorLineApi.ReturnVendorLine;
+    data.returnId = returnId.value;
+    try {
+      await (formData.value?.id
+        ? updateReturnVendorLine({ ...data, id: formData.value.id })
+        : createReturnVendorLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      id?: number;
+      returnId: number;
+      vendorId?: number;
+    }>();
+    returnId.value = data.returnId;
+    formApi.setState({ schema: useLineFormSchema(data.vendorId, formApi) });
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getReturnVendorLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/returnvendor/modules/line-list.vue b/src/views/wls/returnvendor/modules/line-list.vue
new file mode 100644
index 0000000..9822d7b
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/line-list.vue
@@ -0,0 +1,237 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnVendorDetailListByLineId } from '#/api/mes/wm/returnvendor/detail';
+import {
+  deleteReturnVendorLine,
+  getReturnVendorLinePage,
+} from '#/api/mes/wm/returnvendor/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  returnId: number;
+  vendorId?: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+  Record<number, MesWmReturnVendorDetailApi.ReturnVendorDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi
+    .setData({ returnId: props.returnId, vendorId: props.vendorId })
+    .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+  lineFormModalApi
+    .setData({ id: row.id, returnId: props.returnId, vendorId: props.vendorId })
+    .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteReturnVendorLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, returnId: props.returnId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getReturnVendorDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+  row: MesWmReturnVendorLineApi.ReturnVendorLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.returnId) {
+            return { list: [], total: 0 };
+          }
+          return await getReturnVendorLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            returnId: props.returnId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmReturnVendorLineApi.ReturnVendorLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmReturnVendorLineApi.ReturnVendorLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <div class="flex items-center justify-center">
+          <TableAction
+            :actions="[
+              {
+                label: $t('common.edit'),
+                type: 'link',
+                icon: ACTION_ICON.EDIT,
+                ifShow: isEditable,
+                onClick: handleEdit.bind(null, row),
+              },
+              {
+                label: $t('common.delete'),
+                type: 'link',
+                danger: true,
+                icon: ACTION_ICON.DELETE,
+                ifShow: isEditable,
+                popConfirm: {
+                  title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                  confirm: handleDelete.bind(null, row),
+                },
+              },
+              {
+                label: '鎷h揣',
+                type: 'link',
+                ifShow: isStock,
+                onClick: handlePicking.bind(null, row),
+              },
+            ]"
+          />
+          <PrinterLabel
+            v-if="isStock"
+            :biz-code="row.batchCode"
+            :biz-id="row.batchId"
+            biz-type="BATCH"
+          />
+        </div>
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/mes/wm/salesnotice/components/index.ts b/src/views/wls/salesnotice/components/index.ts
similarity index 100%
copy from src/views/mes/wm/salesnotice/components/index.ts
copy to src/views/wls/salesnotice/components/index.ts
diff --git a/src/views/wls/salesnotice/components/line-select-dialog.vue b/src/views/wls/salesnotice/components/line-select-dialog.vue
new file mode 100644
index 0000000..39c1058
--- /dev/null
+++ b/src/views/wls/salesnotice/components/line-select-dialog.vue
@@ -0,0 +1,198 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSalesNoticeLinePage } from '#/api/mes/wm/salesnotice/line';
+
+const emit = defineEmits<{
+  selected: [rows: MesWmSalesNoticeLineApi.SalesNoticeLine[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+const syncingSingleSelection = ref(false); // 鏄惁鍚屾鍗曢�夊嬀閫夌姸鎬�
+const selectedRows = ref<MesWmSalesNoticeLineApi.SalesNoticeLine[]>([]); // 宸查�夎鍒楄〃
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎缂栧彿鍒楄〃
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(): VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>['columns'] {
+  return [
+    {
+      type: 'checkbox',
+      width: 50,
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍙戣揣鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'oqcCheckFlag',
+      title: '鏄惁妫�楠�',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+  ];
+}
+
+/** 鍗曢�夋ā寮忎笅鍚屾 VXE 鍕鹃�夌姸鎬� */
+async function syncSingleSelection(
+  row?: MesWmSalesNoticeLineApi.SalesNoticeLine,
+) {
+  syncingSingleSelection.value = true;
+  await nextTick();
+  await gridApi.grid.clearCheckboxRow();
+  if (row) {
+    await gridApi.grid.setCheckboxRow(row, true);
+  }
+  await nextTick();
+  syncingSingleSelection.value = false;
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖栵紝鍙繚鐣欐渶鍚庝竴鏉� */
+async function handleCheckboxChange({
+  checked,
+  row,
+}: {
+  checked: boolean;
+  row?: MesWmSalesNoticeLineApi.SalesNoticeLine;
+}) {
+  if (syncingSingleSelection.value) {
+    return;
+  }
+  const selected = checked && row ? [row] : [];
+  selectedRows.value = selected;
+  await syncSingleSelection(selected[0]);
+}
+
+/** 鍥炴樉棰勯�夎 */
+function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows =
+    gridApi.grid.getData() as MesWmSalesNoticeLineApi.SalesNoticeLine[];
+  for (const row of rows) {
+    if (row.id && preSelectedIds.value.includes(row.id)) {
+      gridApi.grid.setCheckboxRow(row, true);
+      selectedRows.value = [row];
+    }
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 460,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      reserve: true,
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!noticeId.value) {
+            return { list: [], total: 0 };
+          }
+          return await getSalesNoticeLinePage({
+            noticeId: noticeId.value,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>,
+  gridEvents: {
+    checkboxChange: handleCheckboxChange,
+  },
+});
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+async function openModal(id: number | undefined, selectedIds?: number[]) {
+  open.value = true;
+  noticeId.value = id;
+  preSelectedIds.value = selectedIds || [];
+  selectedRows.value = [];
+  await nextTick();
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.query();
+  await nextTick();
+  applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+}
+
+/** 纭閫夋嫨琛� */
+function handleConfirm() {
+  if (selectedRows.value.length === 0) {
+    message.warning('璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', [selectedRows.value[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="鍙戣揣閫氱煡鍗曡閫夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鍙戣揣閫氱煡鍗曡鍒楄〃" />
+  </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/components/line-select.vue b/src/views/wls/salesnotice/components/line-select.vue
new file mode 100644
index 0000000..c24d6a0
--- /dev/null
+++ b/src/views/wls/salesnotice/components/line-select.vue
@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getSalesNoticeLine } from '#/api/mes/wm/salesnotice/line';
+
+import WmSalesNoticeLineSelectDialog from './line-select-dialog.vue';
+
+defineOptions({ name: 'WmSalesNoticeLineSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    noticeId?: number; // 鎵�灞炲彂璐ч�氱煡鍗曠紪鍙�
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    noticeId: undefined,
+    placeholder: '璇烽�夋嫨鍙戣揣閫氱煡鍗曡',
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmSalesNoticeLineApi.SalesNoticeLine | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmSalesNoticeLineSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmSalesNoticeLineApi.SalesNoticeLine>();
+
+const displayLabel = computed(() => {
+  const item = selectedItem.value;
+  if (!item) {
+    return '';
+  }
+  return `${item.itemCode ?? ''} - ${item.itemName ?? ''}`;
+});
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ琛屼俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getSalesNoticeLine(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** noticeId 鍙樺寲鏃舵竻绌洪�変腑锛堝叧鑱旂殑琛屽凡澶辨晥锛� */
+watch(
+  () => props.noticeId,
+  () => {
+    selectedItem.value = undefined;
+    emit('update:modelValue', undefined);
+    emit('change', undefined);
+  },
+);
+
+/** 娓呯┖宸查�夎 */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled || !props.noticeId) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(props.noticeId, selectedIds);
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmSalesNoticeLineApi.SalesNoticeLine[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+          <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+          <div>瑙勬牸鍨嬪彿锛歿{ selectedItem.specification || '-' }}</div>
+          <div>鍙戣揣鏁伴噺锛歿{ selectedItem.quantity ?? '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmSalesNoticeLineSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/salesnotice/components/select-dialog.vue b/src/views/wls/salesnotice/components/select-dialog.vue
new file mode 100644
index 0000000..9ed3377
--- /dev/null
+++ b/src/views/wls/salesnotice/components/select-dialog.vue
@@ -0,0 +1,320 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { markRaw, nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSalesNoticePage } from '#/api/mes/wm/salesnotice';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+
+const emit = defineEmits<{
+  selected: [rows: MesWmSalesNoticeApi.SalesNotice[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(false); // 鏄惁澶氶��
+const fixedStatus = ref<number>(); // 鍥哄畾鐘舵�佺瓫閫�
+const selectedRows = ref<MesWmSalesNoticeApi.SalesNotice[]>([]); // 宸查�夐�氱煡鍗曞垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夐�氱煡鍗曠紪鍙峰垪琛�
+
+/** 鎼滅储琛ㄥ崟锛氭湭鍥哄畾鐘舵�佹椂灞曠ず鐘舵�佷笅鎷夛紝鍥哄畾鐘舵�佹椂闅愯棌 */
+function useSearchSchema(hasFixedStatus: boolean): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+    ...(hasFixedStatus
+      ? []
+      : [
+          {
+            fieldName: 'status',
+            label: '鍗曟嵁鐘舵��',
+            component: 'Select',
+            componentProps: {
+              allowClear: true,
+              options: getDictOptions(
+                DICT_TYPE.MES_WM_SALES_NOTICE_STATUS,
+                'number',
+              ),
+              placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+            },
+          } as VbenFormSchema,
+        ]),
+  ];
+}
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(
+  multipleSelect = false,
+): VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>['columns'] {
+  return [
+    {
+      type: multipleSelect ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'code',
+      title: '閫氱煡鍗曠紪鍙�',
+      minWidth: 160,
+    },
+    {
+      field: 'name',
+      title: '閫氱煡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'salesDate',
+      title: '鍙戣揣鏃ユ湡',
+      width: 120,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_SALES_NOTICE_STATUS },
+      },
+    },
+  ];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, MesWmSalesNoticeApi.SalesNotice>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as MesWmSalesNoticeApi.SalesNotice[];
+  records.forEach((row) => {
+    const rowId = row.id;
+    if (rowId !== undefined) {
+      selectedMap.set(rowId, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊澶氶�夊嬀閫夊彉鍖� */
+function handleCheckboxSelectChange() {
+  if (!multiple.value) {
+    return;
+  }
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊垏鎹� */
+function handleRadioChange(row: MesWmSalesNoticeApi.SalesNotice) {
+  selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmSalesNoticeApi.SalesNotice) {
+  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+  await gridApi.grid.setCheckboxRow(row, !selected);
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紝澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+  row,
+}: {
+  row: MesWmSalesNoticeApi.SalesNotice;
+}) {
+  if (multiple.value) {
+    await toggleMultipleRow(row);
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐�氱煡鍗� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = gridApi.grid.getData() as MesWmSalesNoticeApi.SalesNotice[];
+  for (const row of rows) {
+    if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
+      continue;
+    }
+    if (multiple.value) {
+      await gridApi.grid.setCheckboxRow(row, true);
+    } else {
+      await gridApi.grid.setRadioRow(row);
+      selectedRows.value = [row];
+      return;
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useSearchSchema(false),
+  },
+  gridOptions: {
+    columns: useGridColumns(false),
+    height: 520,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      range: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getSalesNoticePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+            // 鍥哄畾鐘舵�佷紭鍏堬紝鏈浐瀹氭椂鐢ㄦ悳绱㈣〃鍗曢噷鐨勭姸鎬�
+            status: fixedStatus.value ?? formValues.status,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: MesWmSalesNoticeApi.SalesNotice }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean; status?: number },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? false;
+  fixedStatus.value = options?.status;
+  preSelectedIds.value = selectedIds || [];
+  // 鍥哄畾鐘舵�佹椂闅愯棌鐘舵�佹悳绱㈤」锛屾湭鍥哄畾鏃跺睍绀�
+  gridApi.formApi.setState({
+    schema: useSearchSchema(fixedStatus.value !== undefined),
+  });
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴閫氱煡鍗曢�夋嫨寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  await resetQueryState();
+}
+
+/** 纭閫夋嫨閫氱煡鍗� */
+function handleConfirm() {
+  if (selectedRows.value.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit(
+    'selected',
+    multiple.value ? selectedRows.value : [selectedRows.value[0]!],
+  );
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="鍙戣揣閫氱煡鍗曢�夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鍙戣揣閫氱煡鍗曞垪琛�" />
+  </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/components/select.vue b/src/views/wls/salesnotice/components/select.vue
new file mode 100644
index 0000000..45aa8b0
--- /dev/null
+++ b/src/views/wls/salesnotice/components/select.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getSalesNotice } from '#/api/mes/wm/salesnotice';
+
+import WmSalesNoticeSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmSalesNoticeSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+    status?: number; // 鍥哄畾鐘舵�佺瓫閫�
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨鍙戣揣閫氱煡鍗�',
+    status: undefined,
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmSalesNoticeApi.SalesNotice | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmSalesNoticeSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmSalesNoticeApi.SalesNotice>();
+
+const displayLabel = computed(() => selectedItem.value?.name ?? '');
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== undefined &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ閫氱煡鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getSalesNotice(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夐�氱煡鍗� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, {
+    multiple: false,
+    status: props.status,
+  });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmSalesNoticeApi.SalesNotice[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+          <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+          <div>瀹㈡埛锛歿{ selectedItem.clientName || '-' }}</div>
+          <div>閿�鍞鍗曪細{{ selectedItem.salesOrderCode || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <WmSalesNoticeSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/salesnotice/data.ts b/src/views/wls/salesnotice/data.ts
new file mode 100644
index 0000000..a0aa7ba
--- /dev/null
+++ b/src/views/wls/salesnotice/data.ts
@@ -0,0 +1,362 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽嚭搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+      rules: 'required',
+      suffix: isHeaderReadonly(formType)
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_SALES_NOTICE_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'salesDate',
+      label: '鍙戣揣鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鍙戣揣鏃ユ湡',
+        valueFormat: 'x',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'recipientName',
+      label: '鏀惰揣浜�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ敹璐т汉',
+      },
+    },
+    {
+      fieldName: 'recipientTelephone',
+      label: '鑱旂郴鏂瑰紡',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+      },
+    },
+    {
+      fieldName: 'recipientAddress',
+      label: '鏀惰揣鍦板潃',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ敹璐у湴鍧�',
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '閫氱煡鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '閫氱煡鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+      },
+    },
+    {
+      fieldName: 'salesOrderCode',
+      label: '閿�鍞鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+      },
+    },
+    {
+      fieldName: 'clientId',
+      label: '瀹㈡埛',
+      component: markRaw(MdClientSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨瀹㈡埛',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '閫氱煡鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '閫氱煡鍗曞悕绉�',
+      minWidth: 150,
+    },
+    {
+      field: 'salesOrderCode',
+      title: '閿�鍞鍗曠紪鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'clientName',
+      title: '瀹㈡埛鍚嶇О',
+      minWidth: 120,
+    },
+    {
+      field: 'salesDate',
+      title: '鍙戣揣鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'recipientName',
+      title: '鏀惰揣浜�',
+      minWidth: 100,
+    },
+    {
+      field: 'recipientTelephone',
+      title: '鑱旂郴鏂瑰紡',
+      minWidth: 120,
+    },
+    {
+      field: 'recipientAddress',
+      title: '鏀惰揣鍦板潃',
+      minWidth: 200,
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_SALES_NOTICE_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 閫氱煡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+): VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '鍙戣揣鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'oqcCheckFlag',
+      title: '鏄惁妫�楠�',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 120,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 閫氱煡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鍙戣揣鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0.01,
+        placeholder: '璇疯緭鍏ュ彂璐ф暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'oqcCheckFlag',
+      label: '鏄惁妫�楠�',
+      component: 'Switch',
+      rules: z.boolean().default(true),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/salesnotice/index.vue b/src/views/wls/salesnotice/index.vue
new file mode 100644
index 0000000..e2958c8
--- /dev/null
+++ b/src/views/wls/salesnotice/index.vue
@@ -0,0 +1,168 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmSalesNoticeStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteSalesNotice,
+  exportSalesNotice,
+  getSalesNoticePage,
+} from '#/api/mes/wm/salesnotice';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鍙戣揣閫氱煡鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鍙戣揣閫氱煡鍗� */
+function handleDetail(row: MesWmSalesNoticeApi.SalesNotice) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鍙戣揣閫氱煡鍗� */
+function handleEdit(row: MesWmSalesNoticeApi.SalesNotice) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmSalesNoticeApi.SalesNotice) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗� */
+async function handleDelete(row: MesWmSalesNoticeApi.SalesNotice) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteSalesNotice(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportSalesNotice(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鍙戣揣閫氱煡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getSalesNoticePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鍙戣揣閫氱煡鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-sales-notice:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-sales-notice:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-sales-notice:update'],
+              ifShow: row.status === MesWmSalesNoticeStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-sales-notice:delete'],
+              ifShow: row.status === MesWmSalesNoticeStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鍑哄簱',
+              type: 'link',
+              auth: ['mes:wm-sales-notice:update'],
+              ifShow: row.status === MesWmSalesNoticeStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/salesnotice/modules/form.vue b/src/views/wls/salesnotice/modules/form.vue
new file mode 100644
index 0000000..074d6a9
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/form.vue
@@ -0,0 +1,182 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmSalesNoticeStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createSalesNotice,
+  getSalesNotice,
+  submitSalesNotice,
+  updateSalesNotice,
+} from '#/api/mes/wm/salesnotice';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmSalesNoticeApi.SalesNotice>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+  () =>
+    // 鏄惁鍙彁浜�
+    formType.value === 'update' &&
+    formData.value?.status === MesWmSalesNoticeStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鍙戣揣閫氱煡鍗�']);
+  }
+  if (formType.value === 'finish') {
+    return '鎵ц鍑哄簱';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鍙戣揣閫氱煡鍗�'])
+    : $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫氱煡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmSalesNoticeApi.SalesNotice;
+      await updateSalesNotice({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitSalesNotice(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鍑哄簱锛堝悗绔殏鏈疄鐜帮紝鎻愮ず鐢ㄦ埛锛� */
+function handleFinish() {
+  message.info('鎵ц鍑哄簱鍔熻兘鏆傛椂涓嶆敮鎸侊紝鏁鏈熷緟锛�');
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmSalesNoticeApi.SalesNotice;
+    try {
+      if (formData.value?.id) {
+        await updateSalesNotice({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createSalesNotice(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmSalesNoticeStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getSalesNotice(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :notice-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ュ彂璐ч�氱煡鍗曪紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Button v-if="isFinish" type="primary" @click="handleFinish">
+          鎵ц鍑哄簱
+        </Button>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/modules/line-form.vue b/src/views/wls/salesnotice/modules/line-form.vue
new file mode 100644
index 0000000..740a8ad
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createSalesNoticeLine,
+  getSalesNoticeLine,
+  updateSalesNoticeLine,
+} from '#/api/mes/wm/salesnotice/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmSalesNoticeLineApi.SalesNoticeLine>();
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鍙戣揣閫氱煡鍗曡'])
+    : $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useLineFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmSalesNoticeLineApi.SalesNoticeLine;
+    data.noticeId = noticeId.value;
+    try {
+      await (formData.value?.id
+        ? updateSalesNoticeLine({ ...data, id: formData.value.id })
+        : createSalesNoticeLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; noticeId: number }>();
+    noticeId.value = data.noticeId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getSalesNoticeLine(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/modules/line-list.vue b/src/views/wls/salesnotice/modules/line-list.vue
new file mode 100644
index 0000000..bd3041c
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteSalesNoticeLine,
+  getSalesNoticeLinePage,
+} from '#/api/mes/wm/salesnotice/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  noticeId: number;
+}>();
+
+const isEditable = computed(() =>
+  // 鏄惁鍙紪杈戞槑缁嗚
+  ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ noticeId: props.noticeId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmSalesNoticeLineApi.SalesNoticeLine) {
+  lineFormModalApi.setData({ id: row.id, noticeId: props.noticeId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmSalesNoticeLineApi.SalesNoticeLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteSalesNoticeLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value),
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.noticeId) {
+            return { list: [], total: 0 };
+          }
+          return await getSalesNoticeLinePage({
+            noticeId: props.noticeId,
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>,
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/mes/wm/sn/data.ts b/src/views/wls/sn/data.ts
similarity index 100%
copy from src/views/mes/wm/sn/data.ts
copy to src/views/wls/sn/data.ts
diff --git a/src/views/wls/sn/index.vue b/src/views/wls/sn/index.vue
new file mode 100644
index 0000000..e3bca26
--- /dev/null
+++ b/src/views/wls/sn/index.vue
@@ -0,0 +1,177 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteSnBatch,
+  exportSnDetailExcel,
+  exportSnGroupExcel,
+  getSnGroupPage,
+} from '#/api/mes/wm/sn';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Detail from './modules/detail.vue';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [DetailModal, detailModalApi] = useVbenModal({
+  connectedComponent: Detail,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鐢熸垚 SN 鐮� */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 瀵煎嚭鍒嗙粍琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportSnGroupExcel(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: 'SN鐮佸垎缁�.xls', source: data });
+}
+
+/** 瀵煎嚭鎵规鏄庣粏 */
+async function handleExportDetail(row: MesWmSnApi.SnGroup) {
+  const data = await exportSnDetailExcel(row.uuid!);
+  downloadFileFromBlobPart({ fileName: 'SN鐮佹槑缁�.xls', source: data });
+}
+
+/** 鏌ョ湅 SN 鐮佹槑缁� */
+function handleDetail(row: MesWmSnApi.SnGroup) {
+  detailModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎 SN 鐮佹壒娆� */
+async function handleDelete(row: MesWmSnApi.SnGroup) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.batchCode || row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteSnBatch(row.uuid!);
+    message.success(
+      $t('ui.actionMessage.deleteSuccess', [row.batchCode || '']),
+    );
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getSnGroupPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'uuid',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmSnApi.SnGroup>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+    <DetailModal />
+
+    <Grid table-title="SN 鐮佸垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '鐢熸垚 SN 鐮�',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-sn:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-sn:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #count="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: `${row.count ?? 0}`,
+              type: 'link',
+              auth: ['mes:wm-sn:query'],
+              onClick: handleDetail.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '鏌ョ湅鏄庣粏',
+              type: 'link',
+              auth: ['mes:wm-sn:query'],
+              onClick: handleDetail.bind(null, row),
+            },
+            {
+              label: '瀵煎嚭鏄庣粏',
+              type: 'link',
+              auth: ['mes:wm-sn:export'],
+              onClick: handleExportDetail.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-sn:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [
+                  row.batchCode || row.itemName,
+                ]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/sn/modules/detail.vue b/src/views/wls/sn/modules/detail.vue
new file mode 100644
index 0000000..6162d9e
--- /dev/null
+++ b/src/views/wls/sn/modules/detail.vue
@@ -0,0 +1,100 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { nextTick, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSnListByUuid } from '#/api/mes/wm/sn';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useDetailGridColumns } from '../data';
+
+defineOptions({ name: 'WlsSnDetail' });
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 鏌ョ湅 SN 鐮佹潯鐮� */
+function handleBarcode(row: MesWmSnApi.Sn) {
+  if (!row.id) {
+    message.warning('缂哄皯 SN 鐮佺紪鍙凤紝鏃犳硶鏌ョ湅鏉$爜');
+    return;
+  }
+  barcodeDetailRef.value?.openByBusiness(
+    row.id,
+    BarcodeBizTypeEnum.SN,
+    row.code,
+    row.itemName || row.code,
+  );
+}
+
+/** 鍔犺浇 SN 鐮佹槑缁� */
+async function loadDetail(row?: MesWmSnApi.SnGroup) {
+  if (!row?.uuid) {
+    await gridApi.grid?.loadData([]);
+    return;
+  }
+  gridApi.setLoading(true);
+  try {
+    const list = await getSnListByUuid(row.uuid);
+    await gridApi.grid?.loadData(list);
+  } finally {
+    gridApi.setLoading(false);
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useDetailGridColumns(),
+    height: 520,
+    pagerConfig: {
+      enabled: false,
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      enabled: false,
+    },
+  } as VxeTableGridOptions<MesWmSnApi.Sn>,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  showCancelButton: false,
+  showConfirmButton: false,
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      await gridApi.grid?.loadData([]);
+      return;
+    }
+    await nextTick();
+    await loadDetail(modalApi.getData<MesWmSnApi.SnGroup>());
+  },
+});
+</script>
+
+<template>
+  <Modal class="w-3/5" title="SN 鐮佹槑缁�">
+    <Grid class="mx-4">
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '鏉$爜',
+              type: 'link',
+              auth: ['mes:wm-sn:query'],
+              onClick: handleBarcode.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </Modal>
+</template>
diff --git a/src/views/wls/sn/modules/form.vue b/src/views/wls/sn/modules/form.vue
new file mode 100644
index 0000000..6a22db2
--- /dev/null
+++ b/src/views/wls/sn/modules/form.vue
@@ -0,0 +1,62 @@
+<script lang="ts" setup>
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { generateSnCodes } from '#/api/mes/wm/sn';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmSnApi.SnGenerate;
+    try {
+      await generateSnCodes(data);
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success('鐢熸垚鎴愬姛');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      return;
+    }
+    await formApi.resetForm();
+    await formApi.setValues({ count: 100 });
+  },
+});
+</script>
+
+<template>
+  <Modal :title="$t('ui.actionTitle.create', ['SN 鐮�'])" class="w-1/2">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/components/condition-value-input.vue b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
new file mode 100644
index 0000000..1586a6f
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
@@ -0,0 +1,204 @@
+<script lang="ts" setup>
+import type { NumberDictDataType } from '../../../../../packages/effects/hooks/src';
+
+import { ref, watch } from 'vue';
+
+import { DICT_TYPE, WlsStockTakingParamTypeEnum } from '../../../../../packages/constants/src';
+import { getDictOptions } from '../../../../../packages/effects/hooks/src';
+
+import { Select } from 'ant-design-vue';
+
+import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+defineOptions({ name: 'StockTakingPlanConditionValueInput' });
+
+const props = withDefaults(
+  defineProps<{
+    modelValue?: number;
+    type?: number;
+    valueCode?: string;
+  }>(),
+  {
+    modelValue: undefined,
+    type: undefined,
+    valueCode: undefined,
+  },
+);
+const emit = defineEmits<{
+  'update:modelValue': [value?: number];
+  valueChange: [
+    payload: { valueCode?: string; valueId?: number; valueName?: string },
+  ];
+}>();
+
+const qualityStatusOptions = getDictOptions(
+  DICT_TYPE.MES_WM_QUALITY_STATUS,
+  'number',
+) as NumberDictDataType[];
+
+const locationWarehouseId = ref<number>(); // 搴撳尯閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
+const areaWarehouseId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
+const areaLocationId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫夊簱鍖哄悗浼犵粰搴撲綅閫夋嫨鍣�
+
+const qualityStatusValue = ref<number>();
+
+/** 閫氱敤閫夋嫨鍣ㄥ彉鍖栵細鍥炲~鏉′欢鍊肩紪鐮併�佸悕绉� */
+function handleSelectorChange(item?: any) {
+  emit('update:modelValue', item?.id);
+  emit('valueChange', {
+    valueId: item?.id,
+    valueCode: item?.code || '',
+    valueName: item?.name || item?.nickname || '',
+  });
+}
+
+/** 鎵规閫夋嫨鍣ㄥ彉鍖� */
+function handleBatchChange(batch?: any) {
+  emit('update:modelValue', batch?.id);
+  emit('valueChange', {
+    valueId: batch?.id,
+    valueCode: batch?.code || '',
+    valueName: batch?.code || '',
+  });
+}
+
+/** 璐ㄩ噺鐘舵�侀�夋嫨鍣ㄥ彉鍖栵細鏃犲疄浣撶紪鍙凤紝浠呰褰曞瓧鍏哥紪鐮佸拰鏂囨 */
+function handleQualityStatusChange(value?: number) {
+  qualityStatusValue.value = value;
+  const selected = qualityStatusOptions.find((item) => item.value === value);
+  emit('update:modelValue', undefined);
+  emit('valueChange', {
+    valueId: undefined,
+    valueCode: value === null ? '' : String(value),
+    valueName: selected?.label || '',
+  });
+}
+
+/** 搴撳尯浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖� */
+function handleLocationWarehouseChange() {
+  emit('update:modelValue', undefined);
+  emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 搴撲綅浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖哄拰搴撲綅 */
+function handleAreaWarehouseChange() {
+  areaLocationId.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 搴撲綅搴撳尯閫夋嫨鍥炶皟锛氭竻绌哄簱浣� */
+function handleAreaLocationChange() {
+  emit('update:modelValue', undefined);
+  emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 缂栬緫鏃跺洖濉骇鑱旈�夋嫨鍣ㄧ殑涓婄骇鏁版嵁锛堝簱鍖烘墍灞炰粨搴撱�佸簱浣嶆墍灞炰粨搴�/搴撳尯锛� */
+async function loadCascadeData() {
+  if (props.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS) {
+    qualityStatusValue.value = props.valueCode
+      ? Number(props.valueCode)
+      : undefined;
+    return;
+  }
+  if (!props.modelValue) {
+    return;
+  }
+  if (props.type === WlsStockTakingParamTypeEnum.LOCATION) {
+    const location = await getWarehouseLocation(props.modelValue);
+    locationWarehouseId.value = location?.warehouseId;
+  } else if (props.type === WlsStockTakingParamTypeEnum.AREA) {
+    const area = await getWarehouseArea(props.modelValue);
+    areaWarehouseId.value = area?.warehouseId;
+    areaLocationId.value = area?.locationId;
+  }
+}
+
+// 鏉′欢绫诲瀷鍙樺寲鏃堕噸缃骇鑱斾复鏃舵暟鎹紱缂栬緫鍥炴樉鏃舵寜绫诲瀷鍔犺浇涓婄骇鏁版嵁
+watch(
+  () => props.type,
+  () => {
+    locationWarehouseId.value = undefined;
+    areaWarehouseId.value = undefined;
+    areaLocationId.value = undefined;
+    qualityStatusValue.value = undefined;
+    void loadCascadeData();
+  },
+  { immediate: true },
+);
+</script>
+
+<template>
+  <WmWarehouseSelect
+    v-if="type === WlsStockTakingParamTypeEnum.WAREHOUSE"
+    :model-value="modelValue"
+    @change="handleSelectorChange"
+  />
+  <div
+    v-else-if="type === WlsStockTakingParamTypeEnum.LOCATION"
+    class="space-y-2"
+  >
+    <WmWarehouseSelect
+      v-model="locationWarehouseId"
+      placeholder="璇烽�夋嫨浠撳簱"
+      @change="handleLocationWarehouseChange"
+    />
+    <WmWarehouseLocationSelect
+      v-if="locationWarehouseId"
+      :model-value="modelValue"
+      :warehouse-id="locationWarehouseId"
+      placeholder="璇烽�夋嫨搴撳尯"
+      @change="handleSelectorChange"
+    />
+  </div>
+  <div
+    v-else-if="type === WlsStockTakingParamTypeEnum.AREA"
+    class="space-y-2"
+  >
+    <WmWarehouseSelect
+      v-model="areaWarehouseId"
+      placeholder="璇烽�夋嫨浠撳簱"
+      @change="handleAreaWarehouseChange"
+    />
+    <WmWarehouseLocationSelect
+      v-if="areaWarehouseId"
+      v-model="areaLocationId"
+      placeholder="璇烽�夋嫨搴撳尯"
+      :warehouse-id="areaWarehouseId"
+      @change="handleAreaLocationChange"
+    />
+    <WmWarehouseAreaSelect
+      v-if="areaLocationId"
+      :model-value="modelValue"
+      :location-id="areaLocationId"
+      placeholder="璇烽�夋嫨搴撲綅"
+      @change="handleSelectorChange"
+    />
+  </div>
+  <MdItemSelect
+    v-else-if="type === WlsStockTakingParamTypeEnum.ITEM"
+    :model-value="modelValue"
+    @change="handleSelectorChange"
+  />
+  <WmBatchSelect
+    v-else-if="type === WlsStockTakingParamTypeEnum.BATCH"
+    :model-value="modelValue"
+    @change="handleBatchChange"
+  />
+  <Select
+    v-else-if="type === WlsStockTakingParamTypeEnum.QUALITY_STATUS"
+    :options="qualityStatusOptions"
+    :value="qualityStatusValue"
+    placeholder="璇烽�夋嫨璐ㄩ噺鐘舵��"
+    class="w-full"
+    @change="(value) => handleQualityStatusChange(value as number)"
+  />
+</template>
diff --git a/src/views/mes/wm/stocktaking/plan/components/index.ts b/src/views/wls/stocktaking/plan/components/index.ts
similarity index 100%
copy from src/views/mes/wm/stocktaking/plan/components/index.ts
copy to src/views/wls/stocktaking/plan/components/index.ts
diff --git a/src/views/wls/stocktaking/plan/components/select-dialog.vue b/src/views/wls/stocktaking/plan/components/select-dialog.vue
new file mode 100644
index 0000000..d16740c
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/select-dialog.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { nextTick, ref } from 'vue';
+
+import { CommonStatusEnum } from '../../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getStockTakingPlanPage } from '#/api/mes/wm/stocktaking/plan';
+
+import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
+
+const emit = defineEmits<{
+  selected: [rows: MesWmStockTakingPlanApi.StockTakingPlan[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(true); // 鏄惁澶氶��
+const selectedRows = ref<MesWmStockTakingPlanApi.StockTakingPlan[]>([]); // 宸查�夌洏鐐规柟妗�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夌洏鐐规柟妗堢紪鍙�
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<
+    number,
+    MesWmStockTakingPlanApi.StockTakingPlan
+  >();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as MesWmStockTakingPlanApi.StockTakingPlan[];
+  records.forEach((row) => {
+    if (row.id !== undefined && row.id !== null) {
+      selectedMap.set(row.id, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+  selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+  await gridApi.grid.setCheckboxRow(row, !selected);
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紱澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+  row,
+}: {
+  row: MesWmStockTakingPlanApi.StockTakingPlan;
+}) {
+  if (multiple.value) {
+    await toggleMultipleRow(row);
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夌洏鐐规柟妗� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows =
+    gridApi.grid.getData() as MesWmStockTakingPlanApi.StockTakingPlan[];
+  for (const row of rows) {
+    if (
+      row.id === undefined ||
+      row.id === null ||
+      !preSelectedIds.value.includes(row.id)
+    ) {
+      continue;
+    }
+    if (multiple.value) {
+      await gridApi.grid.setCheckboxRow(row, true);
+    } else {
+      await gridApi.grid.setRadioRow(row);
+      selectedRows.value = [row];
+      return;
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useSelectGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useSelectGridColumns(true),
+    height: 480,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      range: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getStockTakingPlanPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+            // 閫夋嫨鍣ㄥ彧灞曠ず鍚敤鐘舵�佺殑鏂规
+            status: CommonStatusEnum.ENABLE,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({
+      row,
+    }: {
+      row: MesWmStockTakingPlanApi.StockTakingPlan;
+    }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑鐩樼偣鏂规閫夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? true;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useSelectGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+function closeModal() {
+  open.value = false;
+}
+
+/** 纭閫夋嫨 */
+function handleConfirm() {
+  const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+  if (rows.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', multiple.value ? rows : [rows[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    :destroy-on-close="true"
+    title="鐩樼偣鏂规閫夋嫨"
+    width="70%"
+    @cancel="closeModal"
+    @ok="handleConfirm"
+  >
+    <Grid table-title="鐩樼偣鏂规鍒楄〃" />
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/components/select.vue b/src/views/wls/stocktaking/plan/components/select.vue
new file mode 100644
index 0000000..cfb9115
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/select.vue
@@ -0,0 +1,135 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getStockTakingPlan } from '#/api/mes/wm/stocktaking/plan';
+
+import StockTakingPlanSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'StockTakingPlanSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨鐩樼偣鏂规',
+  },
+);
+
+const emit = defineEmits<{
+  change: [item: MesWmStockTakingPlanApi.StockTakingPlan | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof StockTakingPlanSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmStockTakingPlanApi.StockTakingPlan>();
+
+const displayLabel = computed(() => selectedItem.value?.name ?? '');
+
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ鐩樼偣鏂规淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getStockTakingPlan(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夌洏鐐规柟妗� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑鐩樼偣鏂规閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmStockTakingPlanApi.StockTakingPlan[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>缂栫爜锛歿{ selectedItem.code || '-' }}</div>
+          <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+          <div>鏄惁鐩茬洏锛歿{ selectedItem.blindFlag ? '鏄�' : '鍚�' }}</div>
+          <div>鏄惁鍐荤粨搴撳瓨锛歿{ selectedItem.frozen ? '鏄�' : '鍚�' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <StockTakingPlanSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
new file mode 100644
index 0000000..cfbc882
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -0,0 +1,472 @@
+import type { NumberDictDataType } from '../../../../packages/effects/hooks/src';
+
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { h, markRaw } from 'vue';
+
+import {
+  CommonStatusEnum,
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  WlsStockTakingTypeEnum,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+
+import { StockTakingPlanConditionValueInput } from './components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '鏂规缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_STOCK_TAKING_PLAN_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'name',
+      label: '鏂规鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '鐩樼偣绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'startTime',
+      label: '寮�濮嬫椂闂�',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+      },
+    },
+    {
+      fieldName: 'endTime',
+      label: '缁撴潫鏃堕棿',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+      },
+    },
+    {
+      fieldName: 'blindFlag',
+      label: '鏄惁鐩茬洏',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'frozen',
+      label: '鍐荤粨搴撳瓨',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鏂规缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鏂规鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '鐩樼偣绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+  onStatusChange?: (
+    newStatus: number,
+    row: MesWmStockTakingPlanApi.StockTakingPlan,
+  ) => Promise<boolean | undefined>,
+): VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '鏂规缂栫爜',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '鏂规鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'type',
+      title: '鐩樼偣绫诲瀷',
+      minWidth: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+      },
+    },
+    {
+      field: 'startTime',
+      title: '寮�濮嬫椂闂�',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'endTime',
+      title: '缁撴潫鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'blindFlag',
+      title: '鏄惁鐩茬洏',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'frozen',
+      title: '鏄惁鍐荤粨搴撳瓨',
+      width: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 120,
+      cellRender: {
+        attrs: { beforeChange: onStatusChange },
+        name: 'CellSwitch',
+        props: {
+          checkedValue: CommonStatusEnum.ENABLE,
+          unCheckedValue: CommonStatusEnum.DISABLE,
+        },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 160,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鐩樼偣鏂规鏉′欢鍒楄〃鐨勫瓧娈� */
+export function useParamGridColumns(
+  editable = true,
+): VxeTableGridOptions<MesWmStockTakingPlanParamApi.StockTakingPlanParam>['columns'] {
+  return [
+    {
+      field: 'type',
+      title: '鏉′欢绫诲瀷',
+      minWidth: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE },
+      },
+    },
+    {
+      field: 'valueCode',
+      title: '鏉′欢鍊肩紪鐮�',
+      minWidth: 140,
+    },
+    {
+      field: 'valueName',
+      title: '鏉′欢鍊煎悕绉�',
+      minWidth: 160,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鐩樼偣鏂规鏉′欢琛ㄥ崟 schema */
+export function useParamFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'type',
+      label: '鏉′欢绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(
+          DICT_TYPE.MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE,
+          'number',
+        ) as NumberDictDataType[],
+        placeholder: '璇烽�夋嫨鏉′欢绫诲瀷',
+        // 鏉′欢绫诲瀷鍙樺寲锛氭竻绌哄凡閫夋潯浠跺�硷紝閬垮厤娈嬬暀鏃х被鍨嬬殑鏉′欢鍊�
+        onChange: async () => {
+          await formApi?.setValues({
+            valueCode: '',
+            valueId: undefined,
+            valueName: '',
+          });
+        },
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'valueId',
+      label: '鏉′欢鍊�',
+      component: markRaw(StockTakingPlanConditionValueInput),
+      // 鏉′欢鍊兼帶浠跺唴閮ㄦ寜鏉′欢绫诲瀷鍒囨崲閫夋嫨鍣紝浠呴�夋嫨绫诲瀷鍚庡睍绀�
+      dependencies: {
+        triggerFields: ['type'],
+        if: (values) => values.type !== null,
+        componentProps: (values) => ({
+          type: values.type,
+          valueCode: values.valueCode,
+          // 鏉′欢鍊兼帶浠跺洖濉� valueId / valueCode / valueName
+          onValueChange: async (payload: {
+            valueCode?: string;
+            valueId?: number;
+            valueName?: string;
+          }) => {
+            await formApi?.setValues({
+              valueCode: payload.valueCode ?? '',
+              valueId: payload.valueId,
+              valueName: payload.valueName ?? '',
+            });
+          },
+        }),
+      },
+    },
+    {
+      // 鏉′欢鍊肩紪鐮侊細鐢辨潯浠跺�兼帶浠跺洖鍐欙紝闅愯棌瀛楁
+      fieldName: 'valueCode',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      // 鏉′欢鍊煎悕绉帮細鐢辨潯浠跺�兼帶浠跺洖鍐欙紝闅愯棌瀛楁
+      fieldName: 'valueName',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鏂规缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鏂规鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '鐩樼偣绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+      },
+    },
+  ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+  multiple = true,
+): VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>['columns'] {
+  return [
+    {
+      type: multiple ? 'checkbox' : 'radio',
+      width: 50,
+    },
+    {
+      field: 'code',
+      title: '鏂规缂栫爜',
+      width: 200,
+    },
+    {
+      field: 'name',
+      title: '鏂规鍚嶇О',
+      minWidth: 150,
+    },
+    {
+      field: 'type',
+      title: '鐩樼偣绫诲瀷',
+      width: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+      },
+    },
+    {
+      field: 'startTime',
+      title: '寮�濮嬫椂闂�',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'endTime',
+      title: '缁撴潫鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'blindFlag',
+      title: '鏄惁鐩茬洏',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'frozen',
+      title: '鏄惁鍐荤粨搴撳瓨',
+      width: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/stocktaking/plan/index.vue b/src/views/wls/stocktaking/plan/index.vue
new file mode 100644
index 0000000..637fd26
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/index.vue
@@ -0,0 +1,176 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { CommonStatusEnum } from '../../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteStockTakingPlan,
+  exportStockTakingPlan,
+  getStockTakingPlanPage,
+  updateStockTakingPlanStatus,
+} from '#/api/mes/wm/stocktaking/plan';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鐩樼偣鏂规 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐩樼偣鏂规 */
+function handleDetail(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐩樼偣鏂规 */
+function handleEdit(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鏇存柊鐩樼偣鏂规鐘舵�� */
+async function handleStatusChange(
+  newStatus: number,
+  row: MesWmStockTakingPlanApi.StockTakingPlan,
+): Promise<boolean | undefined> {
+  try {
+    await confirm(
+      `纭瑕�${newStatus === CommonStatusEnum.ENABLE ? '鍚敤' : '鍋滅敤'}"${row.name}"鐩樼偣鏂规鍚楋紵`,
+    );
+  } catch {
+    return false;
+  }
+  await updateStockTakingPlanStatus(row.id!, newStatus);
+  message.success($t('ui.actionMessage.operationSuccess'));
+  return true;
+}
+
+/** 鍒犻櫎鐩樼偣鏂规 */
+async function handleDelete(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteStockTakingPlan(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportStockTakingPlan(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鐩樼偣鏂规.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(handleStatusChange),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getStockTakingPlanPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>,
+});
+
+const StatusEnum = CommonStatusEnum;
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鐩樼偣鏂规鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鐩樼偣鏂规']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-stock-taking-plan:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-stock-taking-plan:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-stock-taking-plan:update'],
+              disabled: row.status !== StatusEnum.DISABLE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-stock-taking-plan:delete'],
+              disabled: row.status !== StatusEnum.DISABLE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/form.vue b/src/views/wls/stocktaking/plan/modules/form.vue
new file mode 100644
index 0000000..0bcdb29
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/form.vue
@@ -0,0 +1,126 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { Divider, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createStockTakingPlan,
+  getStockTakingPlan,
+  updateStockTakingPlan,
+} from '#/api/mes/wm/stocktaking/plan';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import ParamList from './param-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmStockTakingPlanApi.StockTakingPlan>();
+const isDetail = computed(() => formType.value === 'detail');
+const showParam = computed(
+  () =>
+    (formType.value === 'detail' || formType.value === 'update') &&
+    !!formData.value?.id,
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鐩樼偣鏂规']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鐩樼偣鏂规'])
+    : $t('ui.actionTitle.create', ['鐩樼偣鏂规']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (isDetail.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmStockTakingPlanApi.StockTakingPlan;
+    try {
+      if (formData.value?.id) {
+        await updateStockTakingPlan({ ...data, id: formData.value.id });
+        // 鍏抽棴骞舵彁绀�
+        await modalApi.close();
+        emit('success');
+        message.success($t('ui.actionMessage.operationSuccess'));
+      } else {
+        // 鏂板鎴愬姛鍚庡垏鎹负缂栬緫鎬侊紝缁х画缁存姢鐩樼偣鍙傛暟
+        const id = await createStockTakingPlan(data);
+        formData.value = { ...data, id };
+        await formApi.setFieldValue('id', id);
+        formType.value = 'update';
+        emit('success');
+        message.success($t('ui.actionMessage.operationSuccess'));
+      }
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(formType.value === 'detail');
+    modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+    if (!data?.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getStockTakingPlan(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 缂栬緫鎴栬鎯呮椂灞曠ず鐩樼偣鍙傛暟 -->
+    <template v-if="showParam">
+      <Divider>鐩樼偣鍙傛暟</Divider>
+      <div class="mx-4">
+        <ParamList :disabled="isDetail" :plan-id="formData!.id!" />
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/param-form.vue b/src/views/wls/stocktaking/plan/modules/param-form.vue
new file mode 100644
index 0000000..2a810a1
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/param-form.vue
@@ -0,0 +1,108 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { WlsStockTakingParamTypeEnum } from '../../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createStockTakingPlanParam,
+  getStockTakingPlanParam,
+  updateStockTakingPlanParam,
+} from '#/api/mes/wm/stocktaking/plan/param';
+import { $t } from '#/locales';
+
+import { useParamFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formId = ref<number>(); // 褰撳墠缂栬緫鐨勬潯浠剁紪鍙�
+const planId = ref<number>(); // 鎵�灞炵洏鐐规柟妗堢紪鍙�
+
+const getTitle = computed(() =>
+  formId.value
+    ? $t('ui.actionTitle.edit', ['鐩樼偣鏉′欢'])
+    : $t('ui.actionTitle.create', ['鐩樼偣鏉′欢']),
+);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-1',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    const values =
+      (await formApi.getValues()) as MesWmStockTakingPlanParamApi.StockTakingPlanParam;
+    // 璐ㄩ噺鐘舵�佹牎楠� valueCode锛屽叾浣欑被鍨嬫牎楠� valueId
+    const valueValid =
+      values.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS
+        ? !!values.valueCode
+        : values.valueId !== null;
+    if (!valueValid) {
+      message.warning('璇烽�夋嫨鏉′欢鍊�');
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = {
+      ...values,
+      id: formId.value,
+      planId: planId.value,
+    } as MesWmStockTakingPlanParamApi.StockTakingPlanParam;
+    try {
+      await (formId.value
+        ? updateStockTakingPlanParam(data)
+        : createStockTakingPlanParam(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formId.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useParamFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; planId: number }>();
+    planId.value = data.planId;
+    formId.value = data.id;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      const param = await getStockTakingPlanParam(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(param);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/param-list.vue b/src/views/wls/stocktaking/plan/modules/param-list.vue
new file mode 100644
index 0000000..17900a6
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/param-list.vue
@@ -0,0 +1,131 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteStockTakingPlanParam,
+  getStockTakingPlanParamPage,
+} from '#/api/mes/wm/stocktaking/plan/param';
+import { $t } from '#/locales';
+
+import { useParamGridColumns } from '../data';
+import ParamForm from './param-form.vue';
+
+const props = defineProps<{
+  disabled: boolean; // 鏄惁鍙锛堣鎯呮�侊級
+  planId: number; // 鎵�灞炴柟妗堢紪鍙�
+}>();
+
+const [ParamFormModal, paramFormModalApi] = useVbenModal({
+  connectedComponent: ParamForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 娣诲姞鏉′欢 */
+function handleCreate() {
+  paramFormModalApi.setData({ planId: props.planId }).open();
+}
+
+/** 缂栬緫鏉′欢 */
+function handleEdit(row: MesWmStockTakingPlanParamApi.StockTakingPlanParam) {
+  paramFormModalApi.setData({ id: row.id, planId: props.planId }).open();
+}
+
+/** 鍒犻櫎鏉′欢 */
+async function handleDelete(
+  row: MesWmStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.valueName]),
+    duration: 0,
+  });
+  try {
+    await deleteStockTakingPlanParam(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.valueName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useParamGridColumns(!props.disabled),
+    height: 320,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.planId) {
+            return { list: [], total: 0 };
+          }
+          return await getStockTakingPlanParamPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            planId: props.planId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingPlanParamApi.StockTakingPlanParam>,
+});
+</script>
+
+<template>
+  <div>
+    <ParamFormModal @success="handleRefresh" />
+    <Grid table-title="鐩樼偣鍙傛暟">
+      <template v-if="!disabled" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鏉′欢',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.valueName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/stocktaking/task/data.ts b/src/views/wls/stocktaking/task/data.ts
new file mode 100644
index 0000000..f16ff9d
--- /dev/null
+++ b/src/views/wls/stocktaking/task/data.ts
@@ -0,0 +1,600 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+import type { MesWmStockTakingTaskLineApi } from '#/api/mes/wm/stocktaking/task/line';
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  WlsStockTakingTypeEnum,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { StockTakingPlanSelect } from '#/views/wls/stocktaking/plan/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'execute' | 'submit' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟彁浜ゃ�佹墽琛岀洏鐐广�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return (
+    formType === 'detail' || formType === 'execute' || formType === 'submit'
+  );
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  const headerReadonly = isHeaderReadonly(formType);
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '浠诲姟缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ换鍔$紪鐮�',
+      },
+      rules: 'required',
+      suffix: headerReadonly
+        ? undefined
+        : () =>
+            h(
+              Button,
+              {
+                type: 'default',
+                onClick: async () => {
+                  const code = await generateAutoCode(
+                    MesAutoCodeRuleCode.WM_STOCK_TAKING_CODE,
+                  );
+                  await formApi?.setFieldValue('code', code);
+                },
+              },
+              { default: () => '鐢熸垚' },
+            ),
+    },
+    {
+      fieldName: 'name',
+      label: '浠诲姟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ换鍔″悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'planId',
+      label: '鐩樼偣鏂规',
+      component: markRaw(StockTakingPlanSelect),
+      componentProps: {
+        // 閫夋嫨鐩樼偣鏂规鍚庯紝鑷姩甯﹀嚭鍚嶇О銆佺被鍨嬨�佽捣姝㈡椂闂村拰鐩茬洏/鍐荤粨閰嶇疆
+        onChange: async (plan?: MesWmStockTakingPlanApi.StockTakingPlan) => {
+          if (!plan) {
+            return;
+          }
+          await formApi?.setValues({
+            blindFlag: !!plan.blindFlag,
+            endTime: plan.endTime,
+            frozen: !!plan.frozen,
+            name: plan.name,
+            startTime: plan.startTime,
+            type: plan.type,
+          });
+        },
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'type',
+      label: '鐩樼偣绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'startTime',
+      label: '寮�濮嬫椂闂�',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+      },
+    },
+    {
+      fieldName: 'endTime',
+      label: '缁撴潫鏃堕棿',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+        showTime: true,
+        valueFormat: 'x',
+      },
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+      },
+    },
+    {
+      fieldName: 'takingDate',
+      label: '鐩樼偣鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨鐩樼偣鏃ユ湡',
+        valueFormat: 'YYYY-MM-DD',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'blindFlag',
+      label: '鏄惁鐩茬洏',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨搴撳瓨',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'userId',
+      label: '鐩樼偣浜�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        placeholder: '璇烽�夋嫨鐩樼偣浜�',
+        valueField: 'id',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'type',
+      label: '鐩樼偣绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '浠诲姟缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ换鍔$紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '浠诲姟鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ换鍔″悕绉�',
+      },
+    },
+    {
+      fieldName: 'takingDate',
+      label: '鐩樼偣鏃ユ湡',
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(
+          DICT_TYPE.MES_WM_STOCK_TAKING_TASK_STATUS,
+          'number',
+        ),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmStockTakingTaskApi.StockTakingTask>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '浠诲姟缂栫爜',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '浠诲姟鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'type',
+      title: '鐩樼偣绫诲瀷',
+      minWidth: 120,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+      },
+    },
+    {
+      field: 'planName',
+      title: '鐩樼偣鏂规',
+      minWidth: 180,
+    },
+    {
+      field: 'takingDate',
+      title: '鐩樼偣鏃ユ湡',
+      minWidth: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'userNickname',
+      title: '鐩樼偣浜�',
+      minWidth: 120,
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TASK_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 280,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 鐩樼偣浠诲姟琛屽垪琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+  editable = true,
+): VxeTableGridOptions<MesWmStockTakingTaskLineApi.StockTakingTaskLine>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 140,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 90,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'quantity',
+      title: '鍦ㄥ簱鏁伴噺',
+      width: 120,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 120,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 120,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 120,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_LINE_STATUS },
+      },
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 80,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鐩樼偣缁撴灉鍒楄〃鐨勫瓧娈� */
+export function useResultGridColumns(
+  editable = true,
+): VxeTableGridOptions<MesWmStockTakingResultApi.StockTakingResult>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '浜у搧鐗╂枡缂栫爜',
+      minWidth: 140,
+    },
+    {
+      field: 'itemName',
+      title: '浜у搧鐗╂枡鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅鍚嶇О',
+      width: 90,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 120,
+    },
+    {
+      field: 'locationName',
+      title: '搴撳尯',
+      minWidth: 120,
+    },
+    {
+      field: 'areaName',
+      title: '搴撲綅',
+      minWidth: 120,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      minWidth: 120,
+    },
+    {
+      field: 'takingQuantity',
+      title: '鐩樼偣鏁伴噺',
+      minWidth: 120,
+    },
+    ...(editable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 160,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 鐩樼偣缁撴灉鏂板/淇敼鐨勮〃鍗� */
+export function useResultFormSchema(
+  formApi?: VbenFormApi,
+  taskLines: MesWmStockTakingTaskLineApi.StockTakingTaskLine[] = [],
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'lineId',
+      label: '鐩樼偣娓呭崟',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        // 閫夋嫨鐩樼偣娓呭崟鍚庯紝鑷姩甯﹀嚭鐗╂枡銆佹壒娆″拰浠撳偍浣嶇疆淇℃伅
+        onChange: async (lineId?: number) => {
+          const line = taskLines.find((item) => item.id === lineId);
+          await formApi?.setValues({
+            areaId: line?.areaId,
+            batchCode: line?.batchCode,
+            batchId: line?.batchId,
+            itemId: line?.itemId,
+            locationId: line?.locationId,
+            materialStockId: line?.materialStockId,
+            warehouseId: line?.warehouseId,
+          });
+        },
+        options: taskLines.map((line) => ({
+          label: `${line.itemCode} - ${line.itemName} (${line.warehouseName}${
+            line.locationName ? ` / ${line.locationName}` : ''
+          }${line.areaName ? ` / ${line.areaName}` : ''})`,
+          value: line.id,
+        })),
+        placeholder: '璇烽�夋嫨鐩樼偣娓呭崟锛堝彲閫夛級',
+      },
+      formItemClass: 'col-span-3',
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+      },
+      rules: 'selectRequired',
+      // 閫変腑鐩樼偣娓呭崟鍚庯紝鐗╂枡鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+      dependencies: {
+        triggerFields: ['lineId'],
+        disabled: (values) => values.lineId !== null,
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆$紪鐮�',
+      },
+      // 閫変腑鐩樼偣娓呭崟鍚庯紝鎵规鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+      dependencies: {
+        triggerFields: ['lineId'],
+        disabled: (values) => values.lineId !== null,
+      },
+    },
+    {
+      fieldName: 'takingQuantity',
+      label: '鐩樼偣鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        placeholder: '璇疯緭鍏ョ洏鐐规暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        // 浠撳簱鍙樺寲鏃舵竻绌哄簱鍖哄拰搴撲綅
+        onChange: () =>
+          formApi?.setValues({
+            areaId: undefined,
+            locationId: undefined,
+          }),
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'selectRequired',
+      // 閫変腑鐩樼偣娓呭崟鍚庯紝浠撳簱鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+      dependencies: {
+        triggerFields: ['lineId'],
+        disabled: (values) => values.lineId !== null,
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['warehouseId', 'lineId'],
+        show: (values) => !!values.warehouseId,
+        // 閫変腑鐩樼偣娓呭崟鍚庯紝搴撳尯鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+        disabled: (values) => values.lineId !== null,
+        componentProps: (values) => ({
+          onChange: () => formApi?.setFieldValue('areaId', undefined),
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['locationId', 'lineId'],
+        show: (values) => !!values.locationId,
+        // 閫変腑鐩樼偣娓呭崟鍚庯紝搴撲綅鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+        disabled: (values) => values.lineId !== null,
+        componentProps: (values) => ({
+          locationId: values.locationId,
+          placeholder: '璇烽�夋嫨搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/stocktaking/task/index.vue b/src/views/wls/stocktaking/task/index.vue
new file mode 100644
index 0000000..472ed33
--- /dev/null
+++ b/src/views/wls/stocktaking/task/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmStockTakingTaskStatusEnum } from '../../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelStockTaking,
+  deleteStockTaking,
+  exportStockTaking,
+  getStockTakingPage,
+} from '#/api/mes/wm/stocktaking/task';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓鐩樼偣浠诲姟 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐩樼偣浠诲姟 */
+function handleDetail(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐩樼偣浠诲姟 */
+function handleEdit(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎻愪氦鐩樼偣浠诲姟 */
+function handleSubmit(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  formModalApi.setData({ formType: 'submit', id: row.id }).open();
+}
+
+/** 鎵ц鐩樼偣 */
+function handleExecute(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  formModalApi.setData({ formType: 'execute', id: row.id }).open();
+}
+
+/** 鍙栨秷鐩樼偣浠诲姟 */
+async function handleCancel(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  await cancelStockTaking(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟 */
+async function handleDelete(row: MesWmStockTakingTaskApi.StockTakingTask) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteStockTaking(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportStockTaking(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鐩樼偣浠诲姟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getStockTakingPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingTaskApi.StockTakingTask>,
+});
+
+const StatusEnum = MesWmStockTakingTaskStatusEnum;
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="鐩樼偣浠诲姟鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鐩樼偣浠诲姟']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-stock-taking-task:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-stock-taking-task:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-stock-taking-task:update'],
+              ifShow: row.status === StatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: '鎻愪氦',
+              type: 'link',
+              auth: ['mes:wm-stock-taking-task:update'],
+              ifShow: row.status === StatusEnum.PREPARE,
+              onClick: handleSubmit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-stock-taking-task:delete'],
+              ifShow: row.status === StatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鎵ц鐩樼偣',
+              type: 'link',
+              auth: ['mes:wm-stock-taking-task:update'],
+              ifShow: row.status === StatusEnum.APPROVING,
+              onClick: handleExecute.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-stock-taking-task:update'],
+              ifShow: row.status === StatusEnum.APPROVING,
+              popConfirm: {
+                title: '纭鍙栨秷璇ョ洏鐐逛换鍔★紵鍙栨秷鍚庝笉鍙仮澶嶃��',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/form.vue b/src/views/wls/stocktaking/task/modules/form.vue
new file mode 100644
index 0000000..48f190b
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/form.vue
@@ -0,0 +1,226 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { MesWmStockTakingTaskStatusEnum } from '../../../../../packages/constants/src';
+import { useUserStore } from '../../../../../packages/stores/src';
+
+import { Button, message, Popconfirm, Tabs } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createStockTaking,
+  finishStockTaking,
+  getStockTaking,
+  submitStockTaking,
+  updateStockTaking,
+} from '#/api/mes/wm/stocktaking/task';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+import ResultList from './result-list.vue';
+
+const emit = defineEmits(['success']);
+const userStore = useUserStore();
+const formType = ref<FormType>('create');
+const formData = ref<MesWmStockTakingTaskApi.StockTakingTask>();
+const subTabsName = ref('lines'); // 瀛愯〃褰撳墠 tab
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+
+const isEditable = computed(() =>
+  ['create', 'update'].includes(formType.value),
+);
+const isExecute = computed(() => formType.value === 'execute'); // 鏄惁鎵ц鐩樼偣妯″紡
+const isSubmit = computed(() => formType.value === 'submit'); // 鏄惁鎻愪氦妯″紡
+const canSubmit = computed(
+  () =>
+    formType.value === 'update' &&
+    formData.value?.status === MesWmStockTakingTaskStatusEnum.PREPARE,
+);
+const showLineTab = computed(() => !formData.value?.blindFlag); // 鐩茬洏涓嶅睍绀虹洏鐐规竻鍗�
+const showResultTab = computed(
+  () =>
+    isExecute.value ||
+    (!!formData.value?.status &&
+      formData.value.status !== MesWmStockTakingTaskStatusEnum.PREPARE),
+);
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['鐩樼偣浠诲姟']);
+  }
+  if (formType.value === 'execute') {
+    return '鎵ц鐩樼偣';
+  }
+  if (formType.value === 'submit') {
+    return '鎻愪氦鐩樼偣浠诲姟';
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['鐩樼偣浠诲姟'])
+    : $t('ui.actionTitle.create', ['鐩樼偣浠诲姟']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鐩樼偣浠诲姟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data =
+        (await formApi.getValues()) as MesWmStockTakingTaskApi.StockTakingTask;
+      await updateStockTaking({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitStockTaking(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц鐩樼偣 */
+async function handleExecute() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishStockTaking(formData.value.id);
+    message.success('鎵ц鐩樼偣鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmStockTakingTaskApi.StockTakingTask;
+    try {
+      if (formData.value?.id) {
+        await updateStockTaking({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createStockTaking(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmStockTakingTaskStatusEnum.PREPARE,
+        };
+        await formApi.setFieldValue('id', id);
+        await formApi.setFieldValue('status', formData.value.status);
+        formType.value = 'update';
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    subTabsName.value = data.formType === 'execute' ? 'results' : 'lines';
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(!isEditable.value);
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getStockTaking(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else {
+      // 鏂板缓鏃堕粯璁ょ洏鐐逛汉涓哄綋鍓嶇櫥褰曠敤鎴�
+      await formApi.setFieldValue('userId', userStore.userInfo?.id);
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-4/5">
+    <Form class="mx-4" />
+    <Tabs
+      v-if="formData?.id"
+      v-model:active-key="subTabsName"
+      class="mx-4 mt-4"
+      type="card"
+    >
+      <Tabs.TabPane v-if="showLineTab" key="lines" tab="鐩樼偣娓呭崟">
+        <LineList :form-type="formType" :task-id="formData.id" />
+      </Tabs.TabPane>
+      <Tabs.TabPane v-if="showResultTab" key="results" tab="鐩樼偣缁撴灉">
+        <ResultList
+          :form-type="isExecute ? 'execute' : 'detail'"
+          :task-id="formData.id"
+        />
+      </Tabs.TabPane>
+    </Tabs>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit || isSubmit"
+          title="纭鎻愪氦璇ョ洏鐐逛换鍔★紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isExecute"
+          title="纭鎵ц鐩樼偣鎿嶄綔锛�"
+          @confirm="handleExecute"
+        >
+          <Button type="primary">鎵ц鐩樼偣</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/line-list.vue b/src/views/wls/stocktaking/task/modules/line-list.vue
new file mode 100644
index 0000000..ee1eee7
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/line-list.vue
@@ -0,0 +1,151 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmStockTakingTaskLineApi } from '#/api/mes/wm/stocktaking/task/line';
+
+import { computed, ref } from 'vue';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  createStockTakingTaskLine,
+  deleteStockTakingTaskLine,
+  getStockTakingTaskLinePage,
+} from '#/api/mes/wm/stocktaking/task/line';
+import { $t } from '#/locales';
+import { WmMaterialStockSelectDialog } from '#/views/wls/materialstock/components';
+
+import { useLineGridColumns } from '../data';
+
+const props = defineProps<{
+  formType: FormType;
+  taskId: number;
+}>();
+
+const isEditable = computed(() => props.formType === 'update'); // 浠呯紪杈戞�佸彲缁存姢鐩樼偣娓呭崟
+const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鎵撳紑搴撳瓨閫夋嫨寮圭獥鎵归噺娣诲姞鐗╂枡 */
+function handleAdd() {
+  dialogRef.value?.open([], { multiple: true });
+}
+
+/** 搴撳瓨閫夋嫨纭鍥炶皟锛氬皢閫変腑鐨勫簱瀛樿褰曟壒閲忓垱寤轰负鐩樼偣琛� */
+async function handleStockSelected(
+  rows: MesWmMaterialStockApi.MaterialStock[],
+) {
+  if (rows.length === 0) {
+    return;
+  }
+  for (const stock of rows) {
+    await createStockTakingTaskLine({
+      areaId: stock.areaId,
+      batchId: stock.batchId,
+      itemId: stock.itemId,
+      locationId: stock.locationId,
+      materialStockId: stock.id,
+      quantity: stock.quantity,
+      taskId: props.taskId,
+      warehouseId: stock.warehouseId,
+    });
+  }
+  message.success(`鎴愬姛娣诲姞 ${rows.length} 鏉$洏鐐硅`);
+  handleRefresh();
+}
+
+/** 鍒犻櫎鐩樼偣琛� */
+async function handleDelete(
+  row: MesWmStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteStockTakingTaskLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value),
+    height: 360,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.taskId) {
+            return { list: [], total: 0 };
+          }
+          return await getStockTakingTaskLinePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            taskId: props.taskId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingTaskLineApi.StockTakingTaskLine>,
+});
+
+defineExpose({ refresh: handleRefresh });
+</script>
+
+<template>
+  <div>
+    <WmMaterialStockSelectDialog
+      ref="dialogRef"
+      @selected="handleStockSelected"
+    />
+    <Grid table-title="鐩樼偣娓呭崟">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-stock-taking-task:update'],
+              onClick: handleAdd,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/result-form.vue b/src/views/wls/stocktaking/task/modules/result-form.vue
new file mode 100644
index 0000000..74d4bf5
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/result-form.vue
@@ -0,0 +1,106 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { getStockTakingTaskLineSimpleList } from '#/api/mes/wm/stocktaking/task/line';
+import {
+  createStockTakingResult,
+  getStockTakingResult,
+  updateStockTakingResult,
+} from '#/api/mes/wm/stocktaking/task/result';
+import { $t } from '#/locales';
+
+import { useResultFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmStockTakingResultApi.StockTakingResult>();
+const taskId = ref<number>(); // 鎵�灞炵洏鐐逛换鍔$紪鍙�
+const isExecute = ref(false); // 鏄惁鎵ц鐩樼偣妯″紡锛堝彲閫夋嫨鐩樼偣娓呭崟鍥炲~锛�
+const getTitle = computed(() =>
+  formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐩樼偣缁撴灉'])
+    : $t('ui.actionTitle.create', ['鐩樼偣缁撴灉']),
+);
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmStockTakingResultApi.StockTakingResult;
+    data.taskId = taskId.value;
+    try {
+      await (formData.value?.id
+        ? updateStockTakingResult({ ...data, id: formData.value.id })
+        : createStockTakingResult(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      execute?: boolean;
+      id?: number;
+      taskId: number;
+    }>();
+    taskId.value = data.taskId;
+    isExecute.value = !!data.execute;
+    // 鎵ц鐩樼偣妯″紡锛氬姞杞界洏鐐规竻鍗曚綔涓哄彲閫夐」锛屼緵閫夋嫨鍚庡洖濉�
+    const taskLines =
+      isExecute.value && !data.id
+        ? await getStockTakingTaskLineSimpleList(data.taskId)
+        : [];
+    formApi.setState({ schema: useResultFormSchema(formApi, taskLines) });
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getStockTakingResult(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form />
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/result-list.vue b/src/views/wls/stocktaking/task/modules/result-list.vue
new file mode 100644
index 0000000..a686f42
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/result-list.vue
@@ -0,0 +1,136 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteStockTakingResult,
+  getStockTakingResultPage,
+} from '#/api/mes/wm/stocktaking/task/result';
+import { $t } from '#/locales';
+
+import { useResultGridColumns } from '../data';
+import ResultForm from './result-form.vue';
+
+const props = defineProps<{
+  formType: 'detail' | 'execute';
+  taskId: number;
+}>();
+
+const isExecute = computed(() => props.formType === 'execute'); // 鎵ц鐩樼偣鎬佸彲缁存姢鐩樼偣缁撴灉
+
+const [ResultFormModal, resultFormModalApi] = useVbenModal({
+  connectedComponent: ResultForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鏂板鐩樼偣缁撴灉 */
+function handleCreate() {
+  resultFormModalApi.setData({ execute: true, taskId: props.taskId }).open();
+}
+
+/** 缂栬緫鐩樼偣缁撴灉 */
+function handleEdit(row: MesWmStockTakingResultApi.StockTakingResult) {
+  resultFormModalApi.setData({ id: row.id, taskId: props.taskId }).open();
+}
+
+/** 鍒犻櫎鐩樼偣缁撴灉 */
+async function handleDelete(row: MesWmStockTakingResultApi.StockTakingResult) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteStockTakingResult(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useResultGridColumns(isExecute.value),
+    height: 360,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }) => {
+          if (!props.taskId) {
+            return { list: [], total: 0 };
+          }
+          return await getStockTakingResultPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            taskId: props.taskId,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmStockTakingResultApi.StockTakingResult>,
+});
+
+defineExpose({ refresh: handleRefresh });
+</script>
+
+<template>
+  <div>
+    <ResultFormModal @success="handleRefresh" />
+    <Grid table-title="鐩樼偣缁撴灉">
+      <template v-if="isExecute" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.create'),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-stock-taking-task:update'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/transfer/data.ts b/src/views/wls/transfer/data.ts
new file mode 100644
index 0000000..994927c
--- /dev/null
+++ b/src/views/wls/transfer/data.ts
@@ -0,0 +1,647 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  WlsTransferTypeEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType =
+  | 'confirm'
+  | 'create'
+  | 'detail'
+  | 'finish'
+  | 'stock'
+  | 'update';
+
+/** 琛ㄥご鏄惁鍙锛堜笂鏋躲�佺‘璁ゃ�佹墽琛屻�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+  return ['confirm', 'detail', 'finish', 'stock'].includes(formType);
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  const headerReadonly = isHeaderReadonly(formType);
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'status',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '杞Щ鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ヨ浆绉诲崟缂栧彿',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'create' || formType === 'update'
+          ? () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.TRANSFER_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鐢熸垚' },
+              )
+          : undefined,
+    },
+    {
+      fieldName: 'name',
+      label: '杞Щ鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ヨ浆绉诲崟鍚嶇О',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'type',
+      label: '杞Щ鍗曠被鍨�',
+      component: 'Select',
+      componentProps: {
+        disabled: headerReadonly,
+        options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨杞Щ鍗曠被鍨�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'transferDate',
+      label: '杞Щ鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        class: '!w-full',
+        disabled: headerReadonly,
+        format: 'YYYY-MM-DD',
+        placeholder: '璇烽�夋嫨杞Щ鏃ユ湡',
+        valueFormat: 'YYYY-MM-DD 00:00:00',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'deliveryFlag',
+      label: '鏄惁閰嶉��',
+      component: 'Switch',
+      componentProps: {
+        disabled: headerReadonly,
+      },
+      rules: z.boolean().default(false),
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsTransferTypeEnum.OUTER,
+      },
+    },
+    {
+      fieldName: 'confirmFlag',
+      label: '鏄惁纭',
+      component: 'Switch',
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['type'],
+        show: (values) => values.type === WlsTransferTypeEnum.OUTER,
+      },
+    },
+    {
+      fieldName: 'recipientName',
+      label: '鏀惰揣浜�',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ユ敹璐т汉',
+      },
+      dependencies: {
+        triggerFields: ['type', 'deliveryFlag'],
+        show: (values) =>
+          values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+      },
+    },
+    {
+      fieldName: 'recipientTelephone',
+      label: '鑱旂郴鐢佃瘽',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+      },
+      dependencies: {
+        triggerFields: ['type', 'deliveryFlag'],
+        show: (values) =>
+          values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+      },
+    },
+    {
+      fieldName: 'carrier',
+      label: '鎵胯繍鍟�',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ユ壙杩愬晢',
+      },
+      dependencies: {
+        triggerFields: ['type', 'deliveryFlag'],
+        show: (values) =>
+          values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+      },
+    },
+    {
+      fieldName: 'shippingNumber',
+      label: '杩愯緭鍗曞彿',
+      component: 'Input',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ヨ繍杈撳崟鍙�',
+      },
+      dependencies: {
+        triggerFields: ['type', 'deliveryFlag'],
+        show: (values) =>
+          values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+      },
+    },
+    {
+      fieldName: 'destinationAddress',
+      label: '鐩殑鍦�',
+      component: 'Input',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ョ洰鐨勫湴',
+      },
+      dependencies: {
+        triggerFields: ['type', 'deliveryFlag'],
+        show: (values) =>
+          values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        disabled: headerReadonly,
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '杞Щ鍗曠紪鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヨ浆绉诲崟缂栧彿',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '杞Щ鍗曞悕绉�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヨ浆绉诲崟鍚嶇О',
+      },
+    },
+    {
+      fieldName: 'type',
+      label: '杞Щ鍗曠被鍨�',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_TYPE, 'number'),
+        placeholder: '璇烽�夋嫨杞Щ鍗曠被鍨�',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鍗曟嵁鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmTransferApi.Transfer>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '杞Щ鍗曠紪鍙�',
+      minWidth: 160,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '杞Щ鍗曞悕绉�',
+      minWidth: 160,
+    },
+    {
+      field: 'type',
+      title: '杞Щ鍗曠被鍨�',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_TRANSFER_TYPE },
+      },
+    },
+    {
+      field: 'deliveryFlag',
+      title: '鏄惁閰嶉��',
+      width: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'transferDate',
+      title: '杞Щ鏃ユ湡',
+      width: 180,
+      formatter: 'formatDate',
+    },
+    {
+      field: 'status',
+      title: '鍗曟嵁鐘舵��',
+      minWidth: 110,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MES_WM_TRANSFER_STATUS },
+      },
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 杞Щ鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+  editable: boolean,
+  stockable: boolean,
+): VxeTableGridOptions<MesWmTransferLineApi.TransferLine>['columns'] {
+  return [
+    {
+      type: 'expand',
+      width: 48,
+      slots: { content: 'detail' },
+    },
+    {
+      field: 'itemCode',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 120,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 80,
+    },
+    {
+      field: 'quantity',
+      title: '杞Щ鏁伴噺',
+      width: 100,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 120,
+    },
+    {
+      field: 'fromWarehouseName',
+      title: '绉诲嚭浠撳簱',
+      minWidth: 120,
+    },
+    {
+      field: 'fromLocationName',
+      title: '绉诲嚭搴撳尯',
+      minWidth: 120,
+    },
+    {
+      field: 'fromAreaName',
+      title: '绉诲嚭搴撲綅',
+      minWidth: 120,
+    },
+    ...(editable || stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 180,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 杞Щ鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'quantityMax',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'batchId',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'materialStockId',
+      label: '閫夋嫨搴撳瓨',
+      component: markRaw(WmMaterialStockSelect),
+      componentProps: {
+        // 閫夋嫨搴撳瓨鍚庯紝鑷姩鍥炲~鐗╂枡/浠撳簱浣嶇疆/鎵规/鏁伴噺
+        onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+          await formApi?.setValues({
+            batchCode: stock?.batchCode,
+            batchId: stock?.batchId,
+            fromAreaId: stock?.areaId,
+            fromLocationId: stock?.locationId,
+            fromWarehouseId: stock?.warehouseId,
+            itemId: stock?.itemId,
+            quantity: stock?.quantity,
+            quantityMax: stock?.quantity,
+          });
+        },
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '杞Щ鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヨ浆绉绘暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+      dependencies: {
+        triggerFields: ['quantityMax'],
+        componentProps: (values) => ({
+          class: '!w-full',
+          max: values.quantityMax,
+          min: 0,
+          placeholder: '璇疯緭鍏ヨ浆绉绘暟閲�',
+          precision: 2,
+        }),
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'fromWarehouseId',
+      label: '绉诲嚭浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'fromLocationId',
+      label: '绉诲嚭搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['fromWarehouseId'],
+        componentProps: (values) => ({
+          disabled: true,
+          warehouseId: values.fromWarehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'fromAreaId',
+      label: '绉诲嚭搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['fromLocationId'],
+        componentProps: (values) => ({
+          disabled: true,
+          locationId: values.fromLocationId,
+        }),
+      },
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 璋冩嫧鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+  stockable: boolean,
+): VxeTableGridOptions<MesWmTransferDetailApi.TransferDetail>['columns'] {
+  return [
+    {
+      field: 'toWarehouseName',
+      title: '绉诲叆浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'toLocationName',
+      title: '绉诲叆搴撳尯',
+      minWidth: 100,
+    },
+    {
+      field: 'toAreaName',
+      title: '绉诲叆搴撲綅',
+      minWidth: 100,
+    },
+    {
+      field: 'quantity',
+      title: '鏁伴噺',
+      width: 100,
+    },
+    ...(stockable
+      ? [
+          {
+            title: '鎿嶄綔',
+            width: 120,
+            fixed: 'right',
+            slots: { default: 'actions' },
+          } as const,
+        ]
+      : []),
+  ];
+}
+
+/** 璋冩嫧鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      fieldName: 'toWarehouseId',
+      label: '绉诲叆浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨绉诲叆浠撳簱',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'toLocationId',
+      label: '绉诲叆搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨绉诲叆搴撳尯',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['toWarehouseId'],
+        componentProps: (values) => ({
+          placeholder: '璇烽�夋嫨绉诲叆搴撳尯',
+          warehouseId: values.toWarehouseId,
+        }),
+      },
+    },
+    {
+      fieldName: 'toAreaId',
+      label: '绉诲叆搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨绉诲叆搴撲綅',
+      },
+      rules: 'selectRequired',
+      dependencies: {
+        triggerFields: ['toLocationId'],
+        componentProps: (values) => ({
+          locationId: values.toLocationId,
+          placeholder: '璇烽�夋嫨绉诲叆搴撲綅',
+        }),
+      },
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
diff --git a/src/views/wls/transfer/index.vue b/src/views/wls/transfer/index.vue
new file mode 100644
index 0000000..fba3efa
--- /dev/null
+++ b/src/views/wls/transfer/index.vue
@@ -0,0 +1,214 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmTransferStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  cancelTransfer,
+  deleteTransfer,
+  exportTransfer,
+  getTransferPage,
+} from '#/api/mes/wm/transfer';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓杞Щ鍗� */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅杞Щ鍗曡鎯� */
+function handleDetail(row: MesWmTransferApi.Transfer) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫杞Щ鍗� */
+function handleEdit(row: MesWmTransferApi.Transfer) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒拌揣纭 */
+function handleConfirm(row: MesWmTransferApi.Transfer) {
+  formModalApi.setData({ formType: 'confirm', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmTransferApi.Transfer) {
+  formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц杞Щ */
+function handleFinish(row: MesWmTransferApi.Transfer) {
+  formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎杞Щ鍗� */
+async function handleDelete(row: MesWmTransferApi.Transfer) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.code]),
+    duration: 0,
+  });
+  try {
+    await deleteTransfer(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鍙栨秷杞Щ鍗� */
+async function handleCancel(row: MesWmTransferApi.Transfer) {
+  await cancelTransfer(row.id!);
+  message.success('鍙栨秷鎴愬姛');
+  handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportTransfer(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '杞Щ鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getTransferPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmTransferApi.Transfer>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+
+    <Grid table-title="杞Щ鍗曞垪琛�">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['杞Щ鍗�']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-transfer:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mes:wm-transfer:export'],
+              onClick: handleExport,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-transfer:update'],
+              ifShow: row.status === MesWmTransferStatusEnum.PREPARE,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-transfer:delete'],
+              ifShow: row.status === MesWmTransferStatusEnum.PREPARE,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鍒拌揣纭',
+              type: 'link',
+              auth: ['mes:wm-transfer:update'],
+              ifShow: row.status === MesWmTransferStatusEnum.UNCONFIRMED,
+              onClick: handleConfirm.bind(null, row),
+            },
+            {
+              label: '鎵ц涓婃灦',
+              type: 'link',
+              auth: ['mes:wm-transfer:update'],
+              ifShow: row.status === MesWmTransferStatusEnum.APPROVING,
+              onClick: handleStock.bind(null, row),
+            },
+            {
+              label: '鎵ц杞Щ',
+              type: 'link',
+              auth: ['mes:wm-transfer:finish'],
+              ifShow: row.status === MesWmTransferStatusEnum.APPROVED,
+              onClick: handleFinish.bind(null, row),
+            },
+            {
+              label: '鍙栨秷',
+              type: 'link',
+              danger: true,
+              auth: ['mes:wm-transfer:update'],
+              ifShow:
+                row.status === MesWmTransferStatusEnum.UNCONFIRMED ||
+                row.status === MesWmTransferStatusEnum.APPROVING ||
+                row.status === MesWmTransferStatusEnum.APPROVED,
+              popConfirm: {
+                title: '纭鍙栨秷璇ヨ浆绉诲崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+                confirm: handleCancel.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/transfer/modules/detail-form.vue b/src/views/wls/transfer/modules/detail-form.vue
new file mode 100644
index 0000000..8c2dbd6
--- /dev/null
+++ b/src/views/wls/transfer/modules/detail-form.vue
@@ -0,0 +1,103 @@
+<script lang="ts" setup>
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createTransferDetail,
+  getTransferDetail,
+  updateTransferDetail,
+} from '#/api/mes/wm/transfer/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmTransferDetailApi.TransferDetail>();
+const transferId = ref<number>(); // 鎵�灞炶浆绉诲崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炶浆绉诲崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['璋冩嫧鏄庣粏'])
+    : $t('ui.actionTitle.create', ['璋冩嫧鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-2',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: useDetailFormSchema(),
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmTransferDetailApi.TransferDetail;
+    data.transferId = transferId.value;
+    data.lineId = lineId.value;
+    try {
+      await (formData.value?.id
+        ? updateTransferDetail({ ...data, id: formData.value.id })
+        : createTransferDetail(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success', lineId.value!);
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      detailId?: number;
+      itemId?: number;
+      lineId: number;
+      transferId: number;
+    }>();
+    transferId.value = data.transferId;
+    lineId.value = data.lineId;
+    if (data.detailId) {
+      modalApi.lock();
+      try {
+        formData.value = await getTransferDetail(data.detailId);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    } else if (data.itemId) {
+      await formApi.setFieldValue('itemId', data.itemId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-2/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/transfer/modules/detail-list.vue b/src/views/wls/transfer/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/transfer/modules/detail-list.vue
copy to src/views/wls/transfer/modules/detail-list.vue
diff --git a/src/views/wls/transfer/modules/form.vue b/src/views/wls/transfer/modules/form.vue
new file mode 100644
index 0000000..a433608
--- /dev/null
+++ b/src/views/wls/transfer/modules/form.vue
@@ -0,0 +1,258 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmTransferStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  confirmTransfer,
+  createTransfer,
+  finishTransfer,
+  getTransfer,
+  stockTransfer,
+  submitTransfer,
+  updateTransfer,
+} from '#/api/mes/wm/transfer';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmTransferApi.Transfer>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+  // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+  ['create', 'update'].includes(formType.value),
+);
+const isConfirm = computed(() => formType.value === 'confirm'); // 鏄惁涓哄埌璐х‘璁ゆā寮�
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岃浆绉绘ā寮�
+const canSubmit = computed(
+  () =>
+    // 缂栬緫鎬佽崏绋垮彲鎻愪氦
+    formType.value === 'update' &&
+    formData.value?.status === MesWmTransferStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+  switch (formType.value) {
+    case 'confirm': {
+      return '鍒拌揣纭';
+    }
+    case 'detail': {
+      return $t('ui.actionTitle.view', ['杞Щ鍗�']);
+    }
+    case 'finish': {
+      return '鎵ц杞Щ';
+    }
+    case 'stock': {
+      return '鎵ц涓婃灦';
+    }
+    case 'update': {
+      return $t('ui.actionTitle.edit', ['杞Щ鍗�']);
+    }
+    default: {
+      return $t('ui.actionTitle.create', ['杞Щ鍗�']);
+    }
+  }
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦杞Щ鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid || !formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    const current = JSON.stringify(await formApi.getValues());
+    if (current !== originalSnapshot.value) {
+      const data = (await formApi.getValues()) as MesWmTransferApi.Transfer;
+      await updateTransfer({ ...formData.value, ...data });
+      originalSnapshot.value = current;
+    }
+    await submitTransfer(formData.value.id);
+    message.success('鎻愪氦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鍒拌揣纭 */
+async function handleConfirm() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await confirmTransfer(formData.value.id);
+    message.success('纭鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await stockTransfer(formData.value.id);
+    message.success('涓婃灦鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+/** 鎵ц杞Щ */
+async function handleFinish() {
+  if (!formData.value?.id) {
+    return;
+  }
+  modalApi.lock();
+  try {
+    await finishTransfer(formData.value.id);
+    message.success('鎵ц鎴愬姛');
+    await modalApi.close();
+    emit('success');
+  } finally {
+    modalApi.unlock();
+  }
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!isEditable.value) {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmTransferApi.Transfer;
+    try {
+      if (formData.value?.id) {
+        await updateTransfer({ ...formData.value, ...data });
+        formData.value = { ...formData.value, ...data };
+      } else {
+        const id = await createTransfer(data);
+        formData.value = {
+          ...data,
+          id,
+          status: MesWmTransferStatusEnum.PREPARE,
+        };
+        formType.value = 'update';
+        // 鍒涘缓鎴愬姛鍚庡垏鎹㈢紪杈戞�侊紝閲嶆寕 schema 骞跺洖濉富閿� / 鐘舵��
+        formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+        await formApi.setValues(formData.value);
+      }
+      originalSnapshot.value = JSON.stringify(await formApi.getValues());
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      originalSnapshot.value = '';
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    modalApi.setState({ showConfirmButton: isEditable.value });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getTransfer(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+    }
+    originalSnapshot.value = JSON.stringify(await formApi.getValues());
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+    <template v-if="formData?.id">
+      <Divider>鐗╂枡淇℃伅</Divider>
+      <div class="mx-4">
+        <LineList :form-type="formType" :transfer-id="formData.id" />
+      </div>
+    </template>
+    <template #prepend-footer>
+      <div class="flex flex-auto items-center gap-2">
+        <Popconfirm
+          v-if="canSubmit"
+          title="纭鎻愪氦璇ヨ浆绉诲崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+          @confirm="handleSubmit"
+        >
+          <Button type="primary">鎻愪氦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isConfirm"
+          title="纭鍒拌揣鍚庯紝灏嗚繘鍏ュ緟涓婃灦鐘舵�侊紝鏄惁缁х画锛�"
+          @confirm="handleConfirm"
+        >
+          <Button type="primary">鍒拌揣纭</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isStock"
+          title="纭鎵ц涓婃灦锛�"
+          @confirm="handleStock"
+        >
+          <Button type="primary">鎵ц涓婃灦</Button>
+        </Popconfirm>
+        <Popconfirm
+          v-if="isFinish"
+          title="纭鎵ц璋冩嫧锛熸墽琛屽悗灏嗘洿鏂板簱瀛樸��"
+          @confirm="handleFinish"
+        >
+          <Button type="primary">鎵ц杞Щ</Button>
+        </Popconfirm>
+      </div>
+    </template>
+  </Modal>
+</template>
diff --git a/src/views/wls/transfer/modules/line-form.vue b/src/views/wls/transfer/modules/line-form.vue
new file mode 100644
index 0000000..95b14c6
--- /dev/null
+++ b/src/views/wls/transfer/modules/line-form.vue
@@ -0,0 +1,102 @@
+<script lang="ts" setup>
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { getMaterialStock } from '#/api/mes/wm/materialstock';
+import {
+  createTransferLine,
+  getTransferLine,
+  updateTransferLine,
+} from '#/api/mes/wm/transfer/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmTransferLineApi.TransferLine>();
+const transferId = ref<number>(); // 鎵�灞炶浆绉诲崟缂栧彿
+
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['璋冩嫧鐗╂枡'])
+    : $t('ui.actionTitle.create', ['璋冩嫧鐗╂枡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 90,
+  },
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmTransferLineApi.TransferLine;
+    data.transferId = transferId.value;
+    try {
+      await (formData.value?.id
+        ? updateTransferLine({ ...data, id: formData.value.id })
+        : createTransferLine(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    formApi.setState({ schema: useLineFormSchema(formApi) });
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ id?: number; transferId: number }>();
+    transferId.value = data.transferId;
+    if (!data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getTransferLine(data.id);
+      // 缂栬緫鏃舵寜褰撳墠搴撳瓨鍦ㄥ簱閲忎綔涓烘暟閲忎笂闄�
+      let quantityMax: number | undefined;
+      if (formData.value.materialStockId) {
+        const stock = await getMaterialStock(formData.value.materialStockId);
+        quantityMax = stock?.quantity;
+      }
+      // 璁剧疆鍒� values
+      await formApi.setValues({ ...formData.value, quantityMax });
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+  </Modal>
+</template>
diff --git a/src/views/wls/transfer/modules/line-list.vue b/src/views/wls/transfer/modules/line-list.vue
new file mode 100644
index 0000000..827f2c6
--- /dev/null
+++ b/src/views/wls/transfer/modules/line-list.vue
@@ -0,0 +1,228 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getTransferDetailListByLineId } from '#/api/mes/wm/transfer/detail';
+import {
+  deleteTransferLine,
+  getTransferLineList,
+} from '#/api/mes/wm/transfer/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+  formType: FormType;
+  transferId: number;
+}>();
+
+const isEditable = computed(() =>
+  ['create', 'update'].includes(props.formType),
+); // 鏄惁鍙紪杈戣皟鎷ㄧ墿鏂欒
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+  Record<number, MesWmTransferDetailApi.TransferDetail[]>
+>({}); // 宸插睍寮�琛岀殑璋冩嫧鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+  connectedComponent: LineForm,
+  destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+  connectedComponent: DetailForm,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  for (const id of Object.keys(detailMap)) {
+    delete detailMap[Number(id)];
+  }
+  gridApi.query();
+}
+
+/** 娣诲姞璋冩嫧鐗╂枡 */
+function handleCreate() {
+  lineFormModalApi.setData({ transferId: props.transferId }).open();
+}
+
+/** 缂栬緫璋冩嫧鐗╂枡 */
+function handleEdit(row: MesWmTransferLineApi.TransferLine) {
+  lineFormModalApi.setData({ id: row.id, transferId: props.transferId }).open();
+}
+
+/** 鍒犻櫎璋冩嫧鐗╂枡 */
+async function handleDelete(row: MesWmTransferLineApi.TransferLine) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.itemName]),
+    duration: 0,
+  });
+  try {
+    await deleteTransferLine(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鏂板琛ㄥ崟 */
+function handleStock(row: MesWmTransferLineApi.TransferLine) {
+  openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑璋冩嫧鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+  detailFormModalApi
+    .setData({ detailId, itemId, lineId, transferId: props.transferId })
+    .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑璋冩嫧鏄庣粏 */
+function getExpandedDetails(row: MesWmTransferLineApi.TransferLine) {
+  return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑璋冩嫧鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+  detailMap[lineId] = await getTransferDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞借皟鎷ㄦ槑缁� */
+async function handleExpandChange(
+  row: MesWmTransferLineApi.TransferLine,
+  expanded: boolean,
+) {
+  if (!expanded) {
+    return;
+  }
+  await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+  await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useLineGridColumns(isEditable.value, isStock.value),
+    expandConfig: {
+      padding: true,
+    },
+    height: 400,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async () => {
+          if (!props.transferId) {
+            return [];
+          }
+          return await getTransferLineList(props.transferId);
+        },
+      },
+    },
+    pagerConfig: {
+      enabled: false,
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmTransferLineApi.TransferLine>,
+  gridEvents: {
+    toggleRowExpand: ({
+      expanded,
+      row,
+    }: {
+      expanded: boolean;
+      row: MesWmTransferLineApi.TransferLine;
+    }) => {
+      handleExpandChange(row, expanded);
+    },
+  },
+});
+</script>
+
+<template>
+  <div>
+    <LineFormModal @success="handleRefresh" />
+    <DetailFormModal @success="handleDetailSuccess" />
+    <Grid table-title="鐗╂枡淇℃伅">
+      <template v-if="isEditable" #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '娣诲姞璋冩嫧鐗╂枡',
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #detail="{ row }">
+        <DetailList
+          :details="getExpandedDetails(row)"
+          :form-type="formType"
+          @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+          @refresh="loadLineDetails(row.id!)"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              ifShow: isEditable,
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              ifShow: isEditable,
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '涓婃灦',
+              type: 'link',
+              ifShow: isStock,
+              onClick: handleStock.bind(null, row),
+            },
+          ]"
+        />
+        <PrinterLabel
+          v-if="isStock"
+          :biz-code="row.batchCode"
+          :biz-id="row.batchId"
+          biz-type="BATCH"
+        />
+      </template>
+    </Grid>
+  </div>
+</template>
diff --git a/src/views/wls/warehouse/area/data.ts b/src/views/wls/warehouse/area/data.ts
new file mode 100644
index 0000000..e73ead8
--- /dev/null
+++ b/src/views/wls/warehouse/area/data.ts
@@ -0,0 +1,338 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { h } from 'vue';
+
+import {
+  CommonStatusEnum,
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鎵�灞炰粨搴�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨浠撳簱',
+        // 浠撳簱鍙樻洿鏃舵竻绌烘墍灞炲簱鍖�
+        onChange: () => formApi?.setFieldValue('locationId', undefined),
+      },
+    },
+    {
+      fieldName: 'locationId',
+      label: '鎵�灞炲簱鍖�',
+      component: 'ApiSelect',
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values) => ({
+          allowClear: true,
+          api: () =>
+            getWarehouseLocationSimpleList(values.warehouseId as number),
+          // 鏀瑰彉 warehouseId 鏃跺己鍒跺埛鏂伴�夐」
+          params: { warehouseId: values.warehouseId },
+          disabled: !values.warehouseId,
+          labelField: 'name',
+          valueField: 'id',
+          placeholder: '璇烽�夋嫨搴撳尯',
+        }),
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'code',
+      label: '搴撲綅缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_AREA_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鑷姩鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'name',
+      label: '搴撲綅鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ簱浣嶅悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'area',
+      label: '闈㈢Н锛堛帯锛�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ラ潰绉�',
+      },
+    },
+    {
+      fieldName: 'maxLoad',
+      label: '鏈�澶ц浇閲�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ユ渶澶ц浇閲�',
+      },
+    },
+    {
+      fieldName: 'positionX',
+      label: '浣嶇疆 X',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� X',
+      },
+    },
+    {
+      fieldName: 'positionY',
+      label: '浣嶇疆 Y',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� Y',
+      },
+    },
+    {
+      fieldName: 'positionZ',
+      label: '浣嶇疆 Z',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� Z',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+        placeholder: '璇烽�夋嫨',
+      },
+      rules: z.number().default(CommonStatusEnum.ENABLE),
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'allowItemMixing',
+      label: '鍏佽鐗╂枡娣锋斁',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(true),
+    },
+    {
+      fieldName: 'allowBatchMixing',
+      label: '鍏佽鎵规娣锋斁',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(true),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '搴撲綅缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '搴撲綅鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ簱浣嶅悕绉�',
+      },
+    },
+    {
+      fieldName: 'positionX',
+      label: '浣嶇疆 X',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� X',
+      },
+    },
+    {
+      fieldName: 'positionY',
+      label: '浣嶇疆 Y',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� Y',
+      },
+    },
+    {
+      fieldName: 'positionZ',
+      label: '浣嶇疆 Z',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ヤ綅缃� Z',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseAreaApi.WarehouseArea>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '搴撲綅缂栫爜',
+      minWidth: 140,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '搴撲綅鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'area',
+      title: '闈㈢Н锛堛帯锛�',
+      width: 100,
+    },
+    {
+      field: 'maxLoad',
+      title: '鏈�澶ц浇閲�',
+      width: 100,
+    },
+    {
+      field: 'positionX',
+      title: '浣嶇疆 X',
+      width: 80,
+    },
+    {
+      field: 'positionY',
+      title: '浣嶇疆 Y',
+      width: 80,
+    },
+    {
+      field: 'positionZ',
+      title: '浣嶇疆 Z',
+      width: 80,
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.COMMON_STATUS },
+      },
+    },
+    {
+      field: 'frozen',
+      title: '鍐荤粨',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 180,
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 200,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/wls/warehouse/area/index.vue b/src/views/wls/warehouse/area/index.vue
new file mode 100644
index 0000000..64b6950
--- /dev/null
+++ b/src/views/wls/warehouse/area/index.vue
@@ -0,0 +1,211 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { ref } from 'vue';
+import { useRoute } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteWarehouseArea,
+  getWarehouseAreaPage,
+} from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const route = useRoute();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 褰撳墠搴撳尯涓婁笅鏂囷紙鍚屾浠� URL 瑙f瀽锛岀‘淇濋娆℃煡璇㈠甫寰椾笂 locationId锛� */
+const currentLocation = ref<{
+  id: number;
+  name: string;
+  warehouseId: number;
+  warehouseName: string;
+}>(
+  (() => {
+    const id = Number(route.query.locationId);
+    return Number.isInteger(id) && id > 0
+      ? { id, name: '', warehouseId: 0, warehouseName: '' }
+      : { id: 0, name: '', warehouseId: 0, warehouseName: '' };
+  })(),
+);
+
+/** 寮傛鍔犺浇搴撳尯/浠撳簱鍚嶇О锛堜笉闃诲鍒楄〃鏌ヨ锛� */
+async function loadLocationName() {
+  if (!currentLocation.value.id) {
+    return;
+  }
+  try {
+    const location = await getWarehouseLocation(currentLocation.value.id);
+    currentLocation.value.name = location.name || '';
+    currentLocation.value.warehouseId = location.warehouseId || 0;
+    currentLocation.value.warehouseName = location.warehouseName || '';
+  } catch {
+    // 蹇界暐涓婄骇鍚嶇О鍔犺浇寮傚父锛屼笉褰卞搷鍒楄〃鏌ヨ
+  }
+}
+loadLocationName();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓搴撲綅 */
+function handleCreate() {
+  formModalApi
+    .setData({
+      formType: 'create',
+      locationId: currentLocation.value.id || undefined,
+      warehouseId: currentLocation.value.warehouseId || undefined,
+    })
+    .open();
+}
+
+/** 鏌ョ湅搴撲綅璇︽儏 */
+function handleDetail(row: MesWmWarehouseAreaApi.WarehouseArea) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫搴撲綅 */
+function handleEdit(row: MesWmWarehouseAreaApi.WarehouseArea) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎搴撲綅 */
+async function handleDelete(row: MesWmWarehouseAreaApi.WarehouseArea) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteWarehouseArea(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鏌ョ湅搴撲綅鏉$爜 */
+function handleBarcode(row: MesWmWarehouseAreaApi.WarehouseArea) {
+  barcodeDetailRef.value?.openByBusiness(
+    row.id!,
+    BarcodeBizTypeEnum.AREA,
+    row.code,
+    row.name,
+  );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getWarehouseAreaPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            locationId: currentLocation.value.id || undefined,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmWarehouseAreaApi.WarehouseArea>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+    <BarcodeDetail ref="barcodeDetailRef" />
+
+    <Alert
+      v-if="currentLocation.id"
+      class="mb-3"
+      :message="`褰撳墠浠撳簱/搴撳尯锛�${currentLocation.warehouseName || `#${currentLocation.warehouseId || '-'}`} / ${currentLocation.name || `#${currentLocation.id}`}`"
+      show-icon
+      type="info"
+    />
+
+    <Grid table-title="搴撲綅鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['搴撲綅']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-warehouse:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-warehouse:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-warehouse:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鏉$爜',
+              type: 'link',
+              auth: ['mes:wm-warehouse:query'],
+              onClick: handleBarcode.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/warehouse/area/modules/form.vue b/src/views/wls/warehouse/area/modules/form.vue
new file mode 100644
index 0000000..5ae39ab
--- /dev/null
+++ b/src/views/wls/warehouse/area/modules/form.vue
@@ -0,0 +1,149 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createWarehouseArea,
+  getWarehouseArea,
+  updateWarehouseArea,
+} from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseAreaApi.WarehouseArea>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['搴撲綅']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['搴撲綅'])
+    : $t('ui.actionTitle.create', ['搴撲綅']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 120,
+  },
+  wrapperClass: 'grid-cols-3',
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+  if (!formData.value?.id) {
+    return;
+  }
+  barcodeDetailRef.value?.openByBusiness(
+    formData.value.id,
+    BarcodeBizTypeEnum.AREA,
+    formData.value.code,
+    formData.value.name,
+  );
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (formType.value === 'detail') {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmWarehouseAreaApi.WarehouseArea;
+    try {
+      await (formData.value?.id
+        ? updateWarehouseArea(data)
+        : createWarehouseArea(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      formType: FormType;
+      id?: number;
+      locationId?: number;
+      warehouseId?: number;
+    }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(formType.value === 'detail');
+    modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getWarehouseArea(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+      return;
+    }
+    // 鏂板鎬侊細鏍规嵁鐖剁骇涓婁笅鏂囧甫鍏ヤ粨搴�/搴撳尯
+    let warehouseId = data?.warehouseId;
+    if (data?.locationId) {
+      if (!warehouseId) {
+        try {
+          const location = await getWarehouseLocation(data.locationId);
+          warehouseId = location.warehouseId;
+        } catch {
+          // 蹇界暐
+        }
+      }
+      await formApi.setValues({
+        warehouseId,
+        locationId: data.locationId,
+      });
+    } else if (warehouseId) {
+      await formApi.setFieldValue('warehouseId', warehouseId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+      <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+    </template>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </Modal>
+</template>
diff --git a/src/views/mes/wm/warehouse/components/area-select.vue b/src/views/wls/warehouse/components/area-select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/area-select.vue
copy to src/views/wls/warehouse/components/area-select.vue
diff --git a/src/views/mes/wm/warehouse/components/index.ts b/src/views/wls/warehouse/components/index.ts
similarity index 100%
copy from src/views/mes/wm/warehouse/components/index.ts
copy to src/views/wls/warehouse/components/index.ts
diff --git a/src/views/mes/wm/warehouse/components/location-select.vue b/src/views/wls/warehouse/components/location-select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/location-select.vue
copy to src/views/wls/warehouse/components/location-select.vue
diff --git a/src/views/mes/wm/warehouse/components/select.vue b/src/views/wls/warehouse/components/select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/select.vue
copy to src/views/wls/warehouse/components/select.vue
diff --git a/src/views/wls/warehouse/data.ts b/src/views/wls/warehouse/data.ts
new file mode 100644
index 0000000..aead660
--- /dev/null
+++ b/src/views/wls/warehouse/data.ts
@@ -0,0 +1,221 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+import type { SystemUserApi } from '#/api/system/user';
+
+import { h } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鍏宠仈鏁版嵁 */
+let userList: SystemUserApi.User[] = [];
+getSimpleUserList().then((data) => (userList = data));
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      fieldName: 'code',
+      label: '浠撳簱缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_WAREHOUSE_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鑷姩鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'name',
+      label: '浠撳簱鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'chargeUserId',
+      label: '璐熻矗浜�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getSimpleUserList,
+        labelField: 'nickname',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨璐熻矗浜�',
+      },
+    },
+    {
+      fieldName: 'address',
+      label: '浠撳簱鍦板潃',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ粨搴撳湴鍧�',
+      },
+    },
+    {
+      fieldName: 'area',
+      label: '闈㈢Н锛堛帯锛�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ラ潰绉�',
+      },
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '浠撳簱缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '浠撳簱鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�',
+      },
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          { label: '鏄�', value: true },
+          { label: '鍚�', value: false },
+        ],
+        placeholder: '璇烽�夋嫨',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseApi.Warehouse>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '浠撳簱缂栫爜',
+      minWidth: 140,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'address',
+      title: '浠撳簱鍦板潃',
+      minWidth: 180,
+    },
+    {
+      field: 'area',
+      title: '闈㈢Н锛堛帯锛�',
+      width: 110,
+    },
+    {
+      field: 'chargeUserId',
+      title: '璐熻矗浜�',
+      width: 120,
+      formatter: ({ cellValue }) =>
+        userList.find((user) => user.id === cellValue)?.nickname ?? '',
+    },
+    {
+      field: 'frozen',
+      title: '鍐荤粨',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 180,
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 220,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/wls/warehouse/index.vue b/src/views/wls/warehouse/index.vue
new file mode 100644
index 0000000..9edfabd
--- /dev/null
+++ b/src/views/wls/warehouse/index.vue
@@ -0,0 +1,175 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { deleteWarehouse, getWarehousePage } from '#/api/mes/wm/warehouse';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓浠撳簱 */
+function handleCreate() {
+  formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅浠撳簱璇︽儏 */
+function handleDetail(row: MesWmWarehouseApi.Warehouse) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫浠撳簱 */
+function handleEdit(row: MesWmWarehouseApi.Warehouse) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎浠撳簱 */
+async function handleDelete(row: MesWmWarehouseApi.Warehouse) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteWarehouse(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 璺宠浆搴撳尯鍒楄〃 */
+function handleOpenLocation(row: MesWmWarehouseApi.Warehouse) {
+  router.push({
+    name: 'WlsLocation',
+    query: { warehouseId: String(row.id) },
+  });
+}
+
+/** 鏌ョ湅浠撳簱鏉$爜 */
+function handleBarcode(row: MesWmWarehouseApi.Warehouse) {
+  barcodeDetailRef.value?.openByBusiness(
+    row.id!,
+    BarcodeBizTypeEnum.WAREHOUSE,
+    row.code,
+    row.name,
+  );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getWarehousePage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmWarehouseApi.Warehouse>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+    <BarcodeDetail ref="barcodeDetailRef" />
+
+    <Grid table-title="浠撳簱鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['浠撳簱']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-warehouse:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '搴撳尯',
+              type: 'link',
+              auth: ['mes:wm-warehouse:query'],
+              onClick: handleOpenLocation.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-warehouse:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-warehouse:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鏉$爜',
+              type: 'link',
+              auth: ['mes:wm-warehouse:query'],
+              onClick: handleBarcode.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/warehouse/location/data.ts b/src/views/wls/warehouse/location/data.ts
new file mode 100644
index 0000000..44652ff
--- /dev/null
+++ b/src/views/wls/warehouse/location/data.ts
@@ -0,0 +1,193 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { h } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鍏宠仈鏁版嵁 */
+let warehouseList: MesWmWarehouseApi.Warehouse[] = [];
+getWarehouseSimpleList().then((data) => (warehouseList = data));
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+  formType: FormType,
+  formApi?: VbenFormApi,
+): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: { triggerFields: [''], show: () => false },
+    },
+    {
+      fieldName: 'code',
+      label: '搴撳尯缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�',
+      },
+      rules: 'required',
+      suffix:
+        formType === 'detail'
+          ? undefined
+          : () =>
+              h(
+                Button,
+                {
+                  type: 'default',
+                  onClick: async () => {
+                    const code = await generateAutoCode(
+                      MesAutoCodeRuleCode.WM_LOCATION_CODE,
+                    );
+                    await formApi?.setFieldValue('code', code);
+                  },
+                },
+                { default: () => '鑷姩鐢熸垚' },
+              ),
+    },
+    {
+      fieldName: 'name',
+      label: '搴撳尯鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '鎵�灞炰粨搴�',
+      component: 'ApiSelect',
+      componentProps: {
+        allowClear: true,
+        api: getWarehouseSimpleList,
+        labelField: 'name',
+        valueField: 'id',
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'area',
+      label: '闈㈢Н锛堛帯锛�',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ラ潰绉�',
+      },
+    },
+    {
+      fieldName: 'frozen',
+      label: '鏄惁鍐荤粨',
+      component: 'Switch',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      fieldName: 'remark',
+      label: '澶囨敞',
+      component: 'Textarea',
+      formItemClass: 'col-span-3',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '搴撳尯缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '搴撳尯鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseLocationApi.WarehouseLocation>['columns'] {
+  return [
+    {
+      field: 'code',
+      title: '搴撳尯缂栫爜',
+      minWidth: 140,
+      slots: { default: 'code' },
+    },
+    {
+      field: 'name',
+      title: '搴撳尯鍚嶇О',
+      minWidth: 160,
+    },
+    {
+      field: 'warehouseId',
+      title: '鎵�灞炰粨搴�',
+      minWidth: 140,
+      formatter: ({ cellValue }) =>
+        warehouseList.find((w) => w.id === cellValue)?.name ?? '',
+    },
+    {
+      field: 'area',
+      title: '闈㈢Н锛堛帯锛�',
+      width: 110,
+    },
+    {
+      field: 'frozen',
+      title: '鍐荤粨',
+      width: 90,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+      },
+    },
+    {
+      field: 'remark',
+      title: '澶囨敞',
+      minWidth: 180,
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 240,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/wls/warehouse/location/index.vue b/src/views/wls/warehouse/location/index.vue
new file mode 100644
index 0000000..9de6261
--- /dev/null
+++ b/src/views/wls/warehouse/location/index.vue
@@ -0,0 +1,218 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getWarehouse } from '#/api/mes/wm/warehouse';
+import {
+  deleteWarehouseLocation,
+  getWarehouseLocationPage,
+} from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const route = useRoute();
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 褰撳墠浠撳簱涓婁笅鏂囷紙鍚屾浠� URL 瑙f瀽锛岀‘淇濋娆℃煡璇㈠甫寰椾笂 warehouseId锛� */
+const currentWarehouse = ref<{ id: number; name: string }>(
+  (() => {
+    const id = Number(route.query.warehouseId);
+    return Number.isInteger(id) && id > 0
+      ? { id, name: '' }
+      : { id: 0, name: '' };
+  })(),
+);
+
+/** 寮傛鍔犺浇浠撳簱鍚嶇О锛堜笉闃诲鍒楄〃鏌ヨ锛� */
+async function loadWarehouseName() {
+  if (!currentWarehouse.value.id) {
+    return;
+  }
+  try {
+    const warehouse = await getWarehouse(currentWarehouse.value.id);
+    currentWarehouse.value.name = warehouse.name || '';
+  } catch {
+    // 蹇界暐涓婄骇鍚嶇О鍔犺浇寮傚父锛屼笉褰卞搷鍒楄〃鏌ヨ
+  }
+}
+loadWarehouseName();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 鍒涘缓搴撳尯 */
+function handleCreate() {
+  formModalApi
+    .setData({
+      formType: 'create',
+      warehouseId: currentWarehouse.value.id || undefined,
+    })
+    .open();
+}
+
+/** 鏌ョ湅搴撳尯璇︽儏 */
+function handleDetail(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+  formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫搴撳尯 */
+function handleEdit(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+  formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎搴撳尯 */
+async function handleDelete(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteWarehouseLocation(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 璺宠浆搴撲綅鍒楄〃 */
+function handleOpenArea(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+  router.push({
+    name: 'WlsArea',
+    query: { locationId: String(row.id) },
+  });
+}
+
+/** 鏌ョ湅搴撳尯鏉$爜 */
+function handleBarcode(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+  barcodeDetailRef.value?.openByBusiness(
+    row.id!,
+    BarcodeBizTypeEnum.LOCATION,
+    row.code,
+    row.name,
+  );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getWarehouseLocationPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            warehouseId: currentWarehouse.value.id || undefined,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmWarehouseLocationApi.WarehouseLocation>,
+});
+</script>
+
+<template>
+  <Page auto-content-height><FormModal @success="handleRefresh" />
+    <BarcodeDetail ref="barcodeDetailRef" />
+
+    <Alert
+      v-if="currentWarehouse.id"
+      class="mb-3"
+      :message="`褰撳墠浠撳簱锛�${currentWarehouse.name || `#${currentWarehouse.id}`}`"
+      show-icon
+      type="info"
+    />
+
+    <Grid table-title="搴撳尯鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['搴撳尯']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mes:wm-warehouse:create'],
+              onClick: handleCreate,
+            },
+          ]"
+        />
+      </template>
+      <template #code="{ row }">
+        <Button type="link" @click="handleDetail(row)">
+          {{ row.code }}
+        </Button>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '搴撲綅',
+              type: 'link',
+              auth: ['mes:wm-warehouse:query'],
+              onClick: handleOpenArea.bind(null, row),
+            },
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-warehouse:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mes:wm-warehouse:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+            {
+              label: '鏉$爜',
+              type: 'link',
+              auth: ['mes:wm-warehouse:query'],
+              onClick: handleBarcode.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/warehouse/location/modules/form.vue b/src/views/wls/warehouse/location/modules/form.vue
new file mode 100644
index 0000000..430edcc
--- /dev/null
+++ b/src/views/wls/warehouse/location/modules/form.vue
@@ -0,0 +1,184 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createWarehouseLocation,
+  getWarehouseLocation,
+  updateAreaByLocationId,
+  updateWarehouseLocation,
+} from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseLocationApi.WarehouseLocation>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['搴撳尯']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['搴撳尯'])
+    : $t('ui.actionTitle.create', ['搴撳尯']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  wrapperClass: 'grid-cols-3',
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+  if (!formData.value?.id) {
+    return;
+  }
+  barcodeDetailRef.value?.openByBusiness(
+    formData.value.id,
+    BarcodeBizTypeEnum.LOCATION,
+    formData.value.code,
+    formData.value.name,
+  );
+}
+
+/** 鎵归噺璁剧疆搴撲綅娣锋斁瑙勫垯 */
+async function setMixingRule(type: 'batch' | 'item', allow: boolean) {
+  if (!formData.value?.id) {
+    return;
+  }
+  const text = type === 'item' ? '浜у搧娣锋斁' : '鎵规娣锋斁';
+  try {
+    await confirm(`纭瑕侀噸缃簱鍖轰笅鎵�鏈夊簱浣嶇殑${text}瑙勫垯鍚楋紵`);
+  } catch {
+    return;
+  }
+  await updateAreaByLocationId(
+    formData.value.id,
+    type === 'item' ? allow : undefined,
+    type === 'batch' ? allow : undefined,
+  );
+  message.success('璁剧疆鎴愬姛');
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (formType.value === 'detail') {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data =
+      (await formApi.getValues()) as MesWmWarehouseLocationApi.WarehouseLocation;
+    try {
+      await (formData.value?.id
+        ? updateWarehouseLocation(data)
+        : createWarehouseLocation(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{
+      formType: FormType;
+      id?: number;
+      warehouseId?: number;
+    }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(formType.value === 'detail');
+    modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+    if (data?.id) {
+      modalApi.lock();
+      try {
+        formData.value = await getWarehouseLocation(data.id);
+        // 璁剧疆鍒� values
+        await formApi.setValues(formData.value);
+      } finally {
+        modalApi.unlock();
+      }
+      return;
+    }
+    if (data?.warehouseId) {
+      await formApi.setFieldValue('warehouseId', data.warehouseId);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <div
+      v-if="formType === 'update' && formData?.id"
+      class="mx-4 mt-2 border-t pt-4"
+    >
+      <div class="mb-2 font-medium">搴撲綅娣锋斁瑙勫垯锛堟壒閲忚缃級</div>
+      <div class="flex flex-wrap items-center gap-3">
+        <span class="w-20">浜у搧娣锋斁</span>
+        <Button
+          size="small"
+          type="primary"
+          @click="setMixingRule('item', true)"
+        >
+          鍏佽
+        </Button>
+        <Button size="small" danger @click="setMixingRule('item', false)">
+          涓嶅厑璁�
+        </Button>
+      </div>
+      <div class="mt-2 flex flex-wrap items-center gap-3">
+        <span class="w-20">鎵规娣锋斁</span>
+        <Button
+          size="small"
+          type="primary"
+          @click="setMixingRule('batch', true)"
+        >
+          鍏佽
+        </Button>
+        <Button size="small" danger @click="setMixingRule('batch', false)">
+          涓嶅厑璁�
+        </Button>
+      </div>
+    </div>
+    <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+      <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+    </template>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </Modal>
+</template>
diff --git a/src/views/wls/warehouse/modules/form.vue b/src/views/wls/warehouse/modules/form.vue
new file mode 100644
index 0000000..b20c558
--- /dev/null
+++ b/src/views/wls/warehouse/modules/form.vue
@@ -0,0 +1,124 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+  createWarehouse,
+  getWarehouse,
+  updateWarehouse,
+} from '#/api/mes/wm/warehouse';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseApi.Warehouse>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+  if (formType.value === 'detail') {
+    return $t('ui.actionTitle.view', ['浠撳簱']);
+  }
+  return formType.value === 'update'
+    ? $t('ui.actionTitle.edit', ['浠撳簱'])
+    : $t('ui.actionTitle.create', ['浠撳簱']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 110,
+  },
+  wrapperClass: 'grid-cols-3',
+  layout: 'horizontal',
+  schema: [],
+  showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+  if (!formData.value?.id) {
+    return;
+  }
+  barcodeDetailRef.value?.openByBusiness(
+    formData.value.id,
+    BarcodeBizTypeEnum.WAREHOUSE,
+    formData.value.code,
+    formData.value.name,
+  );
+}
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (formType.value === 'detail') {
+      await modalApi.close();
+      return;
+    }
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 鎻愪氦琛ㄥ崟
+    const data = (await formApi.getValues()) as MesWmWarehouseApi.Warehouse;
+    try {
+      await (formData.value?.id
+        ? updateWarehouse(data)
+        : createWarehouse(data));
+      // 鍏抽棴骞舵彁绀�
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    // 鍔犺浇鏁版嵁
+    const data = modalApi.getData<{ formType: FormType; id?: number }>();
+    formType.value = data.formType;
+    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+    formApi.setDisabled(formType.value === 'detail');
+    modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+    if (!data?.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getWarehouse(data.id);
+      // 璁剧疆鍒� values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle" class="w-3/5">
+    <Form class="mx-4" />
+    <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+      <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+    </template>
+    <BarcodeDetail ref="barcodeDetailRef" />
+  </Modal>
+</template>
diff --git a/vite.config.ts b/vite.config.ts
index 97be055..22580b3 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -16,7 +16,7 @@
           "/admin-api": {
             changeOrigin: true,
             rewrite: (path) => path.replace(/^\/admin-api/, ""),
-            target: "http://192.168.0.244:48080/admin-api",
+            target: "http://192.168.0.226:48080/admin-api",
             ws: true,
           },
         },

--
Gitblit v1.9.3