feat(打包模块): 新增打包单据功能及相关组件

refactor(CoilSelector): 修改change事件返回参数包含完整钢卷信息

chore: 添加打包模块相关图标和API文件

test: 添加打包模块单元测试

docs: 更新打包模块API文档
This commit is contained in:
砂糖
2026-03-24 09:16:22 +08:00
parent f5a8d35831
commit 396f861882
37 changed files with 1727 additions and 33 deletions

View File

@@ -0,0 +1,441 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 左侧打包表单和明细 -->
<el-col :span="15">
<div class="section-card">
<div class="section-header">
<h3 class="section-title">创建打包单据</h3>
<div class="header-actions">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">保存单据</el-button>
<el-button @click="resetForm">重置</el-button>
<coil-selector dialog-width="1200px" :use-trigger="true" multiple :multiple="true" :filters="coilFilters"
:disableO="true" @confirm="handleBatchAddCoil">
<el-button type="primary" plain icon="el-icon-plus" size="small">批量添加钢卷</el-button>
</coil-selector>
</div>
</div>
<el-form ref="form" :model="form" :rules="rules" label-width="100px" style="margin-bottom: 20px;">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="单据号" prop="packingNo">
<el-input v-model="form.packingNo" placeholder="系统自动生成" disabled />
</el-form-item>
<el-form-item label="批次号" prop="batchNo">
<el-input v-model="form.batchNo" placeholder="请输入批次号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="打包时间" prop="packingTime">
<el-date-picker v-model="form.packingTime" type="datetime" placeholder="选择打包时间"
value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
</el-form-item>
<el-form-item label="操作人" prop="packingBy">
<el-input v-model="form.packingBy" placeholder="请输入操作人" clearable />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="section-header" style="margin-top: 20px;">
<h3 class="section-title">打包明细</h3>
</div>
<el-table v-loading="detailLoading" :data="detailList" border style="width: 100%" max-height="400">
<el-table-column label="序号" type="index" width="55" align="center" />
<el-table-column label="钢卷号" align="center" prop="coilNo" min-width="120">
<template slot-scope="scope">
<current-coil-no :current-coil-no="scope.row.coilNo" />
</template>
</el-table-column>
<el-table-column label="产品类型" align="center" width="250">
<template slot-scope="scope">
<ProductInfo v-if="scope.row.itemType == 'product'" :product="scope.row.product" />
<RawMaterialInfo v-else-if="scope.row.itemType === 'raw_material'" :material="scope.row.rawMaterial" />
</template>
</el-table-column>
<!-- <el-table-column label="品名" align="center" prop="itemName" min-width="100" />
<el-table-column label="规格" align="center" prop="specification" min-width="120" show-overflow-tooltip />
<el-table-column label="材质" align="center" prop="material" min-width="80" /> -->
<el-table-column label="重量" align="center" prop="coilNetWeight" width="80">
<template slot-scope="scope">{{ scope.row.coilNetWeight }}t</template>
</el-table-column>
<el-table-column label="销售名称" align="center" prop="saleName" width="100">
<template slot-scope="scope">
<el-input v-model="scope.row.saleName"></el-input>
</template>
</el-table-column>
<el-table-column label="打包前位置" align="center" prop="fromWarehouseName" min-width="120"
show-overflow-tooltip />
<!-- <el-table-column label="打包后位置" align="center" prop="toWarehouseName" min-width="120">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.toWarehouseName" size="mini" placeholder="输入位置" />
<span v-else>{{ scope.row.toWarehouseName || '' }}</span>
</template>
</el-table-column> -->
<el-table-column label="操作" align="center" width="100">
<template slot-scope="scope">
<template v-if="scope.row.editing">
<el-button size="mini" type="text" @click="handleSaveDetail(scope.row)">保存</el-button>
<el-button size="mini" type="text" @click="scope.row.editing = false">取消</el-button>
</template>
<template v-else>
<!-- <el-button size="mini" type="text" @click="handleEditDetail(scope.row)">修改</el-button> -->
<el-button size="mini" type="text" @click="handleDeleteDetail(scope.row)">删除</el-button>
</template>
</template>
</el-table-column>
</el-table>
<div v-if="detailList.length > 0" class="detail-summary">
<el-descriptions :column="3" size="small" border>
<el-descriptions-item label="总卷数">
{{ detailList.length }}
</el-descriptions-item>
<el-descriptions-item label="总重量">
{{ totalWeight }}
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-col>
<!-- 右侧最近创建的打包单据 -->
<el-col :span="9">
<div class="section-card">
<div class="section-header">
<h3 class="section-title">最近创建的单据</h3>
<el-button size="mini" icon="el-icon-refresh" @click="getRecentList">刷新</el-button>
</div>
<el-table v-loading="recentLoading" :data="recentList" border style="width: 100%">
<el-table-column label="单据号" align="center" prop="packingNo" show-overflow-tooltip />
<el-table-column label="批次号" align="center" prop="batchNo" show-overflow-tooltip />
<el-table-column label="打包日期" align="center" prop="packingTime" />
<el-table-column label="卷数" align="center" prop="coilCount" width="60" />
<el-table-column label="总重" align="center" prop="totalNetWeight" width="80">
<template slot-scope="scope">{{ scope.row.totalNetWeight }}t</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { addPacking, updatePacking, listPacking, listPackingDetail, updatePackingDetail, delPackingDetail, batchAddPackingDetail, createPacking } from '@/api/wms/packing';
import CoilSelector from '@/components/CoilSelector';
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
export default {
name: 'PackingCreate',
dicts: [],
components: {
CoilSelector,
ProductInfo,
RawMaterialInfo
},
data() {
return {
// 表单参数
form: {
packingNo: '',
batchNo: '',
packingDate: '',
team: '',
packingBy: '',
remark: ''
},
// 当前正在编辑的打包单据ID
currentPackingId: null,
// 打包明细列表
detailList: [],
// 最近创建的单据列表
recentList: [],
// 按钮loading
buttonLoading: false,
// 明细loading
detailLoading: false,
// 最近列表loading
recentLoading: false,
// 表单校验
rules: {
batchNo: [
{ required: true, message: '批次号不能为空', trigger: 'blur' }
],
packingDate: [
{ required: true, message: '打包日期不能为空', trigger: 'change' }
]
},
// 钢卷选择器筛选参数
coilFilters: {
dataType: 1,
status: 0,
selectType: 'product',
excludePacked: true
}
};
},
computed: {
// 总重量
totalWeight() {
return this.detailList.reduce((acc, item) => acc + Number(item.coilNetWeight || 0), 0).toFixed(3);
},
// 已填位置的明细数量
filledPositionCount() {
return this.detailList.filter(item => item.afterPosition).length;
}
},
created() {
// 默认设置为今天
this.form.packingDate = this.formatDate(new Date());
// 设置打包时间默认值为当前时间
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hour = String(now.getHours()).padStart(2, '0');
const minute = String(now.getMinutes()).padStart(2, '0');
const second = String(now.getSeconds()).padStart(2, '0');
this.form.packingTime = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
// 自动生成单据号和批次号格式为yyyyMMddHHmm
const code = `${year}${month}${day}${hour}${minute}`;
this.form.packingNo = code;
this.form.batchNo = code;
// 设置操作人默认值为当前登录用户
this.form.packingBy = this.$store.getters.nickName;
this.getRecentList();
},
methods: {
/** 格式化日期 */
formatDate(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
},
/** 提交表单 */
submitForm() {
this.$refs['form'].validate(valid => {
if (valid) {
if (this.detailList.length === 0) {
this.$modal.msgWarning('请添加打包明细');
return;
}
this.buttonLoading = true;
const data = {
...this.form,
totalNetWeight: this.totalWeight,
details: this.detailList
};
createPacking(data).then(response => {
this.$modal.msgSuccess('创建成功');
this.form.packingNo = response.data.packingNo;
this.getRecentList();
this.resetForm();
}).finally(() => {
this.buttonLoading = false;
});
}
});
},
/** 重置表单 */
resetForm() {
// 自动生成新的单据号和批次号格式为yyyyMMddHHmm
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hour = String(now.getHours()).padStart(2, '0');
const minute = String(now.getMinutes()).padStart(2, '0');
const second = String(now.getSeconds()).padStart(2, '0');
const code = `${year}${month}${day}${hour}${minute}`;
this.form = {
packingNo: code,
batchNo: code,
packingDate: this.formatDate(new Date()),
packingTime: `${year}-${month}-${day} ${hour}:${minute}:${second}`,
packingBy: this.$store.getters.nickName,
remark: ''
};
this.currentPackingId = null;
this.detailList = [];
this.$refs.form.resetFields();
},
/** 批量添加钢卷 */
handleBatchAddCoil(coils) {
const newCoils = coils.map(coil => ({
coilId: coil.coilId,
coilNo: coil.currentCoilNo,
itemName: coil.itemName,
specification: coil.specification,
material: coil.material,
coilNetWeight: coil.netWeight || '',
coilGrossWeight: coil.grossWeight || '0.000',
fromWarehouseId: coil.warehouseId || '',
fromWarehouseName: coil.warehouseName || '',
itemType: coil.itemType || '',
itemId: coil.itemId || '',
product: coil.product || {},
rawMaterial: coil.rawMaterial || {},
// beforePosition: coil.actualWarehouseName || '',
toWarehouseId: '2035892198703349761',
toWarehouseName: '打包待发货'
}));
this.detailList = [...this.detailList, ...newCoils];
this.$modal.msgSuccess('添加成功');
},
/** 获取明细列表 */
getDetailList() {
if (!this.currentPackingId) return;
this.detailLoading = true;
listPackingDetail({ packingId: this.currentPackingId }).then(response => {
this.detailList = response.rows || [];
}).finally(() => {
this.detailLoading = false;
});
},
/** 编辑明细 */
handleEditDetail(row) {
this.$set(row, 'editing', true);
this.$set(row, 'afterPositionBackup', row.afterPosition);
},
/** 保存明细修改 */
handleSaveDetail(row) {
this.detailLoading = true;
updatePackingDetail(row).then(response => {
this.$modal.msgSuccess('保存成功');
row.editing = false;
this.getDetailList();
}).catch(() => {
this.detailLoading = false;
});
},
/** 删除明细 */
handleDeleteDetail(row) {
// 前端删除根据coilId删除所有相关明细
this.detailList = this.detailList.filter(item => item.coilId !== row.coilId);
// this.$modal.confirm('确认删除该钢卷明细吗?').then(() => {
// return delPackingDetail(row.detailId);
// }).then(() => {
// this.$modal.msgSuccess('删除成功');
// this.getDetailList();
// }).catch(() => { });
},
/** 获取最近创建的单据列表 */
getRecentList() {
this.recentLoading = true;
listPacking({
pageNum: 1,
pageSize: 10,
orderByColumn: 'createTime',
isAsc: 'desc'
}).then(response => {
this.recentList = response.rows || [];
}).finally(() => {
this.recentLoading = false;
});
},
/** 查看单据详情 */
handleViewDetail(row) {
this.$router.push({
path: '/wms/packing/record',
query: { packingId: row.packingId }
});
},
/** 继续编辑单据 */
handleContinueEdit(row) {
this.currentPackingId = row.packingId;
this.form.packingNo = row.packingNo;
this.form.batchNo = row.batchNo;
this.form.packingDate = row.packingDate;
this.form.team = row.team;
this.form.operator = row.operator;
this.form.remark = row.remark;
this.getDetailList();
this.$message.success('已加载单据,请继续编辑');
}
}
};
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.section-card {
background-color: #ffffff;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 2px solid #e4e7ed;
.section-title {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.header-actions {
display: flex;
align-items: center;
gap: 12px;
.tip-text {
font-size: 13px;
color: #909399;
}
}
}
.form-footer {
display: flex;
justify-content: center;
gap: 12px;
margin-top: 20px;
}
.empty-tips {
text-align: center;
padding: 60px 20px;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
display: block;
color: #e6a23c;
}
p {
margin: 0;
font-size: 14px;
}
}
.detail-summary {
margin-top: 16px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
}
</style>