Files
klp-oa/klp-ui/src/views/wms/warehouse/components/WarehouseInterlaced.vue
砂糖 b5e053671f feat(仓库管理): 实现交错式多层仓库可视化组件
- 新增 WarehouseInterlaced 组件用于展示交错式多层仓库布局
- 修改仓库编码解析规则以支持新的格式
- 移除不再使用的 layerCount 字段
- 更新仓库鸟瞰图组件以使用新的交错式布局
- 调整图例样式和颜色区分不同层级
2025-12-08 11:48:20 +08:00

372 lines
10 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="multi-layer-grid">
<!-- 顶部列标尺 -->
<div class="col-ruler" :style="{ '--cell-width': `${cellWidth}px` }">
<div class="ruler-empty"></div>
<div v-for="col in maxColumn" :key="`col-${col}`" class="ruler-item">
{{ col }}
</div>
</div>
<!-- 左侧行标尺 + 库位网格 -->
<div class="row-grid-wrapper">
<!-- 左侧行标尺 -->
<div class="row-ruler">
<div v-for="row in maxRow" :key="`row-${row}`" class="ruler-item">
{{ row }}
</div>
</div>
<!-- 库位网格容器 -->
<div
class="grid-container"
:style="{
'--half-cell-width': `${halfCellWidth}px`,
'--cell-width': `${cellWidth}px`,
'--column-count': maxColumn * 2,
'--row-height': '80px'
}"
>
<!-- 第一层库位左半列 -->
<div
v-for="warehouse in layer1Warehouses"
:key="`warehouse-1-${warehouse.actualWarehouseId}`"
class="warehouse-cell layer-1"
:class="{ disabled: warehouse.isEnabled === 0 }"
:style="{
gridRow: `${warehouse.parsedInfo.row} / ${warehouse.parsedInfo.row + 1}`,
gridColumn: `${(warehouse.parsedInfo.column - 1) * 2 + 1} / ${(warehouse.parsedInfo.column - 1) * 2 + 2}`
}"
@click="handleCellClick(warehouse)"
>
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
</div>
</div>
<!-- 第二层库位右半列 + 下移半格 -->
<div
v-for="warehouse in layer2Warehouses"
:key="`warehouse-2-${warehouse.actualWarehouseId}`"
class="warehouse-cell layer-2"
:class="{ disabled: warehouse.isEnabled === 0 }"
:style="{
gridRow: `${warehouse.parsedInfo.row} / ${warehouse.parsedInfo.row + 1}`,
gridColumn: `${(warehouse.parsedInfo.column - 1) * 2 + 2} / ${(warehouse.parsedInfo.column - 1) * 2 + 3}`
}"
@click="handleCellClick(warehouse)"
>
<div class="cell-name">
<div class="cell-line1">{{ warehouse.actualWarehouseName || '-' }}</div>
<div class="cell-line2">{{ warehouse.currentCoilNo || '-' }}</div>
</div>
</div>
</div>
</div>
<!-- 库位详情弹窗 -->
<el-dialog
title="钢卷库位详情"
:visible.sync="dialogVisible"
width="600px"
destroy-on-close
append-to-body
center
>
<el-descriptions :column="2" border size="small" v-if="currentWarehouse">
<el-descriptions-item label="库位编码">{{ currentWarehouse.actualWarehouseCode }}</el-descriptions-item>
<el-descriptions-item label="库位名称">{{ currentWarehouse.actualWarehouseName || '无' }}</el-descriptions-item>
<el-descriptions-item label="钢卷编号">{{ currentWarehouse.currentCoilNo || '无' }}</el-descriptions-item>
<el-descriptions-item label="所属层级">{{ currentWarehouse.parsedInfo.layer || '未知' }}</el-descriptions-item>
<el-descriptions-item label="行号">{{ currentWarehouse.parsedInfo.row || '未知' }}</el-descriptions-item>
<el-descriptions-item label="列号">{{ currentWarehouse.parsedInfo.column || '未知' }}</el-descriptions-item>
<el-descriptions-item label="启用状态">
<el-tag :type="currentWarehouse.isEnabled === 1 ? 'success' : 'danger'">
{{ currentWarehouse.isEnabled === 1 ? '启用' : '禁用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建时间" span="2">{{ currentWarehouse.createTime || '无' }}</el-descriptions-item>
</el-descriptions>
<template slot="footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
export default {
name: "SteelCoilWarehouse",
props: {
layers: {
type: Object,
required: true,
default: () => ({})
}
},
data() {
return {
dialogVisible: false,
currentWarehouse: null,
containerWidth: 0,
resizeTimer: null
};
},
computed: {
layer1Warehouses() {
return this.layers['1']?.warehouses || [];
},
layer2Warehouses() {
return this.layers['2']?.warehouses || [];
},
maxRow() {
let max = 0;
Object.keys(this.layers).forEach(layer => {
max = Math.max(max, this.layers[layer].maxRow);
});
return max;
},
maxColumn() {
let max = 0;
Object.keys(this.layers).forEach(layer => {
max = Math.max(max, this.layers[layer].maxColumn);
});
return max;
},
cellWidth() {
if (!this.containerWidth || this.maxColumn === 0) return 60;
const availableWidth = Math.max(0, this.containerWidth - 30);
const averageWidth = availableWidth / this.maxColumn;
return averageWidth;
},
halfCellWidth() {
return this.cellWidth / 2;
}
},
mounted() {
this.calcContainerWidth();
window.addEventListener('resize', this.handleResize);
},
beforeUnmount() {
window.removeEventListener('resize', this.handleResize);
clearTimeout(this.resizeTimer);
},
methods: {
calcContainerWidth() {
this.containerWidth = this.$el.parentElement?.clientWidth || this.$el.clientWidth || 0;
},
handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
this.calcContainerWidth();
}, 100);
},
handleCellClick(warehouse) {
this.currentWarehouse = warehouse;
this.dialogVisible = true;
}
}
};
</script>
<style scoped lang="scss">
/* 外层容器:填满父元素 + 隐藏滚动条 */
.multi-layer-grid {
width: 100% !important;
height: 100%;
box-sizing: border-box;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
}
/* 列标尺 */
.col-ruler {
display: flex;
height: 30px;
line-height: 30px;
background: #f5f7fa; /* 更柔和的标尺背景 */
border-bottom: 1px solid #e4e7ed;
width: 100% !important;
box-sizing: border-box;
overflow: hidden !important;
.ruler-empty {
width: 30px;
text-align: center;
font-weight: 600;
color: #606266;
border-right: 1px solid #e4e7ed;
flex-shrink: 0;
}
.ruler-item {
width: var(--cell-width);
text-align: center;
font-weight: 600;
color: #606266;
border-right: 1px solid #e4e7ed;
box-sizing: border-box;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.row-grid-wrapper {
display: flex;
width: 100% !important;
height: calc(100% - 30px);
box-sizing: border-box;
overflow: hidden !important;
}
/* 行标尺 */
.row-ruler {
width: 30px;
background: #f5f7fa; /* 更柔和的标尺背景 */
border-right: 1px solid #e4e7ed;
flex-shrink: 0;
overflow: hidden !important;
.ruler-item {
height: 80px;
line-height: 80px;
text-align: center;
font-weight: 600;
color: #606266;
border-bottom: 1px solid #e4e7ed;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
/* 网格容器 */
.grid-container {
display: grid;
width: 100% !important;
grid-template-columns: repeat(var(--column-count), minmax(0, var(--half-cell-width)));
grid-auto-rows: var(--row-height);
gap: 1px;
box-sizing: border-box;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
}
/* 库位单元格基础样式 */
.warehouse-cell {
height: var(--row-height);
border: 1px solid #e4e7ed;
border-radius: 4px; /* 圆角更柔和 */
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s;
box-sizing: border-box;
position: relative;
z-index: 1;
overflow: hidden;
/* 启用状态 - 第一层:柔和暖橙(低饱和度) */
&.layer-1:not(.disabled) {
background: #fff3e0; /* 柔和浅橙 */
color: #e65100; /* 文字用深一点的橙,保证可读性 */
}
/* 启用状态 - 第二层:柔和薄荷绿(低饱和度) */
&.layer-2:not(.disabled) {
background: #e8f5e9; /* 柔和浅绿 */
color: #2e7d32; /* 文字用深一点的绿,保证可读性 */
transform: translateY(50%);
position: relative;
top: 0;
}
/* 禁用状态 - 通用浅灰 */
&.disabled {
background: #111; /* 浅灰背景 */
color: #909399; /* 浅灰文字 */
cursor: not-allowed; /* 禁用光标 */
opacity: 0.8; /* 降低透明度 */
/* 禁用状态取消hover效果 */
&:hover {
border-color: #e4e7ed;
background: #fafafa;
z-index: 1;
}
}
/* 启用状态hover效果更柔和 */
&:not(.disabled):hover {
border-color: #90caf9; /* 柔和的蓝色边框 */
background: #f0f8ff; /* 极浅的蓝背景 */
z-index: 2;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); /* 轻微阴影,提升层次感 */
}
/* 两行文字样式 */
.cell-name {
width: 100%;
height: 90%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 0 4px;
box-sizing: border-box;
/* 第一行:库位名称 */
.cell-line1 {
font-size: 14px;
font-weight: 600;
line-height: 1.2;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
/* 第二行currentCoilNo */
.cell-line2 {
font-size: 13px;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
/* 禁用状态文字颜色 */
.disabled & .cell-line1,
.disabled & .cell-line2 {
color: #909399 !important;
}
/* 启用状态文字颜色适配 */
.layer-1:not(.disabled) & .cell-line1,
.layer-1:not(.disabled) & .cell-line2 {
color: #e65100;
}
.layer-2:not(.disabled) & .cell-line1,
.layer-2:not(.disabled) & .cell-line2 {
color: #2e7d32;
}
}
}
</style>