feat(mes): 添加装箱单及装箱明细相关功能

新增装箱单和装箱明细的请求和响应对象,完善装箱单的创建、更新、删除及查询功能。
同时,增加装箱单状态的枚举和相关逻辑,提升系统的可用性和扩展性。
pull/871/MERGE
YunaiV 2026-03-08 09:22:21 +08:00
parent 001b1c4c86
commit e58ffbb681
8 changed files with 1378 additions and 0 deletions

View File

@ -0,0 +1,174 @@
import request from '@/config/axios'
// TODO @AI拆成两个一个 index.ts一个 linde/index.ts 两个;
// ==================== 装箱单主表 ====================
// 装箱单保存请求 VO
// TODO @AI不需要独立的 SaveReqVO直接用 WmPackageRespVO
export interface WmPackageSaveReqVO {
id?: number
code: string
parentId?: number
packageDate: number
soCode?: string
invoiceCode?: string
clientId?: number
length?: number
width?: number
height?: number
sizeUnitId?: number
netWeight?: number
grossWeight?: number
weightUnitId?: number
inspectorUserId?: number
status?: number
remark?: string
}
// 装箱单响应 VO
export interface WmPackageRespVO {
id: number
code: string
parentId?: number
packageDate: number
soCode?: 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
// TODO @AI后端不用返回 children前端根据 parentId 直接 handleTree 就好了
children?: WmPackageRespVO[]
createTime: string
}
// 装箱单 API
export const WmPackageApi = {
// 创建装箱单
createPackage: async (data: WmPackageSaveReqVO) => {
return await request.post({ url: '/mes/wm/package/create', data })
},
// 修改装箱单
updatePackage: async (data: WmPackageSaveReqVO) => {
return await request.put({ url: '/mes/wm/package/update', data })
},
// 删除装箱单
deletePackage: async (id: number) => {
return await request.delete({ url: '/mes/wm/package/delete?id=' + id })
},
// 获取装箱单详情
getPackage: async (id: number) => {
return await request.get<WmPackageRespVO>({ url: '/mes/wm/package/get?id=' + id })
},
// 分页查询装箱单
getPackagePage: async (params: any) => {
return await request.get({ url: '/mes/wm/package/page', params })
},
// 导出装箱单 Excel
exportPackage: async (params: any) => {
return await request.download({ url: '/mes/wm/package/export-excel', params })
},
// 完成装箱单
finishPackage: async (id: number) => {
return await request.put({ url: '/mes/wm/package/finish?id=' + id })
},
// 获取装箱单树形结构
getPackageTree: async (params?: any) => {
return await request.get({ url: '/mes/wm/package/tree', params })
}
}
// ==================== 装箱明细 ====================
// 装箱明细保存请求 VO
export interface WmPackageLineSaveReqVO {
id?: number
packageId: number
materialStockId?: number
itemId: number
quantity: number
workOrderId?: number
warehouseId?: number
locationId?: number
areaId?: number
expireDate?: number
remark?: string
}
// 装箱明细响应 VO
export interface WmPackageLineRespVO {
id: number
packageId: number
materialStockId?: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitMeasureName?: string
quantity: number
workOrderId?: number
workOrderCode?: string
batchCode?: string
warehouseId?: number
warehouseName?: string
locationId?: number
locationName?: string
areaId?: number
areaName?: string
expireDate?: number
remark?: string
createTime: string
}
// 装箱明细 API
export const WmPackageLineApi = {
// 创建装箱明细
createPackageLine: async (data: WmPackageLineSaveReqVO) => {
return await request.post({ url: '/mes/wm/package-line/create', data })
},
// 修改装箱明细
updatePackageLine: async (data: WmPackageLineSaveReqVO) => {
return await request.put({ url: '/mes/wm/package-line/update', data })
},
// 删除装箱明细
deletePackageLine: async (id: number) => {
return await request.delete({ url: '/mes/wm/package-line/delete?id=' + id })
},
// 获取装箱明细详情
getPackageLine: async (id: number) => {
return await request.get<WmPackageLineRespVO>({ url: '/mes/wm/package-line/get?id=' + id })
},
// 分页查询装箱明细
getPackageLinePage: async (params: any) => {
return await request.get({ url: '/mes/wm/package-line/page', params })
},
// 根据装箱单 ID 获取明细列表
getPackageLineListByPackageId: async (packageId: number) => {
return await request.get({ url: '/mes/wm/package-line/list-by-package-id', params: { packageId } })
}
}

