gaoluyang
2026-07-09 558552aba0f99081a670dc29169989f186a7c7cd
银川
1.仓储物流页面开发、联调
已复制25个文件
已添加204个文件
已重命名161个文件
已修改1个文件
34591 ■■■■■ 文件已修改
src/api/erp/warehouseMapping/index.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/approval/index.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/approval/index.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/arrivalnotice/index.ts 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/arrivalnotice/line/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/barcode/config/index.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/barcode/index.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/batch/index.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/itemconsume/line/index.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/itemreceipt/detail/index.ts 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/itemreceipt/index.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/itemreceipt/line/index.ts 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/materialstock/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/miscissue/index.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/miscissue/line/index.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/miscreceipt/index.ts 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/miscreceipt/line/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourceissue/detail/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourceissue/index.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourceissue/line/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourcereceipt/detail/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourcereceipt/index.ts 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/outsourcereceipt/line/index.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/packages/index.ts 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/packages/line/index.ts 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productissue/detail/index.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productissue/index.ts 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productissue/line/index.ts 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productproduce/line/index.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productreceipt/detail/index.ts 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productreceipt/index.ts 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productreceipt/line/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productsales/detail/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productsales/index.ts 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productsales/line/index.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnissue/detail/index.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnissue/index.ts 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnissue/line/index.ts 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnsales/detail/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnsales/index.ts 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnsales/line/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnvendor/detail/index.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnvendor/index.ts 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/returnvendor/line/index.ts 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/salesnotice/index.ts 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/salesnotice/line/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/sn/index.ts 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/stocktaking/plan/index.ts 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/stocktaking/plan/param/index.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/stocktaking/task/index.ts 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/stocktaking/task/line/index.ts 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/stocktaking/task/result/index.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/transfer/detail/index.ts 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/transfer/index.ts 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/transfer/line/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/warehouse/area/index.ts 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/warehouse/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/warehouse/location/index.ts 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wms/approval/index.ts 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/components/line-select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/components/line-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/arrivalnotice/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/components/barcode.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/components/detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/components/printer-label.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/config/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/config/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/config/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/barcode/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/batch/components/detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/batch/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/batch/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/batch/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/batch/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select-dialog.vue 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/stock/warehouseMapping/data.ts 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/stock/warehouseMapping/index.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/stock/warehouseMapping/modules/form.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/itemreceipt/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/materialstock/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/materialstock/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/materialstock/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/materialstock/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/materialstock/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscissue/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscissue/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscissue/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscissue/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscissue/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscreceipt/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscreceipt/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscreceipt/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscreceipt/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/miscreceipt/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourceissue/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsourcereceipt/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/modules/package-line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/packages/modules/sub-package-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productissue/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productreceipt/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productsales/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnissue/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnsales/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/returnvendor/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/components/line-select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/components/line-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesnotice/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sn/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sn/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sn/modules/detail.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sn/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/components/condition-value-input.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/modules/param-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/plan/modules/param-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/modules/result-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/stocktaking/task/modules/result-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/modules/detail-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/modules/line-form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/transfer/modules/line-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/area/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/area/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/area/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/components/area-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/components/location-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/location/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/location/index.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/location/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/warehouse/modules/form.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/data.ts 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/index.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/approval/config/modules/process-config-form.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/components/line-select-dialog.vue 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/components/line-select.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/components/select-dialog.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/components/select.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/data.ts 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/index.vue 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/form.vue 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/line-form.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/arrivalnotice/modules/line-list.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/components/barcode.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/components/detail.vue 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/components/printer-label.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/config/data.ts 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/config/index.vue 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/config/modules/form.vue 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/data.ts 735 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/barcode/modules/form.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/batch/components/detail.vue 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/batch/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/batch/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/batch/components/select.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/batch/data.ts 372 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/data.ts 527 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/form.vue 242 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/line-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/line-list.vue 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/select-dialog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/select.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/data.ts 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/index.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/data.ts 452 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/index.vue 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/modules/form.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/modules/line-form.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscissue/modules/line-list.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/data.ts 422 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/index.vue 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/modules/form.vue 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/modules/line-form.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/miscreceipt/modules/line-list.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/data.ts 487 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/form.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/line-form.vue 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourceissue/modules/line-list.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/data.ts 540 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/modules/detail-form.vue 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/modules/form.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/modules/line-form.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/outsourcereceipt/modules/line-list.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/components/select-dialog.vue 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/components/select.vue 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/data.ts 641 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/index.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/modules/form.vue 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/modules/line-form.vue 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/modules/package-line-list.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/packages/modules/sub-package-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/data.ts 475 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/index.vue 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/form.vue 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/line-form.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/line-list.vue 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/data.ts 452 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/form.vue 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/line-form.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productreceipt/modules/line-list.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/data.ts 701 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/index.vue 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/detail-form.vue 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/form.vue 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/line-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/modules/line-list.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/data.ts 567 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/index.vue 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/form.vue 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/line-form.vue 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/line-list.vue 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/data.ts 515 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/index.vue 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/form.vue 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/line-form.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnsales/modules/line-list.vue 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/data.ts 530 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/detail-form.vue 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/form.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/line-form.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnvendor/modules/line-list.vue 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/components/line-select-dialog.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/components/line-select.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/components/select-dialog.vue 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/components/select.vue 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/data.ts 362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/index.vue 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/modules/form.vue 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/modules/line-form.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/modules/line-list.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/sn/data.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/sn/index.vue 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/sn/modules/detail.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/sn/modules/form.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/components/condition-value-input.vue 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/components/select-dialog.vue 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/components/select.vue 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/data.ts 472 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/index.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/modules/form.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/modules/param-form.vue 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/plan/modules/param-list.vue 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/data.ts 600 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/index.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/form.vue 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/line-list.vue 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/result-form.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/result-list.vue 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/data.ts 647 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/index.vue 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/modules/detail-form.vue 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/modules/detail-list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/modules/form.vue 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/modules/line-form.vue 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/transfer/modules/line-list.vue 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/area/data.ts 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/area/index.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/area/modules/form.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/components/area-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/components/index.ts 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/components/location-select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/components/select.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/data.ts 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/index.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/location/data.ts 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/location/index.vue 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/location/modules/form.vue 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/modules/form.vue 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 },
  });
}
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 },
    },
  );
}
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 },
    },
  );
}
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,
  });
}
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}`);
}
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}`);
}
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 },
  });
}
src/api/wls/batch/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
import 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 } },
  );
}
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 },
  );
}
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}`);
}
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,
  });
}
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}`);
}
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,
  });
}
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 });
}
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}`);
}
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,
  });
}
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}`);
}
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}`);
}
src/api/wls/outsourceissue/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
import 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}`);
}
/** æ‰§è¡Œæ‹£è´§ */
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,
  });
}
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}`);
}
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}`,
  );
}
src/api/wls/outsourcereceipt/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
import 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,
  });
}
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}`);
}
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}`,
  );
}
src/api/wls/packages/line/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
import 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}`);
}
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}`);
}
src/api/wls/productissue/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
import 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}`);
}
/** æ‰§è¡Œæ‹£è´§ */
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,
  });
}
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}`);
}
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 });
}
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}`);
}
src/api/wls/productreceipt/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
import 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,
  });
}
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}`);
}
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}`);
}
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}`);
}
/** æ‰§è¡Œæ‹£è´§ */
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}`);
}
/** æ ¡éªŒé”€å”®å‡ºåº“单拣货数量是否与出库数量一致 */
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,
  });
}
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}`);
}
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}`);
}
src/api/wls/returnissue/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
import 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,
  });
}
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}`);
}
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}`);
}
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,
  });
}
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}`);
}
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}`);
}
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}`);
}
/** æ‰§è¡Œæ‹£è´§ */
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}`);
}
/** æ ¡éªŒä¾›åº”商退货单拣货数量是否与退货数量一致 */
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,
  });
}
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}`);
}
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,
  });
}
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}`);
}
src/api/wls/sn/index.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
import 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 },
  });
}
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,
  });
}
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}`);
}
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,
  });
}
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}`);
}
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}`,
  );
}
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}`);
}
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 });
}
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}`);
}
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}`);
}
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}`);
}
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 } },
  );
}
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 },
    },
  );
}
src/views/arrivalnotice/components/index.ts
src/views/arrivalnotice/components/line-select-dialog.vue
src/views/arrivalnotice/components/line-select.vue
src/views/arrivalnotice/components/select-dialog.vue
src/views/arrivalnotice/components/select.vue
src/views/arrivalnotice/data.ts
src/views/arrivalnotice/index.vue
src/views/arrivalnotice/modules/form.vue
src/views/arrivalnotice/modules/line-form.vue
src/views/arrivalnotice/modules/line-list.vue
src/views/barcode/components/barcode.vue
src/views/barcode/components/detail.vue
src/views/barcode/components/index.ts
src/views/barcode/components/printer-label.vue
src/views/barcode/config/data.ts
src/views/barcode/config/index.vue
src/views/barcode/config/modules/form.vue
src/views/barcode/data.ts
src/views/barcode/index.vue
src/views/barcode/modules/form.vue
src/views/batch/components/detail.vue
src/views/batch/components/index.ts
src/views/batch/components/select-dialog.vue
src/views/batch/components/select.vue
src/views/batch/data.ts
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 };
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>
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>
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,
      },
    },
  ];
}
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>
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>
src/views/itemreceipt/data.ts
src/views/itemreceipt/index.vue
src/views/itemreceipt/modules/detail-form.vue
src/views/itemreceipt/modules/detail-list.vue
src/views/itemreceipt/modules/form.vue
src/views/itemreceipt/modules/line-form.vue
src/views/itemreceipt/modules/line-list.vue
src/views/materialstock/components/index.ts
src/views/materialstock/components/select-dialog.vue
src/views/materialstock/components/select.vue
src/views/materialstock/data.ts
src/views/materialstock/index.vue
src/views/miscissue/data.ts
src/views/miscissue/index.vue
src/views/miscissue/modules/form.vue
src/views/miscissue/modules/line-form.vue
src/views/miscissue/modules/line-list.vue
src/views/miscreceipt/data.ts
src/views/miscreceipt/index.vue
src/views/miscreceipt/modules/form.vue
src/views/miscreceipt/modules/line-form.vue
src/views/miscreceipt/modules/line-list.vue
src/views/outsourceissue/data.ts
src/views/outsourceissue/index.vue
src/views/outsourceissue/modules/detail-form.vue
src/views/outsourceissue/modules/detail-list.vue
src/views/outsourceissue/modules/form.vue
src/views/outsourceissue/modules/line-form.vue
src/views/outsourceissue/modules/line-list.vue
src/views/outsourcereceipt/data.ts
src/views/outsourcereceipt/index.vue
src/views/outsourcereceipt/modules/detail-form.vue
src/views/outsourcereceipt/modules/detail-list.vue
src/views/outsourcereceipt/modules/form.vue
src/views/outsourcereceipt/modules/line-form.vue
src/views/outsourcereceipt/modules/line-list.vue
src/views/packages/components/index.ts
src/views/packages/components/select-dialog.vue
src/views/packages/components/select.vue
src/views/packages/data.ts
src/views/packages/index.vue
src/views/packages/modules/form.vue
src/views/packages/modules/line-form.vue
src/views/packages/modules/package-line-list.vue
src/views/packages/modules/sub-package-list.vue
src/views/productissue/data.ts
src/views/productissue/index.vue
src/views/productissue/modules/detail-form.vue
src/views/productissue/modules/detail-list.vue
src/views/productissue/modules/form.vue
src/views/productissue/modules/line-form.vue
src/views/productissue/modules/line-list.vue
src/views/productreceipt/data.ts
src/views/productreceipt/index.vue
src/views/productreceipt/modules/detail-form.vue
src/views/productreceipt/modules/detail-list.vue
src/views/productreceipt/modules/form.vue
src/views/productreceipt/modules/line-form.vue
src/views/productreceipt/modules/line-list.vue
src/views/productsales/data.ts
src/views/productsales/index.vue
src/views/productsales/modules/detail-form.vue
src/views/productsales/modules/detail-list.vue
src/views/productsales/modules/form.vue
src/views/productsales/modules/line-form.vue
src/views/productsales/modules/line-list.vue
src/views/returnissue/data.ts
src/views/returnissue/index.vue
src/views/returnissue/modules/detail-form.vue
src/views/returnissue/modules/detail-list.vue
src/views/returnissue/modules/form.vue
src/views/returnissue/modules/line-form.vue
src/views/returnissue/modules/line-list.vue
src/views/returnsales/data.ts
src/views/returnsales/index.vue
src/views/returnsales/modules/detail-form.vue
src/views/returnsales/modules/detail-list.vue
src/views/returnsales/modules/form.vue
src/views/returnsales/modules/line-form.vue
src/views/returnsales/modules/line-list.vue
src/views/returnvendor/data.ts
src/views/returnvendor/index.vue
src/views/returnvendor/modules/detail-form.vue
src/views/returnvendor/modules/detail-list.vue
src/views/returnvendor/modules/form.vue
src/views/returnvendor/modules/line-form.vue
src/views/returnvendor/modules/line-list.vue
src/views/salesnotice/components/index.ts
src/views/salesnotice/components/line-select-dialog.vue
src/views/salesnotice/components/line-select.vue
src/views/salesnotice/components/select-dialog.vue
src/views/salesnotice/components/select.vue
src/views/salesnotice/data.ts
src/views/salesnotice/index.vue
src/views/salesnotice/modules/form.vue
src/views/salesnotice/modules/line-form.vue
src/views/salesnotice/modules/line-list.vue
src/views/sn/data.ts
src/views/sn/index.vue
src/views/sn/modules/detail.vue
src/views/sn/modules/form.vue
src/views/stocktaking/plan/components/condition-value-input.vue
src/views/stocktaking/plan/components/index.ts
src/views/stocktaking/plan/components/select-dialog.vue
src/views/stocktaking/plan/components/select.vue
src/views/stocktaking/plan/data.ts
src/views/stocktaking/plan/index.vue
src/views/stocktaking/plan/modules/form.vue
src/views/stocktaking/plan/modules/param-form.vue
src/views/stocktaking/plan/modules/param-list.vue
src/views/stocktaking/task/data.ts
src/views/stocktaking/task/index.vue
src/views/stocktaking/task/modules/form.vue
src/views/stocktaking/task/modules/line-list.vue
src/views/stocktaking/task/modules/result-form.vue
src/views/stocktaking/task/modules/result-list.vue
src/views/transfer/data.ts
src/views/transfer/index.vue
src/views/transfer/modules/detail-form.vue
src/views/transfer/modules/detail-list.vue
src/views/transfer/modules/form.vue
src/views/transfer/modules/line-form.vue
src/views/transfer/modules/line-list.vue
src/views/warehouse/area/data.ts
src/views/warehouse/area/index.vue
src/views/warehouse/area/modules/form.vue
src/views/warehouse/components/area-select.vue
src/views/warehouse/components/index.ts
src/views/warehouse/components/location-select.vue
src/views/warehouse/components/select.vue
src/views/warehouse/data.ts
src/views/warehouse/index.vue
src/views/warehouse/location/data.ts
src/views/warehouse/location/index.vue
src/views/warehouse/location/modules/form.vue
src/views/warehouse/modules/form.vue
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',
    },
  ];
}
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>
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>
src/views/wls/arrivalnotice/components/index.ts
copy from src/views/mes/wm/arrivalnotice/components/index.ts copy to src/views/wls/arrivalnotice/components/index.ts
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>
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>
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>
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>
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,
      },
    },
  ];
}
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>
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>
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>
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>
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>
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>
src/views/wls/barcode/components/index.ts
copy from src/views/mes/wm/barcode/components/index.ts copy to src/views/wls/barcode/components/index.ts
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>
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,
      },
    },
  ];
}
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>
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>
src/views/wls/barcode/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,735 @@
import 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) || '-',
    },
  ];
}
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>
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>
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>
src/views/wls/batch/components/index.ts
copy from src/views/mes/wm/batch/components/index.ts copy to src/views/wls/batch/components/index.ts
src/views/wls/batch/components/select-dialog.vue
copy from src/views/mes/wm/batch/components/select-dialog.vue copy to src/views/wls/batch/components/select-dialog.vue
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>
src/views/wls/batch/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,372 @@
import 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 || '-',
    },
  ];
}
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',
    },
  ];
}
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>
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>
src/views/wls/itemreceipt/modules/detail-list.vue
copy from src/views/mes/wm/itemreceipt/modules/detail-list.vue copy to src/views/wls/itemreceipt/modules/detail-list.vue
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>
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>
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>
src/views/wls/materialstock/components/index.ts
copy from src/views/mes/wm/materialstock/components/index.ts copy to src/views/wls/materialstock/components/index.ts
src/views/wls/materialstock/components/select-dialog.vue
copy from src/views/mes/wm/materialstock/components/select-dialog.vue copy to src/views/wls/materialstock/components/select-dialog.vue
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>
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 },
      },
    },
  ];
}
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 ? '冻结' : '解冻';
  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>
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,
      },
    },
  ];
}
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>
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>
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>
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>
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,
      },
    },
  ];
}
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>
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>
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>
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>
src/views/wls/outsourceissue/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,487 @@
import 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,
      },
    },
  ];
}
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();
}
/** æ‰§è¡Œæ‹£è´§ */
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: '执行拣货',
              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>
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>
src/views/wls/outsourceissue/modules/detail-list.vue
copy from src/views/mes/wm/outsourceissue/modules/detail-list.vue copy to src/views/wls/outsourceissue/modules/detail-list.vue
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 '执行拣货';
  }
  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();
  }
}
/** æ‰§è¡Œæ‹£è´§ï¼šæ‹£è´§æ•°é‡ä¸Žå‘料数量不一致时二次确认 */
async function handleStock() {
  if (!formData.value?.id) {
    return;
  }
  const quantityMatch = await checkOutsourceIssueQuantity(formData.value.id);
  if (!quantityMatch) {
    try {
      await confirm('发料数量与拣货数量不一致,确认执行拣货?');
    } catch {
      return;
    }
  }
  modalApi.lock();
  try {
    await stockOutsourceIssue(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 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="确认执行拣货?"
          @confirm="handleStock"
        >
          <Button type="primary">执行拣货</Button>
        </Popconfirm>
        <Popconfirm
          v-if="isFinish"
          title="确认执行领出?执行后将扣减库存,且无法撤销。"
          @confirm="handleFinish"
        >
          <Button type="primary">执行领出</Button>
        </Popconfirm>
      </div>
    </template>
  </Modal>
</template>
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>
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();
  }
}
/** æ‹£è´§ï¼šç›´æŽ¥æ‰“开明细创建表单 */
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: '拣货',
              type: 'link',
              ifShow: isStock,
              onClick: handlePicking.bind(null, row),
            },
          ]"
        />
      </template>
    </Grid>
  </div>
