Files
klp-oa/klp-ui/src/views/wms/coil/split.vue
砂糖 261f55dded feat(钢卷管理): 在多个页面添加当前钢卷号显示组件
为提升用户体验,在钢卷分切、合并、录入等页面统一添加current-coil-no组件,用于显示当前钢卷号信息
2026-03-04 14:02:25 +08:00

956 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="split-coil-container">
<!-- 顶部操作栏 -->
<div class="header-bar">
<div class="header-title">
<i class="el-icon-s-operation"></i>
<span>钢卷分条</span>
</div>
<div class="header-actions">
<el-button v-if="!readonly" type="primary" size="small" @click="handleSave" :loading="loading">保存分条</el-button>
<el-button size="small" @click="handleCancel" :disabled="loading">{{ readonly ? '返回' : '取消' }}</el-button>
</div>
</div>
<!-- 流程图区域 -->
<div class="flow-container">
<!-- 左侧母卷信息 -->
<div class="flow-left">
<div class="flow-section-title">
<span>母卷信息</span>
</div>
<div class="coil-card mother-coil">
<div class="coil-header">
<i class="el-icon-s-grid"></i>
<span class="coil-title">钢卷信息</span>
</div>
<div class="coil-body">
<div class="coil-info-row">
<span class="label">入场钢卷号</span>
<span class="value">{{ motherCoil.enterCoilNo || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">当前钢卷号</span>
<span class="value">{{ motherCoil.currentCoilNo || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">当前库区</span>
<span class="value">{{ motherCoil.warehouseName || '未分配' }}</span>
</div>
<div class="coil-info-row">
<span class="label">班组</span>
<span class="value">{{ motherCoil.team || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">毛重</span>
<span class="value">{{ motherCoil.grossWeight ? motherCoil.grossWeight + ' t' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">净重</span>
<span class="value">{{ motherCoil.netWeight ? motherCoil.netWeight + ' t' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">长度</span>
<span class="value">{{ motherCoil.length ? motherCoil.length + ' m' : '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">调制度</span>
<span class="value">{{ motherCoil.temperGrade || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">镀层种类</span>
<span class="value">{{ motherCoil.coatingType || '—' }}</span>
</div>
<div class="coil-info-row" v-if="motherCoil.materialName || motherCoil.productName">
<span class="label">物料名称</span>
<span class="value">{{ motherCoil.materialName || motherCoil.productName || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">规格</span>
<span class="value">{{ motherCoil.itemSpecification || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">材质</span>
<span class="value">{{ motherCoil.itemMaterial || '—' }}</span>
</div>
<div class="coil-info-row">
<span class="label">厂家</span>
<span class="value">{{ motherCoil.itemManufacturer || '—' }}</span>
</div>
</div>
</div>
</div>
<!-- 中间流程箭头 -->
<div class="flow-middle">
<div class="flow-arrow-container">
<div class="arrow-line" v-for="(item, index) in splitList" :key="index">
<div class="arrow-start"></div>
<div class="arrow-body"></div>
<div class="arrow-end"></div>
</div>
</div>
<div class="flow-label">
<i class="el-icon-d-arrow-right"></i>
<span>分割</span>
</div>
</div>
<!-- 右侧子卷列表 -->
<div class="flow-right">
<div class="flow-section-title">
<span>子卷列表 {{ splitList.length }} </span>
<div>
<el-button v-if="!readonly" type="text" size="mini" @click="copyToAllSubCoils"
icon="el-icon-document-copy">复制到全部</el-button>
<el-button v-if="!readonly" type="text" size="mini" @click="addSplitItem"
icon="el-icon-plus">添加子卷</el-button>
</div>
</div>
<div class="split-list">
<div class="sub-coil-card" v-for="(item, index) in splitList" :key="index">
<div class="sub-coil-header">
<span class="sub-coil-number">{{ index + 1 }}</span>
<el-button v-if="!readonly" type="text" size="mini" icon="el-icon-delete" @click="removeSplitItem(index)"
class="btn-remove"></el-button>
</div>
<div class="sub-coil-body">
<el-form size="small" label-width="90px">
<el-form-item label="卷号" required>
<el-input v-model="item.currentCoilNo" placeholder="输入子卷卷号" :disabled="readonly"></el-input>
<current-coil-no :current-coil-no="item.currentCoilNo" />
</el-form-item>
<el-form-item label="班组" required>
<el-select v-model="item.team" placeholder="请选择班组" style="width: 100%" :disabled="readonly">
<el-option key="甲" label="甲" value="甲" />
<el-option key="乙" label="乙" value="乙" />
</el-select>
</el-form-item>
<el-form-item label="材料类型" required>
<el-select v-model="item.materialType" placeholder="请选择材料类型" style="width: 100%" :disabled="readonly"
@change="handleMaterialTypeChange(item, index)">
<el-option label="原料" value="原料" />
<el-option label="成品" value="成品" />
</el-select>
</el-form-item>
<!-- 物品类型由材料类型自动决定不显示选择框 -->
<el-form-item :label="getItemLabel(item.materialType)">
<raw-material-select v-if="item.materialType === '原料'" v-model="item.itemId" placeholder="请选择原料"
style="width: 100%" clearable :disabled="readonly || !item.materialType" />
<product-select v-else-if="item.materialType === '成品'" v-model="item.itemId" placeholder="请选择成品"
style="width: 100%" clearable :disabled="readonly || !item.materialType" />
<div v-else>请先选择物料类型</div>
</el-form-item>
<el-form-item label="质量状态" prop="qualityStatus">
<el-select v-model="item.qualityStatus" placeholder="请选择质量状态" style="width: 100%"
:disabled="readonly">
<el-option v-for="item in dict.type.coil_quality_status" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="切边要求" prop="trimmingRequirement">
<el-select v-model="item.trimmingRequirement" placeholder="请选择切边要求" style="width: 100%"
:disabled="readonly">
<el-option label="净边料" value="净边料" />
<el-option label="毛边料" value="毛边料" />
</el-select>
</el-form-item>
<el-form-item label="打包状态" prop="packingStatus">
<el-input v-model="item.packingStatus" placeholder="请输入打包状态" :disabled="readonly">
</el-input>
</el-form-item>
<el-form-item label="包装要求" prop="packagingRequirement">
<el-select v-model="item.packagingRequirement" placeholder="请选择包装要求" style="width: 100%"
:disabled="readonly">
<el-option label="裸包" value="裸包" />
<el-option label="普包" value="普包" />
<el-option label="简包" value="简包" />
</el-select>
</el-form-item>
<el-form-item label="毛重(t)" required>
<el-input-number precision="3" :controls="false" v-model="item.grossWeight" placeholder="请输入毛重"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="净重(t)" required>
<el-input-number precision="3" :controls="false" v-model="item.netWeight" placeholder="请输入净重"
type="number" :step="0.01" :disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="长度(m)">
<el-input-number :controls="false" v-model="item.length" placeholder="请输入长度" type="number" :step="0.01"
:disabled="readonly">
<template slot="append"></template>
</el-input-number>
</el-form-item>
<el-form-item label="调制度" prop="temperGrade">
<el-input v-model="item.temperGrade" placeholder="请输入调制度" />
</el-form-item>
<el-form-item label="镀层种类" prop="coatingType">
<MemoInput storageKey="coatingType" v-model="item.coatingType" placeholder="请输入镀层种类" />
</el-form-item>
<el-form-item label="逻辑库区" required>
<WarehouseSelect v-model="item.warehouseId" placeholder="请选择逻辑库区" :disabled="readonly" />
</el-form-item>
<!-- <el-form-item label="真实库区">
<ActualWarehouseSelect v-model="item.actualWarehouseId" placeholder="请选择真实库区" block
:disabled="readonly" />
</el-form-item> -->
<el-form-item label="备注">
<el-input v-model="item.remark" placeholder="请输入备注" :disabled="readonly" />
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { getMaterialCoil, splitMaterialCoil } from '@/api/wms/coil';
import { listWarehouse } from '@/api/wms/warehouse';
import { completeAction } from '@/api/wms/pendingAction';
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
import RawMaterialSelect from "@/components/KLPService/RawMaterialSelect";
import ProductSelect from "@/components/KLPService/ProductSelect";
import WarehouseSelect from "@/components/KLPService/WarehouseSelect";
import { generateCoilNoPrefix } from "@/utils/coil/coilNo";
export default {
name: 'SplitCoil',
components: {
ActualWarehouseSelect,
RawMaterialSelect,
ProductSelect,
WarehouseSelect,
},
dicts: ['coil_quality_status'],
data() {
const currentCoilNoPrefix = generateCoilNoPrefix()
return {
currentCoilNoPrefix,
// 母卷信息
motherCoil: {
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
team: '',
warehouseId: null,
warehouseName: '',
itemType: null,
itemId: null,
materialName: '',
productName: '',
grossWeight: null,
netWeight: null,
itemName: null,
itemManufacturer: null,
itemMaterial: null,
itemSpecification: null,
},
// 子卷列表
splitList: [
{
currentCoilNo: currentCoilNoPrefix,
team: '',
materialType: null,
itemType: null,
itemId: null,
grossWeight: undefined,
netWeight: undefined,
warehouseId: null,
actualWarehouseId: null,
length: undefined,
temperGrade: '',
coatingType: '',
}
],
loading: false,
// 钢卷选择器可见性
coilSelectorVisible: false,
// 库区列表
warehouseList: [],
// 原材料和产品列表(实时搜索,不再保存完整备份)
rawMaterialList: [],
productList: [],
itemSearchLoading: false,
// 只读模式
readonly: false,
// 待操作ID
actionId: null
};
},
computed: {
},
async created() {
// 先加载库区列表
await this.loadWarehouses();
// 不再一次性加载所有数据,改为实时搜索
// await this.loadAllItems();
// 从路由参数获取coilId、actionId和readonly
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
const readonly = this.$route.query.readonly;
if (coilId) {
await this.loadMotherCoil(coilId);
}
// 保存待操作ID
if (actionId) {
this.actionId = actionId;
}
// 设置只读模式
if (readonly === 'true' || readonly === true) {
this.readonly = true;
}
},
methods: {
// 处理材料类型变化
handleMaterialTypeChange(item, index) {
// 清空物品选择
this.$set(item, 'itemId', null);
// 根据材料类型设置物品类型
if (item.materialType === '成品') {
this.$set(item, 'itemType', 'product');
// 清空列表,等待用户搜索
this.productList = [];
} else if (item.materialType === '原料') {
this.$set(item, 'itemType', 'raw_material');
// 清空列表,等待用户搜索
this.rawMaterialList = [];
}
},
// 动态获取标签
getItemLabel(materialType) {
if (materialType === '成品') {
return '产品类型';
} else if (materialType === '原料') {
return '原料类型';
}
return '物品';
},
// 动态获取占位符
getItemPlaceholder(materialType) {
if (materialType === '成品') {
return '请选择产品类型';
} else if (materialType === '原料') {
return '请选择原料类型';
}
return '请先选择材料类型';
},
// 格式化物品名称(添加规格和参数信息)
formatItemName(item) {
if (!item) return '';
// 获取名称(原材料或产品)
const name = item.rawMaterialName || item.productName || '';
if (!name) return '';
let displayName = name;
const specs = [];
// 1. 优先显示规格从对象的specification字段
if (item.specification) {
specs.push(item.specification);
}
// 2. 添加参数参数最多2个
if (item.bomItems && item.bomItems.length > 0) {
const bomParams = item.bomItems
.filter(bomItem => bomItem.attrKey && bomItem.attrValue)
.slice(0, 2); // 最多2个参数参数
bomParams.forEach(param => {
specs.push(`${param.attrKey}:${param.attrValue}`);
});
}
// 3. 拼接成最终格式
if (specs.length > 0) {
displayName += `${specs.join(' ')}`;
}
return displayName;
},
// 加载母卷信息
async loadMotherCoil(coilId) {
try {
this.loading = true;
const response = await getMaterialCoil(coilId);
if (response.code === 200 && response.data) {
const data = response.data;
this.motherCoil = {
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
team: data.team || '',
warehouseId: data.warehouseId,
warehouseName: data.warehouseName || (data.warehouse ? data.warehouse.warehouseName : ''),
itemType: data.itemType,
itemId: data.itemId,
materialType: data.materialType,
materialName: data.materialName || (data.rawMaterial ? data.rawMaterial.rawMaterialName : ''),
productName: data.productName || (data.product ? data.product.productName : ''),
grossWeight: data.grossWeight,
netWeight: data.netWeight,
length: data.length,
itemName: data?.rawMaterial?.rawMaterialName || data?.product?.productName || '',
itemManufacturer: data?.rawMaterial?.manufacturer || data?.product?.manufacturer || '',
itemMaterial: data?.rawMaterial?.material || data?.product?.material || '',
itemSpecification: data?.rawMaterial?.specification || data?.product?.specification || '',
};
}
} catch (error) {
this.$message.error('加载母卷信息失败');
console.error(error);
} finally {
this.loading = false;
}
},
// 加载库区列表
async loadWarehouses() {
try {
const response = await listWarehouse({ pageNum: 1, pageSize: 1000 });
if (response.code === 200) {
this.warehouseList = response.rows || response.data || [];
}
} catch (error) {
console.error('加载库区列表失败', error);
}
},
// 添加子卷
addSplitItem() {
this.splitList.push({
currentCoilNo: this.currentCoilNoPrefix,
team: '',
materialType: null,
itemType: null,
itemId: null,
grossWeight: null,
netWeight: null,
length: null,
warehouseId: null,
actualWarehouseId: null,
qualityStatus: '',
packagingRequirement: '',
packingStatus: '',
trimmingRequirement: '',
temperGrade: '',
coatingType: '',
});
},
// 删除子卷
removeSplitItem(index) {
if (this.splitList.length > 1) {
this.splitList.splice(index, 1);
} else {
this.$message.warning('至少保留一个子卷');
}
},
// 保存分条
async handleSave() {
// 验证母卷信息
if (!this.motherCoil.coilId) {
this.$message.error('请先选择母卷');
return;
}
// 验证子卷数量
if (this.splitList.length < 1) {
this.$message.error('至少需要一个子卷');
return;
}
// 验证子卷信息
for (let i = 0; i < this.splitList.length; i++) {
const item = this.splitList[i];
if (!item.currentCoilNo || item.currentCoilNo.trim() === '') {
this.$message.error(`${i + 1}个子卷的卷号不能为空`);
return;
}
if (!item.team || item.team.trim() === '') {
this.$message.error(`${i + 1}个子卷的班组不能为空`);
return;
}
if (!item.itemType) {
this.$message.error(`${i + 1}个子卷的物品类型不能为空`);
return;
}
if (!item.itemId) {
this.$message.error(`${i + 1}个子卷的物品不能为空`);
return;
}
if (item.grossWeight === null || item.grossWeight === undefined || item.grossWeight === '') {
this.$message.error(`${i + 1}个子卷的毛重不能为空`);
return;
}
if (item.netWeight === null || item.netWeight === undefined || item.netWeight === '') {
this.$message.error(`${i + 1}个子卷的净重不能为空`);
return;
}
if (!item.warehouseId) {
this.$message.error(`${i + 1}个子卷的目标库区不能为空`);
return;
}
}
const loadingInstance = this.$loading({
lock: true,
text: '正在分条,请稍后...',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
this.loading = true;
// 构造分条数据
const splitData = {
coilId: this.motherCoil.coilId,
enterCoilNo: this.motherCoil.enterCoilNo, // 入场钢卷号(必填)
currentCoilNo: this.motherCoil.currentCoilNo,
materialType: this.motherCoil.materialType,
hasMergeSplit: 1, // 1表示分条
newCoils: this.splitList.map(item => ({
...item,
itemType: item.itemType || this.motherCoil.itemType,
itemId: item.itemId || this.motherCoil.itemId,
hasMergeSplit: 1
}))
};
const response = await splitMaterialCoil(splitData);
if (response.code === 200) {
this.$message.success('分条保存成功');
// 如果是从待操作列表进来的,标记操作为完成
if (this.actionId) {
await completeAction(this.actionId);
}
// 延迟返回,让用户看到成功提示
setTimeout(() => {
this.$router.back();
}, 100);
} else {
this.$message.error(response.msg || '分条保存失败');
}
} catch (error) {
this.$message.error('分条保存失败');
console.error(error);
} finally {
this.loading = false;
loadingInstance.close();
}
},
// 取消操作
handleCancel() {
this.$router.back();
},
// 复制到全部子卷
copyToAllSubCoils() {
if (!this.motherCoil.coilId) {
this.$message.warning('请先加载母卷信息');
return;
}
// 复制到所有子卷
this.splitList.forEach((item, index) => {
// 自动生成卷号:母卷号-1, 母卷号-2, 母卷号-3...
if (!item.currentCoilNo) {
item.currentCoilNo = `${this.motherCoil.currentCoilNo}-${index + 1}`;
}
// 复制班组
if (!item.team) {
item.team = this.motherCoil.team;
}
});
// 不再预加载物品列表,改为实时搜索
this.$message.success('已复制到所有子卷');
}
}
};
</script>
<style scoped lang="scss">
.split-coil-container {
padding: 20px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
/* 顶部操作栏 */
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
background: #fff;
padding: 16px 20px;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header-title {
font-size: 18px;
font-weight: 500;
color: #303133;
display: flex;
align-items: center;
gap: 8px;
i {
color: #0066cc;
font-size: 20px;
}
}
/* 流程图容器 */
.flow-container {
display: flex;
gap: 40px;
background: #fff;
padding: 30px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
min-height: 600px;
}
.flow-left {
flex: 0 0 300px;
}
.flow-middle {
flex: 0 0 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
}
.flow-right {
flex: 1;
min-width: 0;
}
.flow-section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
padding-left: 12px;
border-left: 4px solid #0066cc;
display: flex;
justify-content: space-between;
align-items: center;
.el-button--text {
font-size: 12px;
}
}
/* 母卷卡片 */
.mother-coil {
border: 2px solid #0066cc;
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.15);
}
.coil-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
overflow: hidden;
}
.coil-header {
background: #0066cc;
color: #fff;
padding: 16px 20px;
display: flex;
align-items: center;
gap: 10px;
i {
font-size: 20px;
}
.coil-title {
flex: 1;
font-size: 16px;
font-weight: 600;
}
}
.coil-body {
padding: 20px;
}
.coil-info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #909399;
font-size: 14px;
min-width: 100px;
flex-shrink: 0;
}
.value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
word-break: break-all;
&.highlight {
color: #0066cc;
font-size: 16px;
font-weight: 600;
}
}
}
/* 参数参数展示 */
.bom-params {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 10px;
}
.param-item {
display: flex;
align-items: center;
font-size: 13px;
.param-name {
color: #909399;
min-width: 80px;
}
.param-value {
color: #303133;
font-weight: 500;
}
}
/* 流程箭头 */
.flow-arrow-container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 100%;
}
.arrow-line {
position: relative;
height: 3px;
margin: 20px 0;
display: flex;
align-items: center;
}
.arrow-start {
width: 8px;
height: 8px;
background: #0066cc;
border-radius: 50%;
}
.arrow-body {
flex: 1;
height: 3px;
background: #0066cc;
}
.arrow-end {
width: 0;
height: 0;
border-left: 12px solid #0066cc;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
}
.flow-label {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: #ecf5ff;
color: #0066cc;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
border: 1px solid #d9ecff;
}
/* 子卷列表 */
.split-list {
max-height: 500px;
overflow-y: auto;
margin-bottom: 20px;
padding-right: 10px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
.sub-coil-card {
background: #fff;
border: 1px solid #e4e7ed;
border-radius: 8px;
margin-bottom: 16px;
transition: all 0.3s;
&:hover {
border-color: #0066cc;
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.15);
}
}
.sub-coil-header {
background: #f5f7fa;
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e4e7ed;
}
.sub-coil-number {
font-size: 16px;
font-weight: 600;
color: #0066cc;
width: 32px;
height: 32px;
border-radius: 50%;
background: #ecf5ff;
display: flex;
align-items: center;
justify-content: center;
}
.btn-remove {
color: #f56c6c;
padding: 0;
&:hover {
color: #f56c6c;
}
}
.sub-coil-body {
padding: 16px;
}
/* 汇总卡片 */
.summary-card {
background: #ecf5ff;
border: 1px solid #d9ecff;
border-radius: 8px;
padding: 16px 20px;
display: flex;
justify-content: space-around;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.summary-label {
font-size: 13px;
color: #606266;
}
.summary-value {
font-size: 20px;
font-weight: 600;
color: #0066cc;
&.error {
color: #f56c6c;
}
}
// 优化按钮文字颜色
// 实心primary按钮白色文字
::v-deep .el-button--primary.el-button--small:not(.is-plain) {
color: #fff;
}
// plain按钮和text按钮蓝色文字
::v-deep .el-button--primary.el-button--small.is-plain,
::v-deep .el-button--text {
color: #409eff;
&:hover {
color: #66b1ff;
}
}
// 修复数字输入框的上下箭头溢出
.sub-coil-body {
::v-deep input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
appearance: none;
margin: 0;
}
}
}
</style>