<template>
|
<div class="container" id="vol-main"> <!-- 新增id,匹配JS中的获取逻辑 -->
|
<div class="header">
|
<h2 class="title">货位排图</h2>
|
</div>
|
|
<div class="content-wrapper">
|
<!-- 控制面板区域 -->
|
<div class="control-panel">
|
<div class="form-group">
|
<label>区域:</label>
|
<el-select size="mini" filterable v-model="Area.shelf_code" placeholder="请选择" class="full-width">
|
<el-option v-for="item in slectData" :value="item.shelf_code" :label="item.house_name"
|
:key="item.shelf_code"></el-option> <!-- 修复key值,避免重复 -->
|
</el-select>
|
</div>
|
|
<div class="form-group">
|
<label>排:</label>
|
<el-select size="mini" clearable filterable @change="SCChange" v-model="Area.tunnel" placeholder="请选择"
|
class="full-width">
|
<el-option v-for="item in scList" :value="item" :label="item" :key="item"></el-option>
|
</el-select>
|
</div>
|
|
<el-button type="success" class="refresh-btn" @click="GetViewData">
|
刷新
|
</el-button>
|
<el-button plain class="notify-trigger-btn" @click="open2">
|
警告
|
</el-button>
|
<div class="legend-section">
|
<h4>说明</h4>
|
<div class="legend-grid">
|
<div class="legend-item" v-for="item in infoMsg" :key="item.state"> <!-- 修复key值,用state更唯一 -->
|
<span class="color-box" :style="{ 'background-color': item.bgcolor }"></span>
|
<span class="legend-label">{{ item.msg }} {{ item.quantity }}</span>
|
</div>
|
</div>
|
</div>
|
|
<!-- 饼图容器:修改样式使其居中 -->
|
<div class="echarts-container">
|
<div ref="myChart" class="chart-inner"></div>
|
</div>
|
</div>
|
|
<!-- 货位展示区域 -->
|
<div class="location-view">
|
<!-- 增加无数据提示 -->
|
<div v-if="locationData.length === 0" class="empty-tip">暂无货位数据,请选择区域并点击刷新</div>
|
<div class="layer-container" v-for="layer in locationData" :key="layer.index">
|
<h3 class="layer-title">第{{ layer.index }}层</h3>
|
<div class="row" v-for="row in layer.rows" :key="row.index">
|
<div class="location-cell" :style="{ 'background-color': GetBgColor(col) }" v-for="col in row.cols"
|
:key="col.index" @mouseenter="showTooltip(col, $event)" @mouseleave="hideTooltip">
|
{{ row.index }}-{{ col.index }}-{{ layer.index }}
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- 悬浮提示框 -->
|
<div v-if="showTooltipFlag" class="location-tooltip" :style="{
|
left: tooltipPosition.x + 'px',
|
top: tooltipPosition.y + 'px',
|
}">
|
<div v-if="currentLocation">
|
<p><strong>货位号:</strong>{{ currentLocation.locationCode || '无' }}</p> <!-- 增加默认值 -->
|
<p>
|
<strong>货位排列层:</strong> {{ currentLocation.row || 0 }}排{{
|
currentLocation.index || 0
|
}}列{{ currentLocation.layer || 0 }}层
|
</p>
|
<p><strong>状态:</strong> {{ getStatusText(currentLocation) }}</p>
|
<p>
|
<strong>禁用:</strong>
|
{{ currentLocation.location_lock == 3 ? "是" : "否" }}
|
</p>
|
<p v-if="currentLocation.location_state === 2">
|
<strong>物料编码:</strong>
|
{{ currentLocation.material_code || "无" }}
|
</p>
|
<p v-if="currentLocation.location_state === 2">
|
<strong>数量:</strong> {{ currentLocation.quantity || "无" }}
|
</p>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import { ElButton, ElSelect, ElOption } from "element-plus"; // 完整引入需要的组件
|
import * as echarts from 'echarts';
|
|
export default {
|
components: { ElButton, ElSelect, ElOption }, // 注册所有用到的组件
|
data() {
|
return {
|
slectData: [],
|
scList: [],
|
Area: { house_name: "", tunnel: "", shelf_code: "" },
|
mian_height: "",
|
infoMsg: [
|
{ bgcolor: "lightgreen", msg: "空货位", state: 0, quantity: 0 },
|
{ bgcolor: "orange", msg: "有货", state: 2, quantity: 0 },
|
{ bgcolor: "#2BB3D5", msg: "锁定", state: "InAssigned", quantity: 0 },
|
{ bgcolor: "#ccc", msg: "禁用", state: 3, quantity: 0 },
|
{ bgcolor: "#b7ba6b", msg: "其它", state: "else", quantity: 0 },
|
],
|
locationData: [],
|
showTooltipFlag: false,
|
currentLocation: null,
|
tooltipPosition: { x: 0, y: 0 },
|
chart: null,
|
notifyOffset: 0, // 新增:通知偏移量计数器
|
notifyHeight: 80, // 通知组件高度(可根据实际调整)
|
notifyGap: 15, // 通知之间的间距
|
};
|
},
|
computed: {
|
GetBgColor() {
|
return (col) => {
|
// 简化逻辑,提高可读性
|
if (col.location_lock == 3) {
|
return this.infoMsg.find(item => item.state === 3).bgcolor;
|
}
|
if (col.location_state === 2) {
|
return this.infoMsg.find(item => item.state === 2).bgcolor;
|
}
|
if (col.location_state > 0 && col.location_state < 100) {
|
return this.infoMsg.find(item => item.state === "InAssigned").bgcolor;
|
}
|
if (col.location_state === 0) {
|
return this.infoMsg.find(item => item.state === 0).bgcolor;
|
}
|
return this.infoMsg.find(item => item.state === "else").bgcolor;
|
};
|
},
|
// 新增:将infoMsg转换为饼图需要的数据格式
|
chartData() {
|
return this.infoMsg.map(item => ({
|
name: item.msg,
|
value: item.quantity,
|
itemStyle: {
|
color: item.bgcolor // 让饼图颜色和图例保持一致
|
}
|
})).filter(item => item.value > 0); // 过滤掉数量为0的项
|
}
|
},
|
watch: {
|
"Area.shelf_code"(newValue, oldValue) {
|
if (!newValue) return; // 空值时不执行
|
this.scList = [];
|
const target = this.slectData.find(e => e.shelf_code === newValue);
|
if (target) {
|
this.Area.tunnel = target.tunnel?.[0] || ""; // 可选链避免报错
|
this.scList = target.tunnel || [];
|
this.GetViewData(); // 数据加载完成后再调用
|
}
|
},
|
// 监听chartData变化,更新饼图
|
chartData: {
|
deep: true,
|
handler() {
|
this.updateChart();
|
}
|
},
|
},
|
methods: {
|
async GetViewData() {
|
this.warinngNotification();
|
// 增加参数校验
|
if (!this.Area.shelf_code || !this.Area.tunnel) {
|
this.$message?.warning("请先选择区域和排!"); // Element Plus 提示
|
return;
|
}
|
|
try {
|
const res = await this.http.post(
|
"/api/LocationInfoRow/GetLocationStatu",
|
this.Area,
|
"查询中"
|
);
|
this.locationData = res || [];
|
console.log("后端返回:", this.locationData);
|
|
// 重置统计数量
|
this.infoMsg.forEach(item => item.quantity = 0);
|
|
// 统计各状态数量
|
this.locationData.forEach(layer => {
|
(layer.rows || []).forEach(row => {
|
(row.cols || []).forEach(col => {
|
if (col.location_lock == 3) {
|
const item = this.infoMsg.find(el => el.state === 3);
|
if (item) item.quantity++;
|
} else if (col.location_state === 2) {
|
const item = this.infoMsg.find(el => el.state === 2);
|
if (item) item.quantity++;
|
} else if (col.location_state > 0 && col.location_state < 100) {
|
const item = this.infoMsg.find(el => el.state === "InAssigned");
|
if (item) item.quantity++;
|
} else if (col.location_state === 0) {
|
const item = this.infoMsg.find(el => el.state === 0);
|
if (item) item.quantity++;
|
} else {
|
const item = this.infoMsg.find(el => el.state === "else");
|
if (item) item.quantity++;
|
}
|
});
|
});
|
});
|
} catch (error) {
|
console.error("获取货位数据失败:", error);
|
this.$message?.error("获取数据失败,请重试!");
|
this.locationData = [];
|
}
|
},
|
SCChange() {
|
this.GetViewData();
|
},
|
showTooltip(location, event) {
|
this.currentLocation = location;
|
this.showTooltipFlag = true;
|
this.tooltipPosition = {
|
x: event.clientX + 10,
|
y: event.clientY + 10,
|
};
|
},
|
hideTooltip() {
|
this.showTooltipFlag = false;
|
this.currentLocation = null;
|
},
|
getStatusText(location) {
|
const stateMap = {
|
0: "空货位",
|
1: "锁定",
|
2: "有货",
|
10: "有货锁定",
|
20: "空闲锁定",
|
99: "大托盘锁定"
|
};
|
return stateMap[location.location_state] || "其他";
|
},
|
// 初始化饼图
|
initChart() {
|
// 正确获取myChart元素
|
const chartDom = this.$refs.myChart;
|
if (!chartDom) return;
|
this.chart = echarts.init(chartDom);
|
// 设置饼图基础配置
|
const option = {
|
// 优化tooltip配置,防止被遮挡
|
tooltip: {
|
trigger: 'item',
|
formatter: '{a} <br/>{b}: {c} ({d}%)', // 显示名称、数量、百分比
|
// 关键配置:防止tooltip被遮挡
|
zIndex: 99999, // 设置极高的层级,确保在最上层
|
confine: true, // 将tooltip限制在图表容器内
|
position: function (point, params, dom, rect, size) {
|
// 自定义tooltip位置,避免超出容器
|
const x = point[0];
|
const y = point[1];
|
// 计算tooltip的显示位置,优先显示在右侧,超出则显示在左侧
|
const ret = {
|
left: x + size.contentSize[0] > size.viewSize[0]
|
? (x - size.contentSize[0] - 10) + 'px'
|
: (x + 10) + 'px',
|
top: y + size.contentSize[1] > size.viewSize[1]
|
? (y - size.contentSize[1] - 10) + 'px'
|
: (y + 10) + 'px'
|
};
|
return ret;
|
},
|
// 增加tooltip样式,增强视觉效果
|
textStyle: {
|
fontSize: 12
|
},
|
backgroundColor: 'rgba(255,255,255,0.95)',
|
borderColor: '#ddd',
|
borderWidth: 1,
|
padding: 8,
|
shadowBlur: 5,
|
shadowColor: 'rgba(0,0,0,0.1)'
|
},
|
series: [
|
{
|
name: '',
|
type: 'pie',
|
radius: '64%',
|
label: {
|
show: true,
|
position: 'outside', // 标签显示在外部
|
formatter: '{b}:{d}%' // 显示名称和数量
|
},
|
data: this.chartData,
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
}
|
}
|
}
|
]
|
};
|
this.chart.setOption(option);
|
// 监听窗口大小变化,自适应图表
|
window.addEventListener('resize', () => this.chart?.resize());
|
},
|
// 更新饼图数据
|
updateChart() {
|
if (!this.chart) return;
|
this.chart.setOption({
|
series: [
|
{
|
data: this.chartData
|
}
|
]
|
});
|
},
|
warinngNotification() {
|
this.http.post("/api/LocationInfo/LocationWarning", {}, true).then((result) => {
|
if (!result.status) {
|
this.$Message.$error(x.message);
|
} else {
|
console.log(result.data)
|
result.data.forEach(item => {
|
this.open2(item);
|
})
|
console.log(this.messageList)
|
}
|
});
|
},
|
open2(locationName) {
|
// 1. 计算当前通知的偏移量
|
const currentOffset = this.notifyOffset;
|
|
// 2. 显示通知
|
const notifyInstance = this.$notify({
|
title: '警告 [' + locationName + ']',
|
message: "仓库占有率已达到80%或80%以上",
|
type: 'warning',
|
duration: 5000,
|
offset: currentOffset, // 手动指定偏移量
|
// 3. 通知关闭后,重置偏移量(避免后续通知位置错乱)
|
onClose: () => {
|
this.notifyOffset -= (this.notifyHeight + this.notifyGap);
|
// 防止偏移量为负数
|
if (this.notifyOffset < 0) this.notifyOffset = 0;
|
}
|
});
|
|
// 4. 更新下一个通知的偏移量
|
this.notifyOffset += (this.notifyHeight + this.notifyGap);
|
},
|
},
|
mounted() {
|
// 确保DOM加载完成后获取元素
|
this.$nextTick(() => {
|
const mainHeight = document.getElementById("vol-main");
|
if (mainHeight) {
|
this.mian_height = mainHeight.offsetHeight - 40 + "px";
|
}
|
// this.warinngNotification();
|
// 初始化下拉数据
|
this.http.get("/api/LocationInfoRow/GetArea", {}, "查询中")
|
.then((x) => {
|
this.slectData = x || [];
|
if (this.slectData.length > 0) {
|
this.Area.shelf_code = this.slectData[0].shelf_code;
|
this.scList = this.slectData[0].tunnel || [];
|
this.Area.tunnel = this.scList[0] || "";
|
// 初始化图表
|
this.initChart();
|
}
|
})
|
.catch(error => {
|
console.error("获取区域数据失败:", error);
|
this.$message?.error("加载区域数据失败!");
|
});
|
});
|
},
|
beforeUnmount() {
|
// 销毁图表,避免内存泄漏
|
if (this.chart) {
|
this.chart.dispose();
|
this.chart = null;
|
}
|
},
|
};
|
</script>
|
|
<style scoped>
|
/* 原有样式保持不变,新增以下样式 */
|
#vol-main {
|
height: 100vh;
|
/* 确保容器有高度 */
|
box-sizing: border-box;
|
}
|
|
.empty-tip {
|
text-align: center;
|
padding: 50px 0;
|
color: #999;
|
font-size: 14px;
|
}
|
|
/* 关键修改:饼图容器样式,实现在控制面板区域居中 */
|
.echarts-container {
|
margin-top: 20px;
|
width: 100%;
|
flex: 1;
|
/* 让饼图容器占据控制面板剩余空间 */
|
display: flex;
|
/* Flex布局实现水平+垂直居中 */
|
justify-content: center;
|
/* 水平居中 */
|
align-items: center;
|
/* 垂直居中 */
|
position: relative;
|
z-index: 1;
|
}
|
|
/* 饼图内部容器 */
|
.chart-inner {
|
width: 400px;
|
height: 400px;
|
/* 饼图实际大小 */
|
}
|
|
|
/* 新增:给ECharts实例容器增加样式,确保tooltip不被遮挡 */
|
:deep(.echarts-tooltip) {
|
z-index: 99999 !important;
|
/* 强制最高层级 */
|
pointer-events: none;
|
/* 防止tooltip遮挡鼠标事件 */
|
}
|
|
/* 原有样式 */
|
.container {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
padding: 10px;
|
}
|
|
.header {
|
text-align: center;
|
margin-bottom: 20px;
|
}
|
|
.title {
|
font-size: 20px;
|
font-weight: bold;
|
margin: 0;
|
}
|
|
.content-wrapper {
|
display: flex;
|
flex: 1;
|
min-height: 0;
|
}
|
|
.control-panel {
|
width: 320px;
|
padding: 15px;
|
background-color: #f5f7fa;
|
border-radius: 4px;
|
margin-right: 15px;
|
display: flex;
|
flex-direction: column;
|
/* 确保控制面板内的元素层级正确 */
|
position: relative;
|
z-index: 10;
|
}
|
|
.form-group {
|
margin-bottom: 15px;
|
}
|
|
.full-width {
|
width: 100%;
|
}
|
|
.refresh-btn {
|
margin-top: 10px;
|
width: 100%;
|
}
|
|
.legend-section {
|
margin-top: 30px;
|
}
|
|
.legend-section h4 {
|
margin-bottom: 10px;
|
}
|
|
.legend-grid {
|
display: grid;
|
grid-template-columns: 1fr;
|
gap: 8px;
|
}
|
|
.legend-item {
|
display: flex;
|
align-items: center;
|
}
|
|
.color-box {
|
display: inline-block;
|
width: 20px;
|
height: 20px;
|
margin-right: 8px;
|
border-radius: 3px;
|
}
|
|
.legend-label {
|
font-size: 13px;
|
}
|
|
.location-view {
|
flex: 1;
|
overflow: auto;
|
padding: 10px;
|
background-color: white;
|
border-radius: 4px;
|
/* 设置合理的层级,低于tooltip */
|
z-index: 1;
|
}
|
|
.layer-container {
|
margin-bottom: 25px;
|
}
|
|
.layer-title {
|
margin: 0 0 10px 0;
|
font-size: 16px;
|
color: #333;
|
}
|
|
.row {
|
display: flex;
|
flex-wrap: wrap;
|
margin-bottom: 8px;
|
cursor: pointer;
|
}
|
|
.location-cell {
|
width: 66px;
|
height: 38px;
|
margin: 3px;
|
text-align: center;
|
font-size: 14px;
|
border-radius: 3px;
|
line-height: 38px;
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
}
|
|
.location-tooltip {
|
position: fixed;
|
z-index: 9999;
|
background-color: white;
|
border: 1px solid #ddd;
|
border-radius: 4px;
|
padding: 10px;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
pointer-events: none;
|
max-width: 250px;
|
}
|
|
.location-tooltip p {
|
margin: 5px 0;
|
font-size: 13px;
|
line-height: 1.4;
|
}
|
|
.location-tooltip strong {
|
display: inline-block;
|
width: 70px;
|
color: #666;
|
}
|
|
.notify-trigger-btn {
|
display: none !important;
|
}
|
</style>
|