</template>
src/views/wls/outsourcereceipt/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,540 @@
import 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,
      },
    },
  ];
}
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>
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>
src/views/wls/outsourcereceipt/modules/detail-list.vue
copy from src/views/mes/wm/outsourcereceipt/modules/detail-list.vue copy to src/views/wls/outsourcereceipt/modules/detail-list.vue
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>
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>
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>
src/views/wls/packages/components/index.ts
copy from src/views/mes/wm/packages/components/index.ts copy to src/views/wls/packages/components/index.ts
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>
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>
src/views/wls/packages/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,641 @@
import 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 },
      },
    },
  ];
}
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>
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',
});
/** æäº¤è¡¨å•(create/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>
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>
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>
src/views/wls/packages/modules/sub-package-list.vue
copy from src/views/mes/wm/packages/modules/sub-package-list.vue copy to src/views/wls/packages/modules/sub-package-list.vue
src/views/wls/productissue/data.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,475 @@
import 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,
      },
    },
  ];
}
/** æ‹£è´§æ˜Žç»†å­è¡¨çš„字段 */
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,
        ]
      : []),
  ];
}
/** æ‹£è´§æ˜Žç»†æ–°å¢ž/修改的表单 */
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,
      },
    },
  ];
}
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();
}
/** æ‰§è¡Œæ‹£è´§ */
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: '执行拣货',
              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>
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', ['拣货明细'])
    : $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 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>
