Files
klp-oa/klp-ui/src/views/wms/coil/typing.vue

869 lines
26 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="typing-coil-container">
<!-- 顶部操作栏 -->
<div class="header-bar">
<div class="header-title">
<i class="el-icon-edit"></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="content-wrapper">
<!-- 左侧当前信息 -->
<div class="left-panel">
<el-card class="info-card">
<div slot="header" class="card-header">
<span><i class="el-icon-info"></i> 当前信息</span>
</div>
<div class="info-section">
<div class="info-row">
<span class="info-label">入场钢卷号</span>
<span class="info-value">{{ currentInfo.enterCoilNo || '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">当前钢卷号</span>
<span class="info-value">{{ currentInfo.currentCoilNo || '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">厂家原料卷号</span>
<span class="info-value">{{ currentInfo.supplierCoilNo || '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">班组</span>
<span class="info-value">{{ currentInfo.team || '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">物品类型</span>
<span class="info-value">{{ getItemTypeText(currentInfo.itemType) }}</span>
</div>
<div class="info-row">
<span class="info-label">物料名称</span>
<span class="info-value">{{ currentInfo.itemName || '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">毛重</span>
<span class="info-value">{{ currentInfo.grossWeight ? currentInfo.grossWeight + ' t' : '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">净重</span>
<span class="info-value">{{ currentInfo.netWeight ? currentInfo.netWeight + ' t' : '—' }}</span>
</div>
<div class="info-row">
<span class="info-label">逻辑库区</span>
<span class="info-value">{{ currentInfo.nextWarehouseName || '—' }}</span>
</div>
<div class="info-row" v-if="currentInfo.remark">
<span class="info-label">备注</span>
<span class="info-value">{{ currentInfo.remark }}</span>
</div>
</div>
</el-card>
</div>
<!-- 右侧更新表单 -->
<div class="right-panel">
<el-card class="form-card">
<div slot="header" class="card-header">
<span><i class="el-icon-edit-outline"></i> {{ readonly ? '查看信息' : '更新信息' }}</span>
<el-button v-if="!readonly" type="text" size="mini" @click="copyFromCurrent" icon="el-icon-document-copy">
复制当前信息
</el-button>
</div>
<el-form ref="updateForm" :model="updateForm" :rules="rules" label-width="120px" size="small">
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input v-model="updateForm.currentCoilNo" placeholder="请输入当前钢卷号" :disabled="readonly">
<template slot="prepend">
<i class="el-icon-document"></i>
</template>
</el-input>
</el-form-item>
<el-form-item label="班组" prop="team">
<el-input v-model="updateForm.team" placeholder="请输入班组名称" :disabled="readonly">
<template slot="prepend">
<i class="el-icon-user-solid"></i>
</template>
</el-input>
</el-form-item>
<!-- <el-form-item label="物品类型" prop="itemType">
<el-select v-model="updateForm.itemType" placeholder="请选择物品类型" style="width: 100%" :disabled="readonly">
<el-option label="原材料" value="raw_material" />
<el-option label="产品" value="product" />
</el-select>
</el-form-item>
<el-form-item label="物品" prop="itemId">
<el-select v-model="updateForm.itemId" placeholder="请选择物品" filterable remote :remote-method="searchItems"
:loading="itemSearchLoading" style="width: 100%" :disabled="readonly">
<el-option v-for="item in currentItemList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item> -->
<MaterialSelect :hideType="hideType" :itemId.sync="updateForm.itemId" :itemType.sync="updateForm.itemType" />
<el-form-item label="毛重(t)" prop="grossWeight">
<el-input v-model.number="updateForm.grossWeight" placeholder="请输入毛重" type="number" step="0.01"
:disabled="readonly">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="净重(t)" prop="netWeight">
<el-input v-model.number="updateForm.netWeight" placeholder="请输入净重" type="number" step="0.01"
:disabled="readonly">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="逻辑库区" prop="warehouseId">
<el-select v-model="updateForm.warehouseId" placeholder="请选择逻辑库区" style="width: 100%" filterable
:disabled="readonly">
<el-option v-for="warehouse in warehouseList" :key="warehouse.warehouseId"
:label="warehouse.warehouseName" :value="warehouse.warehouseId" />
</el-select>
</el-form-item>
<el-form-item label="真实库区" prop="warehouseId">
<ActualWarehouseSelect v-model="updateForm.actualWarehouseId" placeholder="请选择真实库区" style="width: 100%"
filterable :disabled="readonly" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="updateForm.remark" type="textarea" :rows="4" placeholder="请输入备注信息(非必填)" maxlength="500"
show-word-limit :disabled="readonly" />
</el-form-item>
</el-form>
</el-card>
</div>
</div>
<!-- 变更历史占满整行 -->
<div class="history-section">
<el-card class="history-card">
<div slot="header" class="card-header">
<span><i class="el-icon-time"></i> 变更历史</span>
<el-button type="text" size="mini" @click="loadHistory" :loading="historyLoading">
<i class="el-icon-refresh"></i> 刷新
</el-button>
</div>
<el-timeline v-if="historySteps.length > 0">
<el-timeline-item v-for="(step, index) in historySteps" :key="index"
:timestamp="`步骤 ${step.display_step || step.step}`" placement="top"
:type="step.operation === '新增' ? 'success' : 'primary'">
<div class="history-item">
<div class="history-title">{{ step.operation || step.action }}</div>
<div class="history-detail" v-if="step.operator">
<span class="detail-label">操作人</span>
<span>{{ step.operator }}</span>
</div>
<div class="history-detail" v-if="step.old_current_coil_no">
<span class="detail-label">原钢卷号</span>
<span>{{ step.old_current_coil_no }}</span>
</div>
<div class="history-detail" v-if="step.new_current_coil_no">
<span class="detail-label">新钢卷号</span>
<span>{{ step.new_current_coil_no }}</span>
</div>
</div>
</el-timeline-item>
</el-timeline>
<div v-else class="empty-history">
<i class="el-icon-document"></i>
<p>暂无变更历史</p>
</div>
</el-card>
</div>
</div>
</template>
<script>
import { getMaterialCoil, updateMaterialCoil, getMaterialCoilTrace } from '@/api/wms/coil';
import { completeAction } from '@/api/wms/pendingAction';
import { listWarehouse } from '@/api/wms/warehouse';
import { listRawMaterial } from '@/api/wms/rawMaterial';
import { listProduct } from '@/api/wms/product';
import ActualWarehouseSelect from "@/components/KLPService/ActualWarehouseSelect";
import MaterialSelect from "@/components/KLPService/MaterialSelect";
export default {
name: 'TypingCoil',
components: {
ActualWarehouseSelect,
MaterialSelect
},
data() {
return {
loading: false,
historyLoading: false,
// 当前信息(只读)
currentInfo: {
coilId: null,
enterCoilNo: '',
currentCoilNo: '',
supplierCoilNo: '',
team: '',
itemType: null,
itemId: null,
itemName: '',
grossWeight: null,
netWeight: null,
warehouseId: null,
warehouseId: null,
nextWarehouseName: '',
status: 0,
remark: ''
},
// 更新表单
updateForm: {
currentCoilNo: '',
team: '',
itemType: null,
itemId: null,
grossWeight: null,
netWeight: null,
warehouseId: null,
actualWarehouseId: null,
remark: ''
},
rules: {
currentCoilNo: [
{ required: true, message: '请输入当前钢卷号', trigger: 'blur' }
],
team: [
{ required: true, message: '请输入班组', trigger: 'blur' }
],
itemType: [
{ required: true, message: '请选择物品类型', trigger: 'change' }
],
itemId: [
{ required: true, message: '请选择物品', trigger: 'change' }
],
grossWeight: [
{ required: true, message: '请输入毛重', trigger: 'blur' },
{ type: 'number', message: '毛重必须为数字', trigger: 'blur' }
],
netWeight: [
{ required: true, message: '请输入净重', trigger: 'blur' },
{ type: 'number', message: '净重必须为数字', trigger: 'blur' }
],
warehouseId: [
{ required: true, message: '请选择逻辑库区', trigger: 'change' }
],
actualWarehouseId: [
{ required: true, message: '请选择真实库区', trigger: 'change' }
]
},
warehouseList: [],
historySteps: [],
actionId: null,
// 原材料和产品列表
rawMaterialList: [],
productList: [],
itemSearchLoading: false,
// 只读模式
readonly: false
};
},
computed: {
// 当前物品列表(根据物品类型动态切换)
currentItemList() {
if (this.updateForm.itemType === 'raw_material') {
return this.rawMaterialList.map(item => ({
id: item.rawMaterialId,
name: this.formatItemName(item)
}));
} else if (this.updateForm.itemType === 'product') {
return this.productList.map(item => ({
id: item.productId,
name: this.formatItemName(item)
}));
}
return [];
}
},
watch: {
// 监听物品类型变化,加载对应的列表
'updateForm.itemType'(newVal, oldVal) {
if (newVal !== oldVal) {
this.updateForm.itemId = null;
this.loadItemList(newVal);
}
}
},
async created() {
// 先加载库区列表
await this.loadWarehouses();
// 从路由参数获取coilId和actionId
const coilId = this.$route.query.coilId;
const actionId = this.$route.query.actionId;
const readonly = this.$route.query.readonly;
if (coilId) {
await this.loadCoilInfo(coilId);
}
if (actionId) {
this.actionId = actionId;
}
// 设置只读模式
if (readonly === 'true' || readonly === true) {
this.readonly = true;
}
},
methods: {
// 加载钢卷信息
async loadCoilInfo(coilId) {
try {
this.loading = true;
const response = await getMaterialCoil(coilId);
if (response.code === 200 && response.data) {
const data = response.data;
// 填充当前信息(左侧)
this.currentInfo = {
coilId: data.coilId,
enterCoilNo: data.enterCoilNo || '',
currentCoilNo: data.currentCoilNo || '',
supplierCoilNo: data.supplierCoilNo || '',
team: data.team || '',
itemType: data.itemType || null,
itemId: data.itemId || null,
itemName: this.getItemName(data),
grossWeight: data.grossWeight,
netWeight: data.netWeight,
warehouseId: data.warehouseId,
actualWarehouseId: data.actualWarehouseId,
nextWarehouseName: this.getWarehouseName(data.warehouseId),
remark: data.remark || ''
};
console.log('当前信息加载完成:', this.currentInfo);
// 重新获取库区名称确保warehouseList已加载
if (data.warehouseId) {
this.currentInfo.nextWarehouseName = this.getWarehouseName(data.warehouseId);
}
// 加载对应类型的物品列表
if (data.itemType) {
await this.loadItemList(data.itemType);
}
// 加载变更历史
this.loadHistory();
}
} catch (error) {
this.$message.error('加载钢卷信息失败');
console.error(error);
} finally {
this.loading = false;
}
},
// 获取物料名称
getItemName(data) {
if (data.rawMaterial) {
return data.rawMaterial.rawMaterialName;
} else if (data.product) {
return data.product.productName;
}
return '';
},
// 获取物品类型文本
getItemTypeText(itemType) {
if (itemType === 'raw_material') return '原材料';
if (itemType === 'product') return '产品';
return '—';
},
// 获取库区名称
getWarehouseName(warehouseId) {
if (!warehouseId) return '';
const warehouse = this.warehouseList.find(w => w.warehouseId === warehouseId);
return warehouse ? warehouse.warehouseName : '';
},
// 格式化物品名称添加规格和BOM信息
formatItemName(name, bomItems) {
if (!name) return '';
let displayName = name;
// 如果有BOM参数添加到名称后面
if (bomItems && bomItems.length > 0) {
const specs = [];
// 查找规格参数
const specItem = bomItems.find(item =>
item.attrKey === '规格' ||
item.attrKey === 'spec' ||
item.attrKey === '型号'
);
if (specItem && specItem.attrValue) {
specs.push(specItem.attrValue);
}
// 添加其他关键参数最多3个
const otherParams = bomItems
.filter(item =>
item.attrKey !== '规格' &&
item.attrKey !== 'spec' &&
item.attrKey !== '型号'
)
.slice(0, 2); // 最多2个其他参数
otherParams.forEach(param => {
if (param.attrValue) {
specs.push(`${param.attrKey}:${param.attrValue}`);
}
});
if (specs.length > 0) {
displayName += `${specs.join(' ')}`;
}
}
return displayName;
},
// 加载库区列表
async loadWarehouses() {
try {
const response = await listWarehouse({ pageNum: 1, pageSize: 1000 });
if (response.code === 200) {
this.warehouseList = response.rows || response.data || [];
console.log('库区列表加载成功,数量:', this.warehouseList.length);
}
} catch (error) {
console.error('加载库区列表失败', error);
}
},
// 加载物品列表(根据类型)
async loadItemList(itemType) {
if (!itemType) return;
try {
this.itemSearchLoading = true;
if (itemType === 'raw_material') {
// 使用带BOM的接口
const response = await listRawMaterial({
pageNum: 1,
pageSize: 100,
withBom: true // 请求包含BOM信息
});
if (response.code === 200) {
this.rawMaterialList = response.rows || [];
}
} else if (itemType === 'product') {
// 使用带BOM的接口
const response = await listProduct({
pageNum: 1,
pageSize: 100,
withBom: true // 请求包含BOM信息
});
if (response.code === 200) {
this.productList = response.rows || [];
}
}
} catch (error) {
console.error('加载物品列表失败', error);
} finally {
this.itemSearchLoading = false;
}
},
// 搜索物品(支持名称和规格搜索)
async searchItems(query) {
if (!this.updateForm.itemType) {
this.$message.warning('请先选择物品类型');
return;
}
if (!query || query.trim() === '') {
// 如果搜索为空,加载默认列表
this.loadItemList(this.updateForm.itemType);
return;
}
try {
this.itemSearchLoading = true;
if (this.updateForm.itemType === 'raw_material') {
const response = await listRawMaterial({
rawMaterialName: query, // 按名称搜索
pageNum: 1,
pageSize: 50,
withBom: true
});
if (response.code === 200) {
this.rawMaterialList = response.rows || [];
}
} else if (this.updateForm.itemType === 'product') {
const response = await listProduct({
productName: query, // 按名称搜索
pageNum: 1,
pageSize: 50,
withBom: true
});
if (response.code === 200) {
this.productList = response.rows || [];
}
}
} catch (error) {
console.error('搜索物品失败', error);
} finally {
this.itemSearchLoading = false;
}
},
// 物品选择变化
handleItemChange(itemId) {
console.log('选择的物品ID:', itemId);
},
// 加载变更历史
async loadHistory() {
if (!this.currentInfo.enterCoilNo) {
return;
}
try {
this.historyLoading = true;
const response = await getMaterialCoilTrace({
enterCoilNo: this.currentInfo.enterCoilNo,
currentCoilNo: this.currentInfo.currentCoilNo || undefined
});
if (response.code === 200 && response.data) {
this.historySteps = response.data.steps || [];
}
} catch (error) {
console.error('加载变更历史失败', error);
} finally {
this.historyLoading = false;
}
},
// 复制当前信息到更新表单
copyFromCurrent() {
this.updateForm = {
currentCoilNo: this.currentInfo.currentCoilNo,
team: this.currentInfo.team,
itemType: this.currentInfo.itemType,
itemId: this.currentInfo.itemId,
grossWeight: parseFloat(this.currentInfo.grossWeight) || null,
netWeight: parseFloat(this.currentInfo.netWeight) || null,
warehouseId: this.currentInfo.warehouseId,
remark: this.currentInfo.remark
};
console.log('复制的表单数据:', this.updateForm);
// 加载对应的物品列表
if (this.updateForm.itemType) {
this.loadItemList(this.updateForm.itemType);
}
this.$message.success('已复制当前信息');
},
// 保存更新
async handleSave() {
this.$refs.updateForm.validate(async (valid) => {
if (!valid) {
return false;
}
const loadingInstance = this.$loading({
lock: true,
text: '正在更新钢卷信息,请稍后...',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
this.loading = true;
// 构造更新数据使用标准update接口直接更新原记录
const updateData = {
coilId: this.currentInfo.coilId,
enterCoilNo: this.currentInfo.enterCoilNo,
supplierCoilNo: this.currentInfo.supplierCoilNo,
currentCoilNo: this.updateForm.currentCoilNo,
team: this.updateForm.team,
itemType: this.updateForm.itemType,
itemId: this.updateForm.itemId,
grossWeight: this.updateForm.grossWeight,
netWeight: this.updateForm.netWeight,
warehouseId: this.updateForm.warehouseId,
actualWarehouseId: this.updateForm.actualWarehouseId,
remark: this.updateForm.remark
// 注意不要传newCoils否则会走批量更新逻辑
};
console.log('=== 正常更新操作 ===');
console.log('提交的更新数据:', updateData);
console.log('是否有newCoils:', updateData.newCoils);
const response = await updateMaterialCoil(updateData);
if (response.code === 200) {
this.$message.success('钢卷信息更新成功');
// 如果是从待操作列表进来的,标记操作为完成
if (this.actionId) {
await completeAction(this.actionId);
}
// 延迟返回
setTimeout(() => {
this.$router.back();
}, 1000);
} else {
this.$message.error(response.msg || '更新失败');
}
} catch (error) {
this.$message.error('更新失败');
console.error(error);
} finally {
this.loading = false;
loadingInstance.close();
}
});
},
// 取消操作
handleCancel() {
this.$router.back();
}
}
};
</script>
<style scoped lang="scss">
.typing-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;
}
}
/* 主内容区 */
.content-wrapper {
display: grid;
grid-template-columns: 400px 1fr;
gap: 20px;
align-items: stretch; // 改为stretch让子元素高度一致
}
/* 左侧面板 */
.left-panel {
min-width: 0;
display: flex;
flex-direction: column;
}
/* 右侧面板 */
.right-panel {
min-width: 0;
display: flex;
flex-direction: column;
}
/* 确保两侧卡片高度一致 */
.info-card,
.form-card {
flex: 1;
display: flex;
flex-direction: column;
::v-deep .el-card__body {
flex: 1;
display: flex;
flex-direction: column;
}
}
/* 变更历史区域(占满整行) */
.history-section {
margin-top: 20px;
}
/* 卡片头部 */
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 500;
i {
color: #0066cc;
margin-right: 5px;
}
}
/* 当前信息展示 */
.info-section {
.info-row {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.info-label {
color: #909399;
font-size: 14px;
min-width: 110px;
flex-shrink: 0;
}
.info-value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
word-break: break-all;
}
}
}
/* 变更历史 */
.history-card {
::v-deep .el-card__body {
max-height: 400px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 3px;
}
}
::v-deep .el-timeline {
padding-left: 10px;
}
}
.history-item {
.history-title {
font-weight: 500;
color: #303133;
margin-bottom: 8px;
}
.history-detail {
font-size: 13px;
color: #606266;
margin-bottom: 4px;
.detail-label {
color: #909399;
margin-right: 5px;
}
}
}
.empty-history {
text-align: center;
padding: 40px 0;
color: #909399;
i {
font-size: 48px;
margin-bottom: 10px;
display: block;
}
p {
margin: 0;
}
}
/* 表单样式优化 */
.form-card {
::v-deep .el-input-number {
width: 100%;
.el-input__inner {
text-align: left;
}
}
::v-deep .el-form-item {
margin-bottom: 20px;
}
// 修复数字输入框的样式
::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;
}
}
}
// 优化按钮文字颜色
// 实心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;
}
}
</style>