| | |
| | | <template> |
| | | <div class="container"> |
| | | <div class="container" id="vol-main"> <!-- 新增id,匹配JS中的获取逻辑 --> |
| | | <div class="header"> |
| | | <h2 class="title">货位排图</h2> |
| | | </div> |
| | |
| | | <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.house_name" |
| | | ></el-option> |
| | | <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 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.bgcolor" |
| | | > |
| | | <span |
| | | class="color-box" |
| | | :style="{ 'background-color': item.bgcolor }" |
| | | ></span> |
| | | <span class="legend-label">{{ item.msg }}</span> |
| | | <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 |
| | | class="layer-container" |
| | | v-for="layer in locationData" |
| | | :key="layer.index" |
| | | > |
| | | <!-- 增加无数据提示 --> |
| | | <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" |
| | | > |
| | | <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="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.locationCode || '无' }}</p> <!-- 增加默认值 --> |
| | | <p> |
| | | <strong>货位排列层:</strong> {{ currentLocation.row }}排{{ |
| | | currentLocation.index |
| | | }}列{{ currentLocation.layer }}层 |
| | | <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 || "无" }} |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { ElButton } from "element-plus"; |
| | | import { ElButton, ElSelect, ElOption } from "element-plus"; // 完整引入需要的组件 |
| | | import * as echarts from 'echarts'; |
| | | |
| | | export default { |
| | | components: { ElButton, ElSelect, ElOption }, // 注册所有用到的组件 |
| | | data() { |
| | | return { |
| | | slectData: [], |
| | |
| | | Area: { house_name: "", tunnel: "", shelf_code: "" }, |
| | | mian_height: "", |
| | | infoMsg: [ |
| | | { bgcolor: "lightgreen", msg: "空货位", state: 0 }, |
| | | { bgcolor: "orange", msg: "有货", state: 2 }, // 关键改:state从100→2 |
| | | { bgcolor: "#2BB3D5", msg: "锁定", state: "InAssigned" }, |
| | | { bgcolor: "#ccc", msg: "禁用", state: 3 }, |
| | | { bgcolor: "#b7ba6b", msg: "其它", state: "else" }, |
| | | { 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) => { |
| | | var bgColor = "#b7ba6b"; |
| | | //优先显示禁用状态 |
| | | // 简化逻辑,提高可读性 |
| | | if (col.location_lock == 3) { |
| | | this.infoMsg.forEach((el) => { |
| | | if (el.state == col.location_lock) { |
| | | bgColor = el.bgcolor; |
| | | } |
| | | }); |
| | | } else { |
| | | this.infoMsg.forEach((el) => { |
| | | // 关键改:匹配有货状态(2),而非100 |
| | | if (col.location_state === 2) { |
| | | bgColor = this.infoMsg.find(item => item.state === 2).bgcolor; |
| | | } |
| | | // 锁定状态(1/10/20/99)逻辑保持不变 |
| | | else if (col.location_state > 0 && col.location_state < 100) { |
| | | if (el.state == "InAssigned") { |
| | | bgColor = el.bgcolor; |
| | | } |
| | | } |
| | | // 空货位(0)逻辑保持不变 |
| | | else if (el.state == col.location_state) { |
| | | bgColor = el.bgcolor; |
| | | } |
| | | }); |
| | | return this.infoMsg.find(item => item.state === 3).bgcolor; |
| | | } |
| | | return 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 = []; |
| | | this.slectData.forEach((e) => { |
| | | if (e.shelf_code == newValue) { |
| | | this.Area.tunnel = e.tunnel[0]; |
| | | this.scList = e.tunnel; |
| | | } |
| | | }); |
| | | this.GetViewData(); |
| | | 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: { |
| | | GetViewData() { |
| | | var _this = this; |
| | | this.http |
| | | .post("/api/LocationInfoRow/GetLocationStatu", _this.Area, "查询中") |
| | | .then((x) => { |
| | | _this.locationData = x; |
| | | console.log("后端返回:", x); |
| | | 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) { |
| | | // 关键改:明确匹配有货状态(2) |
| | | if (location.location_state === 2) return "有货"; |
| | | if (location.location_state === 0) return "空货位"; |
| | | if (location.location_state === 1) return "锁定"; |
| | | if (location.location_state === 10) return "有货锁定"; |
| | | if (location.location_state === 20) return "空闲锁定"; |
| | | if (location.location_state === 99) return "大托盘锁定"; |
| | | return "其他"; |
| | | 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() { |
| | | var mainHeight = document.getElementById("vol-main"); |
| | | this.mian_height = mainHeight.offsetHeight - 40 + "px"; |
| | | var _this = this; |
| | | //加载下拉选项 |
| | | this.http.get("/api/LocationInfoRow/GetArea", {}, "查询中").then((x) => { |
| | | _this.slectData = x; |
| | | //加载第一个区域,第一排 |
| | | _this.Area.shelf_code = _this.slectData[0].shelf_code; |
| | | _this.scList = _this.slectData[0].tunnel; |
| | | // 确保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("加载区域数据失败!"); |
| | | }); |
| | | }); |
| | | }, |
| | | components: { ElButton }, |
| | | 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; |
| | |
| | | } |
| | | |
| | | .control-panel { |
| | | width: 220px; |
| | | 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 { |
| | |
| | | padding: 10px; |
| | | background-color: white; |
| | | border-radius: 4px; |
| | | /* 设置合理的层级,低于tooltip */ |
| | | z-index: 1; |
| | | } |
| | | |
| | | .layer-container { |
| | |
| | | width: 70px; |
| | | color: #666; |
| | | } |
| | | |
| | | .notify-trigger-btn { |
| | | display: none !important; |
| | | } |
| | | </style> |