src/views/wls/productissue/modules/detail-list.vue
copy from src/views/mes/wm/productissue/modules/detail-list.vue copy to src/views/wls/productissue/modules/detail-list.vue
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 '执行拣货';
  }
  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();
  }
}
/** æ‰§è¡Œæ‹£è´§ï¼šé¢†æ–™æ•°é‡ä¸Žæ‹£è´§æ•°é‡ä¸ä¸€è‡´æ—¶äºŒæ¬¡ç¡®è®¤ */
async function handleStock() {
  if (!formData.value?.id) {
    return;
  }
  const quantityMatch = await checkProductIssueQuantity(formData.value.id);
  if (!quantityMatch) {
    try {
      await confirm('领料数量与拣货数量不一致,确认执行拣货?');
    } catch {
      return;
    }
  }
  modalApi.lock();
  try {
    await stockProductIssue(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 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">
          æ‰§è¡Œæ‹£è´§
        </Button>
        <Popconfirm
          v-if="isFinish"
          title="确认完成该领料单并执行出库吗?"
          @confirm="handleFinish"
        >
          <Button type="primary">完成</Button>
        </Popconfirm>
      </div>
    </template>
  </Modal>
</template>
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>
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[]>
>({}); // å·²å±•开行的拣货明细缓存
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();
  }
}
/** æ‹£è´§ï¼šç›´æŽ¥æ‰“开明细创建表单 */
function handlePicking(row: MesWmProductIssueLineApi.ProductIssueLine) {
  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: MesWmProductIssueLineApi.ProductIssueLine) {
  return detailMap[row.id!] || [];
}
/** åŠ è½½æŒ‡å®šè¡Œçš„æ‹£è´§æ˜Žç»† */
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: '拣货',
              type: 'link',
              ifShow: isStock,
              onClick: handlePicking.bind(null, row),
            },
          ]"
        />
      </template>
    </Grid>
  </div>