View File

@ -314,4 +314,5 @@ export enum DICT_TYPE {
MES_MD_AUTO_CODE_CYCLE_METHOD = 'mes_md_auto_code_cycle_method', // MES 编码规则循环方式
MES_WM_BARCODE_FORMAT = 'mes_wm_barcode_format', // MES 条码格式
MES_WM_BARCODE_BIZ_TYPE = 'mes_wm_barcode_biz_type', // MES 条码业务类型
MES_WM_PACKAGE_STATUS = 'mes_wm_package_status', // MES 装箱单状态
}

View File

@ -305,6 +305,12 @@ export const MesWmSalesNoticeStatusEnum = {
FINISHED: MesOrderStatusConstants.FINISHED
}
/** MES 装箱单状态枚举 */
export const MesWmPackageStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
FINISHED: MesOrderStatusConstants.FINISHED
}
/** MES 杂项出库单状态枚举 */
export const MesWmMiscIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,

View File

@ -0,0 +1,292 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1100px">
<!-- TODO @AI每行 3 -->
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
:disabled="isDetail"
>
<el-row>
<el-col :span="8">
<el-form-item label="装箱单编号" prop="code">
<el-input v-model="formData.code" placeholder="请输入装箱单编号">
<template #append>
<el-button @click="generateCode" :disabled="formType === 'update'">
生成
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="装箱日期" prop="packageDate">
<el-date-picker
v-model="formData.packageDate"
type="date"
value-format="x"
placeholder="请选择装箱日期"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="父箱" prop="parentId">
<WmPackageSelect
v-model="formData.parentId"
:disabled="isDetail"
:exclude-id="formData.id"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="销售订单编号" prop="soCode">
<el-input v-model="formData.soCode" placeholder="请输入销售订单编号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="发票编号" prop="invoiceCode">
<el-input v-model="formData.invoiceCode" placeholder="请输入发票编号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="客户" prop="clientId">
<MdClientSelect v-model="formData.clientId" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<!-- TODO @AI长度宽度高度尺寸单位在一行可能这里一行 4 个了 -->
<el-col :span="8">
<el-form-item label="箱长度" prop="length">
<el-input-number
v-model="formData.length"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="箱宽度" prop="width">
<el-input-number
v-model="formData.width"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="箱高度" prop="height">
<el-input-number
v-model="formData.height"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="尺寸单位" prop="sizeUnitId">
<MdUnitMeasureSelect v-model="formData.sizeUnitId" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<!-- TODO @AI净重毛重重量单位 一行 -->
<el-col :span="8">
<el-form-item label="净重" prop="netWeight">
<el-input-number
v-model="formData.netWeight"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="毛重" prop="grossWeight">
<el-input-number
v-model="formData.grossWeight"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="重量单位" prop="weightUnitId">
<MdUnitMeasureSelect v-model="formData.weightUnitId" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="检查员" prop="inspectorUserId">
<UserSelect v-model="formData.inspectorUserId" :disabled="isDetail" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 编辑/详情时展示子表信息 -->
<template v-if="formType !== 'create' && formData.id">
<el-tabs v-model="activeTab" class="mt-15px">
<el-tab-pane label="子箱列表" name="subPackage">
<SubPackageList :package-id="formData.id" :form-type="formType" />
</el-tab-pane>
<el-tab-pane label="装箱明细" name="packageLine">
<PackageLineList :package-id="formData.id" :form-type="formType" />
</el-tab-pane>
</el-tabs>
</template>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!isDetail">
</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { generateRandomStr } from '@/utils'
import { WmPackageApi, WmPackageSaveReqVO } from '@/api/mes/wm/wmpackage'
import MdClientSelect from '@/views/mes/md/client/components/MdClientSelect.vue'
import MdUnitMeasureSelect from '@/views/mes/md/unitmeasure/components/MdUnitMeasureSelect.vue'
import UserSelect from '@/views/system/user/components/UserSelect.vue'
import WmPackageSelect from './components/WmPackageSelect.vue'
import SubPackageList from './SubPackageList.vue'
import PackageLineList from './PackageLineList.vue'
defineOptions({ name: 'PackageForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const formLoading = ref(false)
const formType = ref('')
const isDetail = computed(() => formType.value === 'detail')
const dialogTitle = computed(() => {
const titles: Record<string, string> = {
create: '新增装箱单',
update: '修改装箱单',
detail: '查看装箱单'
}
return titles[formType.value] || t('action.' + formType.value)
})
const activeTab = ref('subPackage')
const formData = ref({
id: undefined as number | undefined,
code: undefined as string | undefined,
parentId: undefined as number | undefined,
packageDate: undefined as number | undefined,
soCode: undefined as string | undefined,
invoiceCode: undefined as string | undefined,
clientId: undefined as number | undefined,
length: undefined as number | undefined,
width: undefined as number | undefined,
height: undefined as number | undefined,
sizeUnitId: undefined as number | undefined,
netWeight: undefined as number | undefined,
grossWeight: undefined as number | undefined,
weightUnitId: undefined as number | undefined,
inspectorUserId: undefined as number | undefined,
remark: undefined as string | undefined
})
const formRules = reactive({
code: [{ required: true, message: '装箱单编号不能为空', trigger: 'blur' }],
packageDate: [{ required: true, message: '请选择装箱日期', trigger: 'change' }]
})
const formRef = ref()
/** 生成装箱单编号 */
const generateCode = () => {
formData.value.code = 'PKG' + generateRandomStr(10)
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
formType.value = type
activeTab.value = 'subPackage'
resetForm()
if (id) {
formLoading.value = true
try {
formData.value = (await WmPackageApi.getPackage(id)) as any
} finally {
formLoading.value = false
}
}
}
defineExpose({ open })
/** 提交表单 */
const emit = defineEmits(['success'])
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = formData.value as unknown as WmPackageSaveReqVO
if (formType.value === 'create') {
const res = await WmPackageApi.createPackage(data)
message.success(t('common.createSuccess'))
// id
formData.value.id = res
formType.value = 'update'
} else {
await WmPackageApi.updatePackage(data)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
emit('success')
}
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
parentId: undefined,
packageDate: undefined,
soCode: undefined,
invoiceCode: undefined,
clientId: undefined,
length: undefined,
width: undefined,
height: undefined,
sizeUnitId: undefined,
netWeight: undefined,
grossWeight: undefined,
weightUnitId: undefined,
inspectorUserId: undefined,
remark: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,343 @@
<!-- MES 装箱明细列表子组件 -->
<template>
<div>
<el-button
v-if="isEditable"
type="primary"
plain
@click="openForm('create')"
class="mb-10px"
>
<Icon icon="ep:plus" class="mr-5px" /> 添加明细
</el-button>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" border>
<el-table-column label="产品物料编码" align="center" prop="itemCode" min-width="120" />
<el-table-column label="产品物料名称" align="center" prop="itemName" min-width="140" />
<el-table-column label="规格型号" align="center" prop="specification" min-width="120" />
<el-table-column label="单位" align="center" prop="unitMeasureName" width="80" />
<el-table-column label="装箱数量" align="center" prop="quantity" width="100" />
<el-table-column label="生产工单编号" align="center" prop="workOrderCode" min-width="140" />
<el-table-column label="批次号" align="center" prop="batchCode" min-width="120" />
<el-table-column
label="有效期"
align="center"
prop="expireDate"
:formatter="dateFormatter2"
width="120"
/>
<el-table-column label="仓库" align="center" prop="warehouseName" min-width="100" />
<el-table-column label="库区" align="center" prop="locationName" min-width="100" />
<el-table-column label="库位" align="center" prop="areaName" min-width="100" />
<el-table-column v-if="isEditable" label="操作" align="center" width="120">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
<!-- 添加/编辑明细弹窗 -->
<Dialog :title="dialogTitle" v-model="dialogVisible" width="960px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-row>
<el-col :span="8">
<el-form-item label="生产工单" prop="workOrderId">
<ProWorkOrderSelect
v-model="formData.workOrderId"
@change="handleWorkOrderChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="批次号" prop="batchCode">
<el-input v-model="formData.batchCode" disabled placeholder="自动填充" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="装箱数量" prop="quantity">
<el-input-number
v-model="formData.quantity"
:precision="2"
:min="0.01"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="产品物料编码">
<el-input v-model="formData.itemCode" disabled placeholder="自动填充" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="产品物料名称">
<el-input v-model="formData.itemName" disabled placeholder="自动填充" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单位">
<el-input v-model="formData.unitMeasureName" disabled placeholder="自动填充" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="规格型号">
<el-input
v-model="formData.specification"
type="textarea"
disabled
placeholder="自动填充"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="仓库" prop="warehouseId">
<WmWarehouseSelect
v-model="formData.warehouseId"
@change="handleWarehouseChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="库区" prop="locationId">
<WmWarehouseLocationSelect
v-model="formData.locationId"
:warehouse-id="formData.warehouseId"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="库位" prop="areaId">
<WmWarehouseAreaSelect
v-model="formData.areaId"
:warehouse-id="formData.warehouseId"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="有效期" prop="expireDate">
<el-date-picker
v-model="formData.expireDate"
type="date"
value-format="x"
placeholder="请选择有效期"
class="!w-1/1"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { dateFormatter2 } from '@/utils/formatTime'
import {
WmPackageLineApi,
WmPackageLineRespVO,
WmPackageLineSaveReqVO
} from '@/api/mes/wm/wmpackage'
import ProWorkOrderSelect from '@/views/mes/pro/workorder/components/ProWorkOrderSelect.vue'
import WmWarehouseSelect from '@/views/mes/wm/warehouse/components/WmWarehouseSelect.vue'
import WmWarehouseLocationSelect from '@/views/mes/wm/warehouse/components/WmWarehouseLocationSelect.vue'
import WmWarehouseAreaSelect from '@/views/mes/wm/warehouse/components/WmWarehouseAreaSelect.vue'
defineOptions({ name: 'PackageLineList' })
const props = defineProps<{
packageId: number
formType: string
}>()
const { t } = useI18n()
const message = useMessage()
const isEditable = computed(() => ['create', 'update'].includes(props.formType))
// ==================== ====================
const loading = ref(false)
const list = ref<WmPackageLineRespVO[]>([])
const total = ref(0)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
packageId: undefined as number | undefined
})
/** 查询明细列表 */
const getList = async () => {
loading.value = true
try {
queryParams.packageId = props.packageId
const data = await WmPackageLineApi.getPackageLinePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 删除 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WmPackageLineApi.deletePackageLine(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
// ==================== / ====================
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const lineFormType = ref('')
const formData = ref({
id: undefined as number | undefined,
packageId: undefined as number | undefined,
materialStockId: undefined as number | undefined,
itemId: undefined as number | undefined,
itemCode: undefined as string | undefined,
itemName: undefined as string | undefined,
specification: undefined as string | undefined,
unitMeasureName: undefined as string | undefined,
quantity: undefined as number | undefined,
workOrderId: undefined as number | undefined,
batchCode: undefined as string | undefined,
warehouseId: undefined as number | undefined,
locationId: undefined as number | undefined,
areaId: undefined as number | undefined,
expireDate: undefined as number | undefined,
remark: undefined as string | undefined
})
const formRules = reactive({
workOrderId: [{ required: true, message: '请选择生产工单', trigger: 'change' }],
quantity: [
{ required: true, message: '装箱数量不能为空', trigger: 'blur' },
{ type: 'number', min: 0.01, message: '装箱数量必须大于0', trigger: 'blur' }
]
})
const formRef = ref()
/** 生产工单变化时,自动填充产品信息 */
const handleWorkOrderChange = (workOrder: any) => {
if (workOrder) {
formData.value.itemId = workOrder.itemId
formData.value.itemCode = workOrder.itemCode
formData.value.itemName = workOrder.itemName
formData.value.specification = workOrder.specification
formData.value.unitMeasureName = workOrder.unitName
formData.value.batchCode = workOrder.batchCode
} else {
formData.value.itemId = undefined
formData.value.itemCode = undefined
formData.value.itemName = undefined
formData.value.specification = undefined
formData.value.unitMeasureName = undefined
formData.value.batchCode = undefined
}
}
/** 仓库变化时,清空库区库位 */
const handleWarehouseChange = () => {
// WmWarehouseLocationSelect WmWarehouseAreaSelect watch warehouseId
}
/** 打开表单弹窗 */
const openForm = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = type === 'create' ? '添加装箱明细' : '修改装箱明细'
lineFormType.value = type
resetForm()
if (id) {
formLoading.value = true
try {
formData.value = await WmPackageLineApi.getPackageLine(id)
} finally {
formLoading.value = false
}
}
}
/** 提交表单 */
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = {
...formData.value,
packageId: props.packageId
} as unknown as WmPackageLineSaveReqVO
if (lineFormType.value === 'create') {
await WmPackageLineApi.createPackageLine(data)
message.success(t('common.createSuccess'))
} else {
await WmPackageLineApi.updatePackageLine(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
await getList()
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
packageId: undefined,
materialStockId: undefined,
itemId: undefined,
itemCode: undefined,
itemName: undefined,
specification: undefined,
unitMeasureName: undefined,
quantity: undefined,
workOrderId: undefined,
batchCode: undefined,
warehouseId: undefined,
locationId: undefined,
areaId: undefined,
expireDate: undefined,
remark: undefined
}
formRef.value?.resetFields()
}
/** 初始化 */
onMounted(async () => {
await getList()
})
</script>

View File

@ -0,0 +1,171 @@
<!-- MES 子箱列表子组件 -->
<template>
<div>
<el-button
v-if="isEditable"
type="primary"
plain
@click="openForm('create')"
class="mb-10px"
>
<Icon icon="ep:plus" class="mr-5px" /> 添加子箱
</el-button>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" border>
<el-table-column label="装箱单编号" align="center" prop="code" min-width="160">
<template #default="scope">
<el-link type="primary" @click="handleView(scope.row.id)">
{{ scope.row.code }}
</el-link>
</template>
</el-table-column>
<el-table-column
label="装箱日期"
align="center"
prop="packageDate"
:formatter="dateFormatter2"
width="120"
/>
<el-table-column label="客户名称" align="center" prop="clientName" min-width="120" />
<el-table-column label="单据状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_WM_PACKAGE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column v-if="isEditable" label="操作" align="center" width="120">
<template #default="scope">
<el-button
link
type="danger"
@click="handleRemoveChild(scope.row.id)"
v-if="scope.row.status === MesWmPackageStatusEnum.PREPARE"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 添加子箱弹窗选择已有装箱单作为子箱 -->
<Dialog title="添加子箱" v-model="dialogVisible" width="500px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-form-item label="选择装箱单" prop="childId">
<WmPackageSelect
v-model="formData.childId"
:exclude-id="props.packageId"
placeholder="请选择要作为子箱的装箱单"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { WmPackageApi, WmPackageRespVO } from '@/api/mes/wm/wmpackage'
import { MesWmPackageStatusEnum } from '@/views/mes/utils/constants'
import WmPackageSelect from './components/WmPackageSelect.vue'
defineOptions({ name: 'SubPackageList' })
const props = defineProps<{
packageId: number
formType: string
}>()
const { t } = useI18n()
const message = useMessage()
const isEditable = computed(() => ['create', 'update'].includes(props.formType))
// ==================== ====================
const loading = ref(false)
const list = ref<WmPackageRespVO[]>([])
/** 查询子箱列表 */
const getList = async () => {
loading.value = true
try {
// children
const data = await WmPackageApi.getPackage(props.packageId)
list.value = data.children || []
} finally {
loading.value = false
}
}
/** 查看子箱详情(打开新弹窗) */
const handleView = (id: number) => {
//
window.open(`/mes/wm/wmpackage?id=${id}`, '_blank')
}
/** 移除子箱:将子箱的 parentId 清空 */
const handleRemoveChild = async (childId: number) => {
try {
// TODO @AI delete subpackage childId
await message.confirm('确认将该装箱单从子箱列表中移除?')
// parentId 0
const childData = await WmPackageApi.getPackage(childId)
await WmPackageApi.updatePackage({
...childData,
parentId: 0
})
message.success('移除成功')
await getList()
} catch {}
}
// ==================== ====================
const dialogVisible = ref(false)
const formLoading = ref(false)
const formData = ref({
childId: undefined as number | undefined
})
const formRules = reactive({
childId: [{ required: true, message: '请选择装箱单', trigger: 'change' }]
})
const formRef = ref()
/** 打开添加子箱弹窗 */
const openForm = async (_type: string) => {
dialogVisible.value = true
formData.value.childId = undefined
formRef.value?.resetFields()
}
/** 提交:将选中的装箱单设为当前装箱单的子箱 */
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const childData = await WmPackageApi.getPackage(formData.value.childId!)
// TODO @AI add subpackage
await WmPackageApi.updatePackage({
...childData,
parentId: props.packageId
})
message.success(t('common.createSuccess'))
dialogVisible.value = false
await getList()
} finally {
formLoading.value = false
}
}
/** 初始化 */
onMounted(async () => {
await getList()
})
</script>

