feat(仓库管理): 优化仓库网格布局并增强库位编码解析

- 在WarehouseGrid组件中添加layerData变化的调试日志
- 将overview.vue中的网格列数从10增加到30
- 重构WarehouseBird组件中的库位编码解析逻辑,支持更灵活的编码格式
- 优化WarehouseInterlaced组件的布局,添加横向滚动容器并改进单元格宽度计算
- 同步列标尺和网格区域的滚动行为
This commit is contained in:
砂糖
2026-01-07 10:18:26 +08:00
parent b0ee494434
commit 6f57ea2c3f
4 changed files with 167 additions and 91 deletions

View File

@@ -48,11 +48,8 @@
<div class="empty-text">该分类下暂无库位数据</div>
<el-button type="primary" icon="el-icon-plus" @click="openInitDialog">初始化库位</el-button>
</div>
<warehouse-interlaced
v-else="warehouseList.length"
:id="id" :columns="columns"
@split-warehouse="handleSplitWarehouse"
@merge-warehouse="handleMergeWarehouse" />
<warehouse-interlaced v-else="warehouseList.length" :id="id" :columns="columns"
@split-warehouse="handleSplitWarehouse" @merge-warehouse="handleMergeWarehouse" />
</div>
</div>
</template>
@@ -105,10 +102,21 @@ export default {
},
/**
* 解析第三级库位编码
* 新规则:
* 1. 前三位:数字或字母的任意组合
* 2. column从第五位开始到第一个"-"为止(支持多位数)
* 3. 保留 row两位数字、layer数字的解析规则
*/
parseWarehouseCode(code) {
if (!code) return null;
const reg = /^([A-Za-z])(\d+[A-Za-z])(\d)-(\d{2})-(\d+)$/;
// 新正则表达式解析规则
// ^([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) {
@@ -118,11 +126,12 @@ export default {
return {
level: 3,
warehouseFirst: match[1],
warehouseSecond: match[2],
column: Number(match[3]),
row: Number(match[4]),
layer: match[5],
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] // 层级数字(可多位数)
};
},
@@ -131,7 +140,7 @@ export default {
*/
parseWarehouseCodeFourth(code) {
if (!code) return null;
const reg = /^([A-Za-z])(\d+[A-Za-z])(\d)-X(\d{2})-(\d+)$/;
const reg = /^([A-Za-z0-9]{3})([^-]+)-X(\d{2})-(\d+)$/;
const match = code.match(reg);
if (!match) {
@@ -139,13 +148,17 @@ export default {
return null;
}
// console.log('match:', match);
return {
level: 4,
warehouseFirst: match[1],
warehouseSecond: match[2],
column: Number(match[3]),
row: Number(match[4]),
layer: match[5],
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],
};
},
@@ -200,7 +213,7 @@ export default {
// 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);
@@ -310,7 +323,7 @@ export default {
// 分列容器样式
.layers-container {
display: flex;
.layer-section {
flex: 1;
max-width: 50%;

View File

@@ -163,6 +163,7 @@ export default {
layerData: {
deep: true,
handler() {
console.log('layerData 变化:', this.layerData);
this.calcContainerWidth();
}
}

View File

@@ -1,72 +1,75 @@
<template>
<div class="multi-layer-grid" ref="gridContainer">
<el-alert v-if="!isComposite" type="warning" title="当前库区不支持复合架" show-icon></el-alert>
<!-- 列标尺区域 - 添加切换按钮 -->
<div class="col-ruler" :style="{ '--cell-width': `${cellWidth}px` }">
<div class="ruler-empty"></div>
<div v-for="col in sortedColumnKeys" :key="`col-${col}`" class="ruler-item">
<!-- 列标尺文本 -->
<span class="column-number">{{ col }}</span>
<!-- 拆分/合并切换按钮 -->
<button class="split-merge-toggle"
v-if="isComposite && splitColumns.includes(Number(col))"
:class="{ 'is-split': getColumnLevel(col) === 4, 'is-merge': getColumnLevel(col) === 3 }"
@click.stop="handleColumnToggle(col)" :title="getColumnLevel(col) === 3 ? '点击切换为小卷状态' : '点击切换为大卷状态'">
<i class="el-icon-s-tools"></i>
<span class="toggle-text">{{ getColumnLevel(col) === 3 ? '大卷状态' : '小卷状态' }}</span>
</button>
</div>
</div>
<div class="row-grid-wrapper">
<div class="row-ruler" :style="{ '--total-height': `${rulerTotalHeight}px` }">
<div v-for="row in rulerMaxRow" :key="`row-${row}`" class="ruler-item">
{{ row }}
<!-- 1. 新增横向滚动容器包裹列标尺和网格区域 -->
<div class="scroll-wrapper" ref="scrollWrapper">
<!-- 列标尺区域 - 添加切换按钮 -->
<div class="col-ruler" :style="{ '--cell-width': `${cellWidth}px` }">
<div class="ruler-empty"></div>
<div v-for="col in sortedColumnKeys" :key="`col-${col}`" class="ruler-item">
<!-- 列标尺文本 -->
<span class="column-number">{{ col }}</span>
<!-- 拆分/合并切换按钮 -->
<button class="split-merge-toggle"
v-if="isComposite && splitColumns.includes(Number(col))"
:class="{ 'is-split': getColumnLevel(col) === 4, 'is-merge': getColumnLevel(col) === 3 }"
@click.stop="handleColumnToggle(col)" :title="getColumnLevel(col) === 3 ? '点击切换为小卷状态' : '点击切换为大卷状态'">
<i class="el-icon-s-tools"></i>
<span class="toggle-text">{{ getColumnLevel(col) === 3 ? '大卷状态' : '小卷状态' }}</span>
</button>
</div>
</div>
<div class="grid-container" :style="{
'--half-cell-width': `${halfCellWidth}px`,
'--cell-width': `${cellWidth}px`,
'--column-count': sortedColumnKeys.length * 2,
'--total-height': `${rulerTotalHeight}px`
}">
<template v-for="column in sortedColumnKeys">
<div class="column-container layer-1-container" v-if="columnWarehouseData[column]" :style="{
gridRow: '1 / -1',
gridColumn: `${(column - 1) * 2 + 1} / ${(column - 1) * 2 + 2}`,
'--item-height': `${columnWarehouseData[column].cellHeight}px`
}">
<div v-for="warehouse in columnWarehouseData[column].layer1"
:key="`warehouse-1-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-1"
:class="{ disabled: warehouse.isEnabled === 0 }" @click.stop="handleCellClick(warehouse)">
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
</div>
</div>
<div class="row-grid-wrapper">
<div class="row-ruler" :style="{ '--total-height': `${rulerTotalHeight}px` }">
<div v-for="row in rulerMaxRow" :key="`row-${row}`" class="ruler-item">
{{ row }}
</div>
</div>
<div class="column-container layer-2-container" v-if="columnWarehouseData[column]" :style="{
gridRow: '1 / -1',
gridColumn: `${(column - 1) * 2 + 2} / ${(column - 1) * 2 + 3}`,
'--item-height': `${columnWarehouseData[column].cellHeight}px`,
'--offset-value': `${columnWarehouseData[column].cellHeight * 0.5}px`,
height: `${(columnWarehouseData[column].layer2.length * columnWarehouseData[column].cellHeight)}px`
}">
<div v-for="warehouse in columnWarehouseData[column].layer2"
:key="`warehouse-2-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-2"
:class="{ disabled: warehouse.isEnabled === 0 }" :style="{
transform: `translateY(var(--offset-value))`,
position: 'relative'
}" @click.stop="handleCellClick(warehouse)">
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
<div class="grid-container" :style="{
'--half-cell-width': `${halfCellWidth}px`,
'--cell-width': `${cellWidth}px`,
'--column-count': sortedColumnKeys.length * 2,
'--total-height': `${rulerTotalHeight}px`
}">
<template v-for="column in sortedColumnKeys">
<div class="column-container layer-1-container" v-if="columnWarehouseData[column]" :style="{
gridRow: '1 / -1',
gridColumn: `${(column - 1) * 2 + 1} / ${(column - 1) * 2 + 2}`,
'--item-height': `${columnWarehouseData[column].cellHeight}px`
}">
<div v-for="warehouse in columnWarehouseData[column].layer1"
:key="`warehouse-1-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-1"
:class="{ disabled: warehouse.isEnabled === 0 }" @click.stop="handleCellClick(warehouse)">
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
</div>
</div>
</div>
</div>
</template>
<div class="column-container layer-2-container" v-if="columnWarehouseData[column]" :style="{
gridRow: '1 / -1',
gridColumn: `${(column - 1) * 2 + 2} / ${(column - 1) * 2 + 3}`,
'--item-height': `${columnWarehouseData[column].cellHeight}px`,
'--offset-value': `${columnWarehouseData[column].cellHeight * 0.5}px`,
height: `${(columnWarehouseData[column].layer2.length * columnWarehouseData[column].cellHeight)}px`
}">
<div v-for="warehouse in columnWarehouseData[column].layer2"
:key="`warehouse-2-${warehouse.actualWarehouseId}`" class="warehouse-cell layer-2"
:class="{ disabled: warehouse.isEnabled === 0 }" :style="{
transform: `translateY(var(--offset-value))`,
position: 'relative'
}" @click.stop="handleCellClick(warehouse)">
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
@@ -186,9 +189,12 @@ export default {
return this.rulerMaxRow * this.rulerRowHeight;
},
cellWidth() {
if (!this.containerWidth || this.sortedColumnKeys.length === 0) return 60;
const availableWidth = Math.max(0, this.containerWidth - 30);
return availableWidth / this.sortedColumnKeys.length;
// 2. 优化cellWidth计算最小宽度从120调整为80避免列数过多时宽度过大导致溢出
if (!this.containerWidth || this.sortedColumnKeys.length === 0) return 120;
const availableWidth = Math.max(0, this.containerWidth - 30); // 30是行标尺宽度
const calcWidth = availableWidth / this.sortedColumnKeys.length;
// 最小宽度80px最大不限制交给滚动容器处理
return Math.max(120, calcWidth);
},
halfCellWidth() {
return this.cellWidth / 2;
@@ -237,6 +243,8 @@ export default {
immediate: true,
deep: true,
handler(newVal) {
console.log('columns 变化:', newVal);
if (this.isMounted) {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
@@ -250,13 +258,34 @@ export default {
this.isMounted = true;
this.calcContainerWidth();
window.addEventListener('resize', this.handleResize);
// 3. 绑定滚动同步事件:列标尺和网格区域同步滚动
const scrollWrapper = this.$refs.scrollWrapper;
if (scrollWrapper) {
scrollWrapper.addEventListener('scroll', this.syncScroll);
}
},
beforeUnmount() {
window.removeEventListener('resize', this.handleResize);
clearTimeout(this.resizeTimer);
this.isMounted = false;
// 移除滚动监听
const scrollWrapper = this.$refs.scrollWrapper;
if (scrollWrapper) {
scrollWrapper.removeEventListener('scroll', this.syncScroll);
}
},
methods: {
// 4. 同步滚动方法(确保列标尺和网格滚动位置一致)
syncScroll(e) {
const target = e.target;
// 确保滚动行为同步此处主要是统一滚动容器的滚动无需额外处理因为都在同一个scroll-wrapper里
this.$nextTick(() => {
const colRuler = target.querySelector('.col-ruler');
if (colRuler) {
colRuler.scrollLeft = target.scrollLeft;
}
});
},
// 获取指定列的level值3=合并4=拆分)
getColumnLevel(column) {
const columnData = this.columns[column] || {};
@@ -391,6 +420,34 @@ export default {
box-sizing: border-box;
margin: 0;
padding: 0;
// 主容器隐藏横向溢出滚动交给内部scroll-wrapper
overflow: hidden;
}
// 5. 新增滚动容器样式:统一管理列标尺和网格的横向滚动
.scroll-wrapper {
width: 100%;
height: calc(100% - 42px); // 42是alert提示框高度可根据实际调整
overflow-x: auto; // 横向滚动,纵向隐藏
overflow-y: hidden;
scrollbar-width: thin; // 优化滚动条样式Firefox
scrollbar-color: #ccc #f5f7fa;
// 优化Chrome滚动条样式
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: #f5f7fa;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
&:hover {
background: #999;
}
}
}
.col-ruler {
@@ -399,9 +456,10 @@ export default {
line-height: 30px;
background: #f5f7fa;
border-bottom: 1px solid #e4e7ed;
width: 100%;
width: fit-content; // 6. 列标尺宽度自适应内容而非100%
box-sizing: border-box;
overflow: hidden;
// 移除overflow: hidden让滚动容器控制溢出
// overflow: hidden;
.ruler-empty {
width: 30px;
@@ -481,10 +539,11 @@ export default {
.row-grid-wrapper {
display: flex;
width: 100%;
width: fit-content; // 7. 网格容器宽度自适应内容
height: calc(100% - 30px);
box-sizing: border-box;
overflow: hidden;
// 移除overflow: hidden交给scroll-wrapper控制
// overflow: hidden;
}
.row-ruler {
@@ -507,16 +566,17 @@ export default {
}
.grid-container {
display: grid;
width: 100%;
grid-template-columns: repeat(var(--column-count), minmax(0, var(--half-cell-width)));
grid-auto-rows: var(--total-height);
display: flex;
width: fit-content; // 8. 网格宽度自适应
// grid-template-columns: repeat(var(--column-count), minmax(0, var(--half-cell-width)));
// grid-auto-rows: var(--total-height);
gap: 0px;
box-sizing: border-box;
margin: 0;
padding: 0;
height: var(--total-height);
overflow: hidden;
// 移除overflow: hidden交给scroll-wrapper控制
// overflow: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
@@ -529,6 +589,7 @@ export default {
.column-container {
display: flex;
width: var(--half-cell-width);
flex-direction: column;
justify-content: flex-start;
padding: 1px;
@@ -553,6 +614,7 @@ export default {
border-radius: 4px;
display: flex;
justify-content: center;
min-width: var(--half-cell-width);
align-items: center;
cursor: pointer;
transition: all 0.2s;

View File

@@ -42,7 +42,7 @@
<div class="grid-selector" @mousemove="handleGridHover" @click="confirmGridSelect"
@mouseleave="resetGridHover">
<div v-for="row in 40" :key="`grid-row-${row}`" class="grid-selector-row">
<div v-for="col in 10" :key="`grid-col-${col}`" class="grid-selector-cell" :class="{
<div v-for="col in 30" :key="`grid-col-${col}`" class="grid-selector-cell" :class="{
hovered: row <= hoverRow && col <= hoverCol,
selected: row <= initForm.rowCount && col <= initForm.columnCount
}">