</template>
src/views/wls/productreceipt/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 { 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',
    },
  ];
}
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>
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>
src/views/wls/productreceipt/modules/detail-list.vue
copy from src/views/mes/wm/productreceipt/modules/detail-list.vue copy to src/views/wls/productreceipt/modules/detail-list.vue
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>
在上述文件截断后对比
src/views/wls/productreceipt/modules/line-form.vue src/views/wls/productreceipt/modules/line-list.vue src/views/wls/productsales/data.ts src/views/wls/productsales/index.vue src/views/wls/productsales/modules/detail-form.vue src/views/wls/productsales/modules/detail-list.vue src/views/wls/productsales/modules/form.vue src/views/wls/productsales/modules/line-form.vue src/views/wls/productsales/modules/line-list.vue src/views/wls/returnissue/data.ts src/views/wls/returnissue/index.vue src/views/wls/returnissue/modules/detail-form.vue src/views/wls/returnissue/modules/detail-list.vue src/views/wls/returnissue/modules/form.vue src/views/wls/returnissue/modules/line-form.vue src/views/wls/returnissue/modules/line-list.vue src/views/wls/returnsales/data.ts src/views/wls/returnsales/index.vue src/views/wls/returnsales/modules/detail-form.vue src/views/wls/returnsales/modules/detail-list.vue src/views/wls/returnsales/modules/form.vue src/views/wls/returnsales/modules/line-form.vue src/views/wls/returnsales/modules/line-list.vue src/views/wls/returnvendor/data.ts src/views/wls/returnvendor/index.vue src/views/wls/returnvendor/modules/detail-form.vue src/views/wls/returnvendor/modules/detail-list.vue src/views/wls/returnvendor/modules/form.vue src/views/wls/returnvendor/modules/line-form.vue src/views/wls/returnvendor/modules/line-list.vue src/views/wls/salesnotice/components/index.ts src/views/wls/salesnotice/components/line-select-dialog.vue src/views/wls/salesnotice/components/line-select.vue src/views/wls/salesnotice/components/select-dialog.vue src/views/wls/salesnotice/components/select.vue src/views/wls/salesnotice/data.ts src/views/wls/salesnotice/index.vue src/views/wls/salesnotice/modules/form.vue src/views/wls/salesnotice/modules/line-form.vue src/views/wls/salesnotice/modules/line-list.vue src/views/wls/sn/data.ts src/views/wls/sn/index.vue src/views/wls/sn/modules/detail.vue src/views/wls/sn/modules/form.vue src/views/wls/stocktaking/plan/components/condition-value-input.vue src/views/wls/stocktaking/plan/components/index.ts src/views/wls/stocktaking/plan/components/select-dialog.vue src/views/wls/stocktaking/plan/components/select.vue src/views/wls/stocktaking/plan/data.ts src/views/wls/stocktaking/plan/index.vue src/views/wls/stocktaking/plan/modules/form.vue src/views/wls/stocktaking/plan/modules/param-form.vue src/views/wls/stocktaking/plan/modules/param-list.vue src/views/wls/stocktaking/task/data.ts src/views/wls/stocktaking/task/index.vue src/views/wls/stocktaking/task/modules/form.vue src/views/wls/stocktaking/task/modules/line-list.vue src/views/wls/stocktaking/task/modules/result-form.vue src/views/wls/stocktaking/task/modules/result-list.vue src/views/wls/transfer/data.ts src/views/wls/transfer/index.vue src/views/wls/transfer/modules/detail-form.vue src/views/wls/transfer/modules/detail-list.vue src/views/wls/transfer/modules/form.vue src/views/wls/transfer/modules/line-form.vue src/views/wls/transfer/modules/line-list.vue src/views/wls/warehouse/area/data.ts src/views/wls/warehouse/area/index.vue src/views/wls/warehouse/area/modules/form.vue src/views/wls/warehouse/components/area-select.vue src/views/wls/warehouse/components/index.ts src/views/wls/warehouse/components/location-select.vue src/views/wls/warehouse/components/select.vue src/views/wls/warehouse/data.ts src/views/wls/warehouse/index.vue src/views/wls/warehouse/location/data.ts src/views/wls/warehouse/location/index.vue src/views/wls/warehouse/location/modules/form.vue src/views/wls/warehouse/modules/form.vue vite.config.ts