Files
klp-oa/klp-ui/src/views/wms/warehouse/components/WarehouseBirdMini.vue
砂糖 c766904b45 feat(组件): 为DragResizeBox添加localStorage持久化功能
添加storageKey属性支持将位置和尺寸保存到localStorage
初始化时优先读取存储值,拖动结束后自动保存
同时优化了CoilSelector的钢卷地图显示逻辑
2026-03-12 10:41:19 +08:00

405 lines
11 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="bird-container">
<!-- 图例 -->
<div class="legend-container">
<div class="legend-item">
<div class="legend-color layer-1"></div>
<span class="legend-text">一层</span>
</div>
<div class="legend-item">
<div class="legend-color layer-2"></div>
<span class="legend-text">二层</span>
</div>
<div class="legend-item">
<div class="legend-color occupied"></div>
<span class="legend-text">已占用</span>
</div>
<div class="legend-item">
<div class="legend-color error"></div>
<span class="legend-text">异常</span>
</div>
</div>
<!-- 分列库位容器 -->
<div class="layers-container">
<!-- 无数据提示 -->
<div class="empty-tip" v-if="Object.keys(columns).length === 0 && warehouseList.length > 0">
暂无解析到有效的库位分列数据
</div>
<div class="empty-tip" v-else-if="warehouseList.length === 0">
<el-empty description="暂无库位数据"></el-empty>
</div>
<warehouse-interlaced ref="warehouseInterlaced" v-else="warehouseList.length" :id="id" :columns="columns" :canToggle="canToggle"
:canRelease="canRelease" @split-warehouse="handleSplitWarehouse" @merge-warehouse="handleMergeWarehouse"
@release-warehouse="handleReleaseWarehouse" />
</div>
</div>
</template>
<script>
import WarehouseInterlaced from './WarehouseInterlaced.vue';
export default {
name: "WarehouseBirdMini",
components: { WarehouseInterlaced },
props: {
// 原始库位列表
warehouseList: {
type: Array,
default: () => []
},
id: {
type: String,
default: ''
},
canToggle: {
default: true,
type: Boolean
},
canRelease: {
default: false,
type: Boolean
}
},
data() {
return {
// 分列库位数据核心修改从layers改为columns
columns: {},
// 统计信息(适配分列逻辑)
statistics: {
total: 0,
columnCount: 0,
columnDetail: {},
// 占用情况
// 每一列每一层的总数和已占用数量
layerDetail: {}
},
tableData: []
};
},
watch: {
// 监听库位列表变化,重新构建分列数据
warehouseList: {
immediate: true,
handler(newVal) {
this.buildWarehouseBox(newVal);
}
}
},
methods: {
// 处理分列数据调整
resize() {
this.$refs.warehouseInterlaced?.handleResize();
},
handleSplitWarehouse(warehouse) {
this.$emit('split-warehouse', warehouse);
},
// 1. 转换layerDetail为表格所需的数组格式
formatTableData() {
const layerDetail = this.statistics.layerDetail;
this.tableData = Object.keys(layerDetail).map(column => {
const layer1 = layerDetail[column].layer1;
const layer2 = layerDetail[column].layer2;
return {
column: column, // 列号
// 第一层数据
layer1_total: layer1.total,
layer1_occupied: layer1.occupied,
layer1_free: layer1.free,
// 第二层数据
layer2_total: layer2.total,
layer2_occupied: layer2.occupied,
layer2_free: layer2.free,
// 该列总计
column_total: layer1.total + layer2.total,
column_occupied: layer1.occupied + layer2.occupied,
column_free: layer1.free + layer2.free
};
});
},
handleMergeWarehouse(warehouse) {
this.$emit('merge-warehouse', warehouse);
},
handleReleaseWarehouse(warehouse) {
this.$emit('release-warehouse', warehouse);
},
/**
* 解析第三级库位编码
* 新规则:
* 1. 前三位:数字或字母的任意组合
* 2. column从第五位开始到第一个"-"为止(支持多位数)
* 3. 保留 row两位数字、layer数字的解析规则
*/
parseWarehouseCode(code) {
if (!code) return null;
// 新正则表达式解析规则
// ^([A-Za-z0-9]{3}) 匹配前3位数字/字母)
// . 匹配第4位任意单个字符
// ([^-]+) 匹配column到第一个"-"为止至少1位
// -(\d{2}) 匹配row两位数字
// -(\d+) 匹配layer一位或多位数字
const reg = /^([A-Za-z0-9]{3})([^-]+)-(\d{2})-(\d+)$/;
const match = code.match(reg);
if (!match) {
console.warn(`库位编码解析失败:${code},格式不符合规范`);
return null;
}
return {
level: 3,
warehousePrefix: match[1], // 前三位(数字/字母替代原warehouseFirst/warehouseSecond
warehouseFirst: match[1].slice(0, 2), // 前三位的前两个字符
warehouseSecond: match[1].slice(2, 3), // 前三位的第三个字符
column: Number(match[2]), // 第五位到第一个"-"的内容(多位数)
row: Number(match[3]), // 两位数字的行号
layer: match[4] // 层级数字(可多位数)
};
},
/**
* 解析第四级库位编码格式为F2A1-X01-1
*/
parseWarehouseCodeFourth(code) {
if (!code) return null;
const reg = /^([A-Za-z0-9]{3})([^-]+)-X(\d{2})-(\d+)$/;
const match = code.match(reg);
if (!match) {
console.warn(`库位编码解析失败:${code},格式不符合规范`);
return null;
}
// console.log('match:', match);
return {
level: 4,
warehousePrefix: match[1],
warehouseFirst: match[1].slice(0, 2), // 前三位的前两个字符
warehouseSecond: match[1].slice(2, 3), // 前三位的第三个字符
// warehouseSecond: match[2],
column: Number(match[2]),
row: Number(match[3]),
layer: match[4],
};
},
buildWarehouseBox(list) {
const columnMap = {}; // 按列分组的核心对象
const statistics = {
total: list.length,
columnCount: 0,
columnDetail: {},
// 占用情况:每一列每一层的总数、已占用、未占用数量
layerDetail: {}
};
// 1. 按列分组每列内部分为layer1和layer2两个数组
list.forEach((warehouse) => {
let codeInfo = {}
if (warehouse.actualWarehouseType == 4) {
codeInfo = this.parseWarehouseCodeFourth(warehouse.actualWarehouseCode);
} else {
codeInfo = this.parseWarehouseCode(warehouse.actualWarehouseCode);
}
if (!codeInfo) return;
const { layer, row, column } = codeInfo;
warehouse.parsedInfo = codeInfo;
// 初始化列数据结构每列包含layer1、layer2数组以及最大行号
if (!columnMap[column]) {
columnMap[column] = {
maxRow: 0,
layer1: [], // 第一层库位数组
layer2: [], // 第二层库位数组
total: 0 // 该列总库位数
};
}
// 更新列的最大行号
columnMap[column].maxRow = Math.max(columnMap[column].maxRow, row);
// 根据层数将库位放入对应数组
if (layer === '1' || layer === 1) {
columnMap[column].layer1.push(warehouse);
} else if (layer === '2' || layer === 2) {
columnMap[column].layer2.push(warehouse);
}
// 更新该列总库位数
columnMap[column].total = columnMap[column].layer1.length + columnMap[column].layer2.length;
});
// 2. 对每列的两层数据分别按行号排序
Object.keys(columnMap).forEach((column) => {
const columnData = columnMap[column];
// 按行号排序(保证展示顺序正确)
columnData.layer1.sort((a, b) => a.parsedInfo.row - b.parsedInfo.row);
columnData.layer2.sort((a, b) => a.parsedInfo.row - b.parsedInfo.row);
});
// 3. 更新统计信息(适配分列逻辑)
statistics.columnCount = Object.keys(columnMap).length;
Object.keys(columnMap).forEach((column) => {
const columnData = columnMap[column];
// 列维度总数量
statistics.columnDetail[column] = columnData.total;
// ========== 新增layerDetail 层级统计逻辑 ==========
// 初始化当前列的层级统计结构
statistics.layerDetail[column] = {
layer1: {
total: 0, // 该层总库位数
occupied: 0, // 已占用isEnabled=0
free: 0 // 未占用isEnabled=1
},
layer2: {
total: 0,
occupied: 0,
free: 0
}
};
// 统计第一层
const layer1Data = columnData.layer1;
statistics.layerDetail[column].layer1.total = layer1Data.length;
statistics.layerDetail[column].layer1.occupied = layer1Data.filter(item => item.isEnabled === 0).length;
statistics.layerDetail[column].layer1.free = layer1Data.filter(item => item.isEnabled === 1).length;
// 统计第二层
const layer2Data = columnData.layer2;
statistics.layerDetail[column].layer2.total = layer2Data.length;
statistics.layerDetail[column].layer2.occupied = layer2Data.filter(item => item.isEnabled === 0).length;
statistics.layerDetail[column].layer2.free = layer2Data.filter(item => item.isEnabled === 1).length;
});
// 4. 赋值到响应式数据
this.columns = columnMap;
this.statistics = statistics;
this.formatTableData();
},
/**
* 打开初始化弹窗(透传至根组件)
*/
openInitDialog() {
this.$emit('open-init-dialog');
}
},
};
</script>
<style scoped lang="scss">
.bird-container {
width: 100%;
height: 100%;
}
// 统计卡片样式
.statistics-card {
margin-bottom: 16px;
.statistics-item {
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #606266;
font-size: 14px;
}
.value {
color: #303133;
font-size: 14px;
font-weight: 500;
margin-left: 8px;
}
}
}
// 图例样式
.legend-container {
display: flex;
align-items: center;
margin-bottom: 16px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 4px;
.legend-item {
display: flex;
align-items: center;
margin-right: 24px;
&:last-child {
margin-right: 0;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 2px;
margin-right: 8px;
border: 1px solid #e6e6e6;
}
.normal {
background-color: #ffffff;
}
.layer-1 {
background-color: #fff3e0;
}
.layer-2 {
background-color: #e8f5e9;
}
.occupied {
background-color: #fafafa;
}
.error {
background-color: #fdecea;
}
.legend-text {
font-size: 14px;
color: #606266;
}
}
}
// 分列容器样式
.layers-container {
display: flex;
.layer-section {
flex: 1;
max-width: 50%;
margin-bottom: 24px;
}
.empty-tip {
text-align: center;
padding: 32px;
color: #909399;
font-size: 14px;
background: #fff;
border-radius: 8px;
.empty-text {
margin-bottom: 16px;
}
}
}
</style>