View File

@ -0,0 +1,126 @@
<!-- MES 装箱单选择器纯下拉前端过滤支持 code用于选择父箱 -->
<template>
<el-select
v-model="selectValue"
:placeholder="placeholder"
:disabled="disabled"
:clearable="clearable"
filterable
:filter-method="handleFilter"
class="!w-1/1"
@change="handleChange"
>
<el-option
v-for="item in filteredList"
:key="item.id"
:label="item.code"
:value="item.id"
>
<div class="flex items-center gap-8px">
<span>{{ item.code }}</span>
<el-tag v-if="item.clientName" size="small" type="info" class="ml-4px">
{{ item.clientName }}
</el-tag>
</div>
</el-option>
</el-select>
</template>
<script setup lang="ts">
import { WmPackageApi, WmPackageRespVO } from '@/api/mes/wm/wmpackage'
defineOptions({ name: 'WmPackageSelect' })
const props = withDefaults(
defineProps<{
modelValue?: number
disabled?: boolean
clearable?: boolean
placeholder?: string
excludeId?: number // ID
}>(),
{
disabled: false,
clearable: true,
placeholder: '请选择父箱'
}
)
const emit = defineEmits<{
'update:modelValue': [value: number | undefined]
change: [item: WmPackageRespVO | undefined]
}>()
const allList = ref<WmPackageRespVO[]>([])
const filteredList = ref<WmPackageRespVO[]>([])
const selectValue = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
/** 获取排除后的列表 */
const getAvailableList = () => {
if (!props.excludeId) return allList.value
return allList.value.filter((item) => item.id !== props.excludeId)
}
/** 前端过滤code + clientName */
const handleFilter = (query: string) => {
const available = getAvailableList()
if (!query) {
filteredList.value = available
return
}
const keyword = query.toLowerCase()
filteredList.value = available.filter(
(item) =>
item.code?.toLowerCase().includes(keyword) ||
item.clientName?.toLowerCase().includes(keyword)
)
}
/** 选中变化 */
const handleChange = (val: number | undefined) => {
const item = allList.value.find((o) => o.id === val)
emit('change', item)
}
/** 加载装箱单列表(扁平化) */
const loadList = async () => {
// TODO @AI tree
// 1.
// 2.
const tree = await WmPackageApi.getPackageTree()
//
const flatList: WmPackageRespVO[] = []
const flatten = (items: WmPackageRespVO[]) => {
for (const item of items) {
flatList.push(item)
if (item.children?.length) {
flatten(item.children)
}
}
}
if (Array.isArray(tree)) {
flatten(tree)
} else if (tree?.list) {
flatten(tree.list)
}
allList.value = flatList
filteredList.value = getAvailableList()
}
/** 监听 excludeId 变化 */
watch(
() => props.excludeId,
() => {
filteredList.value = getAvailableList()
}
)
/** 初始化 */
onMounted(async () => {
await loadList()
})
</script>

