Files
klp-oa/klp-ui/src/views/wms/warehouse/overview.vue
砂糖 32d4a1a6e0 feat(仓库管理): 优化仓库鸟瞰图布局并添加图例
- 在仓库鸟瞰图中添加占用状态图例
- 调整网格布局为两栏显示,优化空间利用率
- 减少网格行数从50到40
- 缩小网格单元格尺寸并移除冗余编码显示
- 添加按钮加载状态控制
2025-12-05 13:03:08 +08:00

391 lines
12 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="app-container">
<!-- 整体布局 -->
<div class="layout-container">
<!-- 左侧树形结构 -->
<div class="tree-container" v-loading="treeLoading">
<el-tree
ref="warehouseTreeRef"
:data="warehouseTree"
:props="treeProps"
node-key="actualWarehouseId"
@node-click="handleNodeClick"
:expand-on-click-node="false"
highlight-current
class="warehouse-tree"
:disabled="isSwitching"
>
</el-tree>
</div>
<!-- 右侧仓库信息区域 - 替换为 Bird 组件 -->
<div class="warehouse-container" v-if="selectedNodeId" v-loading="rightLoading"
element-loading-text="加载中..." element-loading-spinner="el-icon-loading">
<WarehouseBird :warehouse-list="warehouseList" @open-init-dialog="openInitDialog" />
</div>
<!-- 未选中节点提示 -->
<div class="empty-select-tip" v-if="!selectedNodeId">
请选择左侧仓库分类查看库位信息
</div>
</div>
<!-- 库位初始化弹窗 -->
<el-dialog title="库位初始化" :visible.sync="initDialogVisible" width="700px" destroy-on-close append-to-body center>
<el-form ref="initFormRef" :model="initForm" :rules="initFormRules" label-width="100px" size="mini">
<!-- 可视化网格选择区域 -->
<el-form-item label="行列选择" prop="gridSelect">
<div class="grid-selector-container">
<div class="selector-tip">
拖动/点击选择网格范围当前{{ initForm.rowCount || 0 }} × {{ initForm.columnCount || 0 }}
</div>
<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="{
hovered: row <= hoverRow && col <= hoverCol,
selected: row <= initForm.rowCount && col <= initForm.columnCount
}">
</div>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="层数" prop="layerCount">
<el-input-number size="mini" v-model="initForm.layerCount" :min="1" :max="99" placeholder="请输入层数1-99"
style="width: 100%;" />
</el-form-item>
<el-form-item label="编码前缀" prop="prefix">
<el-input v-model="initForm.prefix" disabled placeholder="系统自动生成" />
</el-form-item>
</el-form>
<template slot="footer">
<el-button @click="initDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitInitForm">确认初始化</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { listActualWarehouse, treeActualWarehouseTwoLevel, getActualWarehouse, generateLocations } from "@/api/wms/actualWarehouse";
import WarehouseBird from './components/WarehouseBird.vue';
export default {
name: "Overview",
components: { WarehouseBird },
data() {
// 自定义验证规则:正整数
const positiveIntegerValidator = (rule, value, callback) => {
if (!value) {
callback(new Error('请输入正整数'));
} else if (!/^[1-9]\d*$/.test(value)) {
callback(new Error('请输入大于0的正整数'));
} else {
callback();
}
};
// 网格选择验证规则
const gridSelectValidator = (rule, value, callback) => {
if (!this.initForm.rowCount || !this.initForm.columnCount) {
callback(new Error('请先选择网格行列数'));
} else {
callback();
}
};
return {
warehouseTree: [],
treeProps: { label: "actualWarehouseName", children: "children" },
selectedNodeId: "",
selectedNode: null,
warehouseList: [], // 透传给 Bird 组件的原始数据
// 初始化弹窗相关
initDialogVisible: false,
initForm: {
rowCount: '', columnCount: '', layerCount: '', prefix: '', parentId: ''
},
hoverRow: 0,
hoverCol: 0,
initFormRules: {
gridSelect: [{ validator: gridSelectValidator, trigger: 'change' }],
layerCount: [{ validator: positiveIntegerValidator, trigger: 'blur' }],
parentId: [{ required: true, message: '父节点ID不能为空', trigger: 'blur' }]
},
// 加载状态
treeLoading: false,
rightLoading: false,
isSwitching: false,
nodeClickTimer: null,
buttonLoading: false,
};
},
created() {
this.getWarehouseTree();
},
beforeDestroy() {
if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer);
},
methods: {
// 获取树形数据
getWarehouseTree() {
this.treeLoading = true;
treeActualWarehouseTwoLevel()
.then((res) => { this.warehouseTree = res.data || []; })
.catch((err) => { this.$message.error("获取仓库树形数据失败:" + err.message); })
.finally(() => { this.treeLoading = false; });
},
// 树节点点击
handleNodeClick(node) {
if (this.isSwitching) return;
if (this.nodeClickTimer) clearTimeout(this.nodeClickTimer);
this.nodeClickTimer = setTimeout(() => {
if (!node.children || node.children.length === 0) {
this.isSwitching = true;
this.rightLoading = true;
this.selectedNodeId = node.actualWarehouseId;
this.selectedNode = node;
this.getWarehouseList(node.actualWarehouseId)
.finally(() => {
this.rightLoading = false;
this.isSwitching = false;
});
} else {
this.selectedNodeId = "";
this.selectedNode = null;
this.warehouseList = [];
}
}, 300);
},
// 获取库位列表
getWarehouseList(parentId) {
return listActualWarehouse({ parentId })
.then((res) => { this.warehouseList = res.data || []; })
.catch((err) => {
this.$message.error("获取库位数据失败:" + err.message);
this.warehouseList = [];
});
},
// 打开初始化弹窗
async openInitDialog() {
if (!this.selectedNode) {
this.$message.warning('请先选择左侧仓库分类');
return;
}
try {
await this.$confirm(
'初始化库位将批量生成指定数量的库位数据,此操作不可撤销!请确认是否继续?',
'重要提示',
{ confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }
);
} catch { return; }
this.$nextTick(() => {
this.$refs.initFormRef?.resetFields();
this.initForm.rowCount = '';
this.initForm.columnCount = '';
this.hoverRow = 0;
this.hoverCol = 0;
});
const prefix = await this.generateWarehousePrefix(this.selectedNode);
this.initForm = {
rowCount: '', columnCount: '', layerCount: '',
prefix: prefix,
parentId: this.selectedNode.actualWarehouseId
};
this.initDialogVisible = true;
},
// 生成编码前缀
async generateWarehousePrefix(node) {
try {
const parentRes = await getActualWarehouse(node.parentId);
const parentCode = parentRes.data.actualWarehouseCode || '';
const nodeCode = node.actualWarehouseCode || '';
return (parentCode + nodeCode).toUpperCase();
} catch (err) {
this.$message.error('生成编码前缀失败,将使用默认前缀');
return 'DEFAULT';
}
},
// 网格选择悬浮
handleGridHover(e) {
const cell = e.target.closest('.grid-selector-cell');
if (!cell) return;
const row = Array.from(cell.parentElement.parentElement.children).indexOf(cell.parentElement) + 1;
const col = Array.from(cell.parentElement.children).indexOf(cell) + 1;
this.hoverRow = Math.min(row, 99);
this.hoverCol = Math.min(col, 99);
},
// 确认网格选择
confirmGridSelect() {
if (this.hoverRow < 1 || this.hoverCol < 1) return;
this.initForm.rowCount = this.hoverRow;
this.initForm.columnCount = this.hoverCol;
this.$refs.initFormRef.validateField('gridSelect');
},
// 重置网格悬浮
resetGridHover() {
this.hoverRow = this.initForm.rowCount || 0;
this.hoverCol = this.initForm.columnCount || 0;
},
// 提交初始化表单
submitInitForm() {
this.$refs.initFormRef.validate(async (valid) => {
if (valid) {
const params = {
rowCount: this.initForm.rowCount,
columnCount: this.initForm.columnCount,
layerCount: this.initForm.layerCount,
prefix: this.initForm.prefix,
parentId: this.initForm.parentId
};
const loadingInstance = this.$loading({
lock: true, text: '正在初始化库位,请稍候...',
spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)'
});
try {
await generateLocations(params);
this.$message.success('库位初始化成功!');
this.initDialogVisible = false;
await this.getWarehouseList(this.selectedNodeId);
} catch (err) {
this.$message.error('库位初始化失败:' + err.message);
} finally {
loadingInstance.close();
}
} else {
this.$message.warning('请完善表单必填信息!');
return false;
}
});
}
},
};
</script>
<style scoped lang="scss">
.app-container {
width: 100%;
height: calc(100vh - 90px);
padding: 16px;
box-sizing: border-box;
background: #f5f7fa;
}
.layout-container {
display: flex;
width: 100%;
height: 100%;
gap: 16px;
.tree-container {
width: 280px;
height: 100%;
background: #fff;
border-radius: 8px;
padding: 16px;
box-sizing: border-box;
overflow-y: auto;
.warehouse-tree {
--el-tree-text-color: #303133;
--el-tree-node-hover-bg-color: #e8f4ff;
--el-tree-current-bg-color: #409eff;
--el-tree-current-text-color: #fff;
}
}
.warehouse-container {
flex: 1;
height: 100%;
overflow-y: auto;
}
.empty-select-tip {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
color: #909399;
font-size: 16px;
background: #fff;
border-radius: 8px;
}
}
// 初始化网格选择器样式
.grid-selector-container {
width: 100%;
padding: 10px;
box-sizing: border-box;
.selector-tip {
margin-bottom: 10px;
font-size: 12px;
color: #606266;
}
.grid-selector {
width: 100%;
max-width: 500px;
max-height: 300px;
overflow: auto;
border: 1px solid #e6e6e6;
background: #fafafa;
cursor: crosshair;
.grid-selector-row {
display: flex;
.grid-selector-cell {
width: 20px;
height: 20px;
border: 1px solid #e0e0e0;
box-sizing: border-box;
transition: all 0.1s;
&.hovered {
background: #e8f4ff;
border-color: #409eff;
}
&.selected {
background: #409eff;
border-color: #1e88e5;
}
&:hover {
background: #c6e2ff;
border-color: #409eff;
}
}
}
}
}
::v-deep(.el-form-item) {
margin-bottom: 16px;
}
</style>