feat(打包模块): 新增打包单据功能及相关组件
refactor(CoilSelector): 修改change事件返回参数包含完整钢卷信息 chore: 添加打包模块相关图标和API文件 test: 添加打包模块单元测试 docs: 更新打包模块API文档
This commit is contained in:
441
klp-ui/src/views/wms/packing/packing.vue
Normal file
441
klp-ui/src/views/wms/packing/packing.vue
Normal 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>
|
||||
Reference in New Issue
Block a user