View File

@ -0,0 +1,265 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="装箱单编号" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入装箱单编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="销售订单编号" prop="soCode">
<el-input
v-model="queryParams.soCode"
placeholder="请输入销售订单编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="客户" prop="clientId">
<MdClientSelect v-model="queryParams.clientId" class="!w-240px" />
</el-form-item>
<!-- TODO @AI增加检测员的 select -->
<!-- TODO @AIpackageDatestatus 去掉这样的检测条件前后端都是 -->
<el-form-item label="装箱日期" prop="packageDate">
<el-date-picker
v-model="queryParams.packageDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="单据状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择单据状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.MES_WM_PACKAGE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['mes:wm-package:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['mes:wm-package:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
default-expand-all
>
<el-table-column label="装箱单编号" align="center" prop="code" min-width="160">
<template #default="scope">
<el-link type="primary" @click="openForm('detail', scope.row.id)">
{{ scope.row.code }}
</el-link>
</template>
</el-table-column>
<el-table-column
label="装箱日期"
align="center"
prop="packageDate"
:formatter="dateFormatter2"
width="120"
/>
<el-table-column label="销售订单编号" align="center" prop="soCode" min-width="140" />
<el-table-column label="发票编号" align="center" prop="invoiceCode" min-width="120" />
<el-table-column label="客户编码" align="center" prop="clientCode" min-width="100" />
<el-table-column label="客户名称" align="center" prop="clientName" min-width="120" />
<el-table-column label="箱长度" align="center" prop="length" width="80" />
<el-table-column label="箱宽度" align="center" prop="width" width="80" />
<el-table-column label="箱高度" align="center" prop="height" width="80" />
<el-table-column label="尺寸单位" align="center" prop="sizeUnitName" width="90" />
<el-table-column label="净重" align="center" prop="netWeight" width="80" />
<el-table-column label="毛重" align="center" prop="grossWeight" width="80" />
<el-table-column label="重量单位" align="center" prop="weightUnitName" width="90" />
<el-table-column label="检查员" align="center" prop="inspectorName" min-width="100" />
<el-table-column label="单据状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_WM_PACKAGE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:wm-package:update']"
v-if="scope.row.status === MesWmPackageStatusEnum.PREPARE"
>
编辑
</el-button>
<el-button
link
type="warning"
@click="handleFinish(scope.row.id)"
v-hasPermi="['mes:wm-package:update']"
v-if="scope.row.status === MesWmPackageStatusEnum.PREPARE"
>
完成
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:wm-package:delete']"
v-if="scope.row.status === MesWmPackageStatusEnum.PREPARE"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<PackageForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import download from '@/utils/download'
import { WmPackageApi, WmPackageRespVO } from '@/api/mes/wm/wmpackage'
import MdClientSelect from '@/views/mes/md/client/components/MdClientSelect.vue'
import PackageForm from './PackageForm.vue'
import { MesWmPackageStatusEnum } from '@/views/mes/utils/constants'
defineOptions({ name: 'MesWmPackage' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<WmPackageRespVO[]>([])
const total = ref(0)
const exportLoading = ref(false)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
soCode: undefined,
clientId: undefined,
packageDate: undefined,
status: undefined
})
const queryFormRef = ref()
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
// TODO @AI list handleTree
const data = await WmPackageApi.getPackageTree(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 新增/修改/详情 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 完成 */
const handleFinish = async (id: number) => {
try {
await message.confirm('确认完成该装箱单?完成后将不可编辑。')
await WmPackageApi.finishPackage(id)
message.success('完成成功')
await getList()
} catch {}
}
/** 删除 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WmPackageApi.deletePackage(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 导出 */
// TODO @AI
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await WmPackageApi.exportPackage(queryParams)
download.excel(data, '装箱单.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 */
onMounted(async () => {
await getList()
})
</script>