feat(ems): 新增 EMS 概览页面及 SVG 交互功能
- 添加 EMS 概览页面基础结构与样式 - 实现 SVG 区域绘制与点击交互逻辑 - 支持三级视图切换控制点位显示- 添加区域与点位的缩放与重置功能- 实现点位详情弹窗展示设备数据 - 添加点位状态颜色标识(正常/告警/维护)- 预留点位数据查询接口调用方法 - 样式优化与响应式布局调整
This commit is contained in:
237
klp-ui/src/views/ems/overview/index.vue
Normal file
237
klp-ui/src/views/ems/overview/index.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="app-container ems-overview">
|
||||
<div class="toolbar">
|
||||
<el-button-group>
|
||||
<el-button size="mini" :type="viewLevel===1?'primary':'default'" @click="setView(1)">1</el-button>
|
||||
<el-button size="mini" :type="viewLevel===2?'primary':'default'" @click="setView(2)">2</el-button>
|
||||
<el-button size="mini" :type="viewLevel===3?'primary':'default'" @click="setView(3)">3</el-button>
|
||||
</el-button-group>
|
||||
<div class="toolbar-right">
|
||||
<el-button size="mini" icon="el-icon-zoom-in" @click="zoom(1.1)"/>
|
||||
<el-button size="mini" icon="el-icon-zoom-out" @click="zoom(0.9)"/>
|
||||
<el-button size="mini" icon="el-icon-refresh" @click="resetView">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas-wrapper">
|
||||
<svg :viewBox="viewBox" class="overview-svg">
|
||||
<!-- 区域多边形 -->
|
||||
<g v-for="area in areas" :key="area.id" @click="selectArea(area)" style="cursor:pointer;">
|
||||
<polygon
|
||||
:points="area.points.map(p => p.x+','+p.y).join(' ')"
|
||||
:fill="area.id===selectedAreaId ? area.fillActive : area.fill"
|
||||
:stroke="area.stroke"
|
||||
:stroke-width="2"
|
||||
:opacity="viewLevel===3 ? 0.8 : 1"
|
||||
/>
|
||||
<!-- 区域标题 -->
|
||||
<text :x="area.labelPos.x" :y="area.labelPos.y" class="area-label">{{ area.name }}</text>
|
||||
|
||||
<!-- 点位格子,仅在选中或特定视图显示 -->
|
||||
<g v-if="viewLevel!==1 || area.id===selectedAreaId">
|
||||
<g v-for="pt in area.pointsInfo" :key="pt.id" @click.stop="openPoint(pt, area)" style="cursor:pointer;">
|
||||
<rect
|
||||
:x="pt.x-pt.w/2"
|
||||
:y="pt.y-pt.h/2"
|
||||
:width="pt.w"
|
||||
:height="pt.h"
|
||||
:rx="4"
|
||||
:ry="4"
|
||||
:fill="ptFill(pt)"
|
||||
stroke="#444"
|
||||
:stroke-width="pt.id===selectedPoint&&id ? 2 : 1"
|
||||
:opacity="viewLevel===2 ? 0.9 : 0.75"
|
||||
/>
|
||||
<text :x="pt.x" :y="pt.y-6" class="pt-title">{{ pt.name }}</text>
|
||||
<text :x="pt.x" :y="pt.y+10" class="pt-sub">
|
||||
{{ (pt.deviceData&&metric1&&label || '温度') + ': ' + (pt.deviceData&&metric1&&value || '--') }}
|
||||
</text>
|
||||
<text :x="pt.x" :y="pt.y+24" class="pt-sub">
|
||||
{{ (pt.deviceData&&metric2&&label || '状态') + ': ' + (pt.deviceData&&metric2&&value || '--') }}
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- 点位详情 -->
|
||||
<el-dialog
|
||||
title="点位详情"
|
||||
:visible.sync="pointDialogOpen"
|
||||
width="420px"
|
||||
append-to-body
|
||||
>
|
||||
<div v-if="selectedPoint">
|
||||
<p>点位名称:{{ selectedPoint.name }}</p>
|
||||
<p>所属区域:{{ selectedArea&&name }}</p>
|
||||
<p>坐标:({{ selectedPoint.x }}, {{ selectedPoint.y }})</p>
|
||||
<el-divider></el-divider>
|
||||
<p>设备数据:</p>
|
||||
<ul class="point-ul">
|
||||
<li>{{ selectedPoint.deviceData&&metric1&&label || '温度' }}:{{ selectedPoint.deviceData&&metric1&&value || '--' }}</li>
|
||||
<li>{{ selectedPoint.deviceData&&metric2&&label || '状态' }}:{{ selectedPoint.deviceData&&metric2&&value || '--' }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" type="primary" @click="pointDialogOpen=false">关 闭</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmsOverview',
|
||||
data() {
|
||||
return {
|
||||
viewLevel: 1, // 1/2/3 切换视图
|
||||
scale: 1,
|
||||
viewBox: '0 0 1200 700',
|
||||
selectedAreaId: null,
|
||||
selectedPoint: null,
|
||||
selectedArea: null,
|
||||
pointDialogOpen: false,
|
||||
// 示例数据:可替换为接口返回
|
||||
areas: [
|
||||
{
|
||||
id: 'A',
|
||||
name: '冷藏区',
|
||||
points: [{x:60,y:80},{x:1100,y:80},{x:1100,y:620},{x:60,y:620}], // 矩形(可改为不规则)
|
||||
labelPos: {x:100, y:120},
|
||||
fill: '#4c6ef5',
|
||||
fillActive: '#3b5bdb',
|
||||
stroke: '#2b2d42',
|
||||
pointsInfo: [
|
||||
{ id:'A1', name:'点位-01', x:250, y:220, w:110, h:70,
|
||||
deviceData: { metric1:{label:'温度', value:'4.2℃'}, metric2:{label:'状态', value:'正常'} } },
|
||||
{ id:'A2', name:'点位-02', x:520, y:260, w:110, h:70,
|
||||
deviceData: { metric1:{label:'温度', value:'5.1℃'}, metric2:{label:'状态', value:'正常'} } },
|
||||
{ id:'A3', name:'点位-03', x:820, y:340, w:110, h:70,
|
||||
deviceData: { metric1:{label:'温度', value:'7.8℃'}, metric2:{label:'状态', value:'告警'} } },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
name: '冷冻区',
|
||||
points: [{x:640,y:80},{x:1140,y:80},{x:1140,y:620},{x:640,y:620}], // 与 A 有交叠边以示多区
|
||||
labelPos: {x:680, y:120},
|
||||
fill: '#4ea8de',
|
||||
fillActive: '#3a86ff',
|
||||
stroke: '#1b263b',
|
||||
pointsInfo: [
|
||||
{ id:'B1', name:'点位-11', x:780, y:220, w:110, h:70,
|
||||
deviceData: { metric1:{label:'温度', value:'-12.3℃'}, metric2:{label:'状态', value:'正常'} } },
|
||||
{ id:'B2', name:'点位-12', x:980, y:460, w:110, h:70,
|
||||
deviceData: { metric1:{label:'温度', value:'-10.0℃'}, metric2:{label:'状态', value:'维护'} } },
|
||||
]
|
||||
},
|
||||
// 不规则多边形区域示例
|
||||
{
|
||||
id: 'C',
|
||||
name: '分拣区',
|
||||
points: [{x:1040,y:90},{x:1170,y:180},{x:1170,y:560},{x:1040,y:650},{x:980,y:650},{x:980,y:90}],
|
||||
labelPos: {x:1000, y:220},
|
||||
fill: '#95d5b2',
|
||||
fillActive: '#74c69d',
|
||||
stroke: '#2d6a4f',
|
||||
pointsInfo: [
|
||||
{ id:'C1', name:'点位-21', x:1040, y:240, w:120, h:76,
|
||||
deviceData: { metric1:{label:'能量', value:'1000g'}, metric2:{label:'状态', value:'良好'} } },
|
||||
{ id:'C2', name:'点位-22', x:1040, y:440, w:120, h:76,
|
||||
deviceData: { metric1:{label:'显卡', value:'4001'}, metric2:{label:'状态', value:'正常'} } },
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setView(n) {
|
||||
this.viewLevel = n;
|
||||
// 可在不同视图级别下调整呈现元素
|
||||
// 例如:viewLevel===1 仅显示区域;2 显示点位;3 显示热力/状态覆盖
|
||||
},
|
||||
zoom(factor) {
|
||||
this.scale = Math.max(0.5, Math.min(2.5, this.scale * factor));
|
||||
// 通过调整 viewBox 或 transform 控制缩放,这里直接改 viewBox 以简化
|
||||
const base = { w:1200, h:700 };
|
||||
const w = base.w / this.scale;
|
||||
const h = base.h / this.scale;
|
||||
this.viewBox = `0 0 ${w} ${h}`;
|
||||
},
|
||||
resetView() {
|
||||
this.scale = 1;
|
||||
this.viewBox = '0 0 1200 700';
|
||||
this.selectedAreaId = null;
|
||||
this.selectedPoint = null;
|
||||
this.selectedArea = null;
|
||||
},
|
||||
selectArea(area) {
|
||||
this.selectedAreaId = area.id;
|
||||
this.selectedArea = area;
|
||||
},
|
||||
openPoint(pt, area) {
|
||||
this.selectedPoint = pt;
|
||||
this.selectedArea = area;
|
||||
this.pointDialogOpen = true;
|
||||
},
|
||||
ptFill(pt) {
|
||||
const val = (pt.deviceData&&metric2&&value || '').toString();
|
||||
if (val.includes('告警') || val.includes('异常')) return '#ff6b6b';
|
||||
if (val.includes('维护')) return '#ffd166';
|
||||
return '#8ecae6';
|
||||
},
|
||||
// 预留点位查询(据位查询)接口
|
||||
queryPoints(params) {
|
||||
// TODO: 调用后端接口,更新 areas[].pointsInfo
|
||||
// this.$api.xxx(params).then(res => { ... })
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ems-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
}
|
||||
.canvas-wrapper {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
height: 720px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.overview-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.area-label {
|
||||
fill: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,0.2);
|
||||
}
|
||||
.pt-title, .pt-sub {
|
||||
fill: #fff;
|
||||
font-size: 12px;
|
||||
text-anchor: middle;
|
||||
}
|
||||
.point-ul {
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user