| | |
| | | <template> |
| | | <div class="title"></div> |
| | | <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 { ref, reactive } from 'vue' |
| | | import { ElButton, ElSelect, ElOption } from "element-plus"; // 宿´å¼å
¥éè¦çç»ä»¶ |
| | | import * as echarts from 'echarts'; |
| | | |
| | | export default { |
| | | setup() { |
| | | 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> |
| | | .title { |
| | | line-height: 70vh; |
| | | /* åææ ·å¼ä¿æä¸åï¼æ°å¢ä»¥ä¸æ ·å¼ */ |
| | | #vol-main { |
| | | height: 100vh; |
| | | /* ç¡®ä¿å®¹å¨æé«åº¦ */ |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .empty-tip { |
| | | text-align: center; |
| | | font-size: 28px; |
| | | color: orange; |
| | | 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> |