Quellcode durchsuchen

边界划分-树状图

Signed-off-by: hsshuxian <3049816743@qq.com>
hsshuxian vor 6 Monaten
Ursprung
Commit
008211c6a6

+ 3 - 1
ems-ui-cloud/package.json

@@ -49,7 +49,7 @@
     "core-js": "^3.25.3",
     "dayjs": "^1.11.13",
     "diagram-js": "^14.11.3",
-    "echarts": "^5.4.0",
+    "echarts": "^5.6.0",
     "echarts-gl": "^2.0.9",
     "element-resize-detector": "^1.2.4",
     "element-ui": "^2.15.14",
@@ -67,6 +67,8 @@
     "sortablejs": "1.10.2",
     "three": "^0.156.1",
     "uuidjs": "^5.1.0",
+    "vis": "^4.21.0-EOL",
+    "vis-network": "^9.0.1",
     "vue": "2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",

+ 8 - 0
ems-ui-cloud/src/api/basecfg/meterBoundary.js

@@ -41,3 +41,11 @@ export function delByObj(objType, boundaryObj) {
     }
   })
 }
+
+//拓扑图
+export function getBoundaryTreeByArea(areaCode) {
+  return request({
+    url: `/ems/meterBoundary/getBoundaryTreeByArea?areaCode=${areaCode}`,
+    method: 'get'
+  });
+}

+ 257 - 106
ems-ui-cloud/src/views/basecfg/boundaryRel/index.vue

@@ -9,7 +9,20 @@
           </div>
           <div class="head-container">
             <el-tree :data="areaList" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree"
-                     node-key="id" default-expand-all highlight-current   @node-click="handleNodeClick"/>
+                     node-key="id" default-expand-all highlight-current   @node-click="handleNodeClick">
+              <template slot-scope="scope">
+                <div class="custom-tree-node">
+                  <span>{{ scope.node.label }}</span>
+                  <el-button
+                    v-if="scope.node.data.id !== 'all'"
+                    size="mini"
+                    type="text"
+                    icon="el-icon-map-location"
+                    @click="showTopology(scope.node.data)"
+                  >拓扑图</el-button>
+                </div>
+              </template>
+            </el-tree>
           </div>
         </div>
         <div v-if="activeTab === 'organ'">
@@ -60,57 +73,66 @@
 
     <!-- 地理位置  -->
         <div v-if="activeTab === 'area'">
-          <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-            <el-form-item label="区域名称" prop="areaName">
-              <el-input
-                  v-model="queryParams.areaName"
-                  placeholder="请输入区域名称"
-                  clearable
-                  @keyup.enter.native="handleQuery"
-              />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-              <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-            </el-form-item>
-          </el-form>
-          <el-table
-              :key="tableKey"
-              v-if="refreshTable"
-              v-loading="loading"
-              :data="areaOptions"
-              :default-expand-all="isExpandAll"
-              row-key="areaCode"
-              :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
-          >
+            <el-tabs v-model="activeDeviceTab" @tab-click="handleADTabClick">
+              <el-tab-pane label="电表" name="electricMeter"></el-tab-pane>
+              <el-tab-pane label="水表" name="waterMeter"></el-tab-pane>
+            </el-tabs>
 
-            <el-table-column label="区域名称" align="left" prop="areaName" />
-            <el-table-column label="区域简称" align="left" prop="shortName" />
-            <el-table-column label="区域编码" align="left" prop="areaCode" />
-            <el-table-column label="区域状态" align="center" >
-              <template slot-scope="scope">
-                {{ objStatusMapping[scope.row.status] }}
-              </template>
-            </el-table-column>
-            <el-table-column label="区块标签" align="center">
-              <template slot-scope="scope">
-          <span v-for="tag in (scope.row.tagNames && scope.row.tagNames.split(',')) || []" :key="tag" :style="getTagStyle(tag.trim())" class="tag-label">
-            {{ tag.trim() }}
-          </span>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-              <template slot-scope="scope">
-                <el-button
-                    size="mini"
-                    type="text"
-                    icon="el-icon-reading"
-                    @click="handleDevice(scope.row)"
-                    v-hasPermi="['ems:area:edit']"
-                >计量设备</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
+            <!-- 水表和电表的已绑定和未绑定表格 -->
+            <SubTitle title="已绑定列表" />
+            <el-table  v-loading="loading"  :data="Bound" style="width: 100%">
+              <el-table-column label="表计编号" align="left" prop="meterDevice" />
+              <el-table-column label="表计名称" align="left" prop="meterDeviceName" />
+              <el-table-column label="边界类型" align="left" prop="objType">
+                <template slot-scope="scope">
+                  {{ getObjTypeLabel(scope.row.objType) }}
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                  <el-button size="mini" type="text" icon="el-icon-arrow-down" @click="downToDevice(scope.row)">取消绑定</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <SubTitle title="未绑定列表" />
+            <el-table  v-loading="loading"  :data="Unbound" style="width: 100%">
+              <el-table-column label="表计编号" align="left" prop="deviceCode" />
+              <el-table-column label="表计名称" align="left" prop="deviceName" width="200px"/>
+              <el-table-column label="安装位置" align="left" prop="deviceLocation" width="200px"/>
+              <el-table-column label="计量标签" align="center" prop="objTag">
+                <template slot-scope="scope">
+                  {{formatDict(scope.row.objTag,'objTagOptions')}}
+                </template>
+              </el-table-column>
+              <el-table-column label="采集方式" align="center" prop="colMode">
+                <template slot-scope="scope">
+                  <span>{{ getColModeName(scope.row.colMode) }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="采集周期" align="center" prop="colCycle">
+                <template slot-scope="scope">
+                  <span>{{ getColCycleName(scope.row.colCycle) }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                  <!-- 检查是否已绑定 -->
+                  <div v-if="isAlreadyBound(scope.row.deviceCode)">
+                    <span>已绑定</span>
+                  </div>
+                  <div v-else>
+                    <el-button
+                      size="mini"
+                      type="text"
+                      icon="el-icon-arrow-up"
+                      @click="moveToDevice(scope.row)"
+                    >绑定</el-button>
+                  </div>
+                </template>
+              </el-table-column>
+            </el-table>
+            <pagination v-show="total>0" :total="total" :page.sync="MeterQueryParams.pageNum" :limit.sync="MeterQueryParams.pageSize"
+                        @pagination="getMeterData" />
         </div>
     <!-- 组织机构   -->
         <div v-if="activeTab === 'organ'">
@@ -370,17 +392,23 @@
         </div>
       </el-dialog>
     </el-row>
+    <!-- 拓扑图弹框 -->
+    <el-dialog :visible.sync="topologyDialogVisible" title="拓扑图" width="1200px">
+      <div ref="topologyChart" style="width: 100%; height: 600px;"></div>
+    </el-dialog>
+
   </div>
 </template>
 
 <script>
+import * as echarts from 'echarts';
 import Treeselect from '@riophae/vue-treeselect'
 import '@riophae/vue-treeselect/dist/vue-treeselect.css'
 import { areaTreeSelect, listDetailArea } from '@/api/basecfg/area'
 import SubTitle from '@/components/SubTitle/index.vue'
 import { getDevProcess, getEmsTag } from '@/api/commonApi'
 import { delDevice, listDevice } from '@/api/device/meterDevice'
-import { addAllByObj, listByObj } from '@/api/basecfg/meterBoundary'
+import { addAllByObj, listByObj ,getBoundaryTreeByArea } from '@/api/basecfg/meterBoundary'
 import { listDept } from '@/api/system/dept'
 import { getFacsCategorygetByCode, listAllFacs, listFacs } from '@/api/basecfg/emsfacs'
 import { listDevRecursionByArea } from '@/api/device/device'
@@ -393,6 +421,7 @@ export default {
   components: { SubTitle, Treeselect },
   data() {
     return {
+      topologyDialogVisible: false, // 控制拓扑图弹框的显示与隐藏
       activeTab: 'area',
       areaOptions: [],
       organOptions: [],
@@ -583,6 +612,109 @@ export default {
 
   },
   methods: {
+    showTopology(data) {
+      this.topologyDialogVisible = true;
+      this.$nextTick(() => {
+        getBoundaryTreeByArea(data.areaCode).then(response => {
+          const topologyData = response.data;
+          console.log("拓扑图接口获取数据",topologyData);
+          this.initTreeECharts(topologyData);
+        });
+      });
+    },
+    initTreeECharts(topologyData) {
+      const chartDom = this.$refs.topologyChart;
+      const myChart = echarts.init(chartDom);
+
+      // 递归处理拓扑图数据,生成echarts需要的数据格式
+      const processTopologyData = (data) => {
+        const hasBoundDevices = data.bindElecMeterDevs && data.bindElecMeterDevs.length > 0;
+        const nodeColor = hasBoundDevices ? '#99e170' : '#D3D3D3';
+        return {
+          name: data.objName, // 节点名称取objName
+          children: data.children ? data.children.map(child => processTopologyData(child)) : [],
+          value: [data.bindElecMeterDevs].flat().join(', '),
+          itemStyle: { // 设置节点样式
+            borderColor: '#D8D3D1',
+            borderWidth: 1,
+            color: nodeColor
+          }
+        };
+      };
+
+      // 处理根节点数据
+      const processedData = processTopologyData(topologyData);
+
+      // 指定图表的配置项和数据
+      const option = {
+
+        tooltip: {
+          trigger: 'item',
+          triggerOn: 'mousemove',
+          formatter: function (params) {
+            let bindElecMeterDevs = params.data.value;
+            if (!Array.isArray(bindElecMeterDevs)) {
+              bindElecMeterDevs = [bindElecMeterDevs];
+            }
+            const bindElecMeterDevsStr = bindElecMeterDevs.length > 0 ? bindElecMeterDevs.join(', ') : '无';
+            return `绑定电表:${bindElecMeterDevsStr}`;
+          }
+        },
+        series: [
+          {
+             name: '拓扑图',
+            type: 'tree',
+            layout: 'compactBox',
+            orient: 'TB', // 从上到下的布局
+            data: [processedData], // 直接使用传入的数据
+            top: '5%',
+            left: '1%',
+            bottom: '5%',
+            right: '5%',
+            symbol: 'rect', // 设置节点形状为长方形
+            symbolSize: [100, 40],
+            label: {
+              position: 'inside',
+              verticalAlign: 'middle',
+              align: 'center',
+              fontSize: 12,
+
+            },
+
+            leaves: {
+              label: {
+                position: 'inside', //  叶子节点标签位置在内部
+                verticalAlign: 'middle',
+                align: 'center'
+              }
+            },
+            expandAndCollapse: true,
+            animationDuration: 550,
+            animationDurationUpdate: 750,
+            itemStyle: {
+              borderColor: '#555',
+              borderWidth: 1,
+              gapWidth: 10, // 增加节点之间的间隔
+            },
+            edgeLabel: {
+              position: 'middle',
+              fontSize: 10
+            },
+            emphasis: {
+              itemStyle: {
+                borderColor: '#333',
+                borderWidth: 2
+              }
+            },
+
+          }
+        ]
+      };
+      myChart.setOption(option);
+    },
+
+
+
     getAreaFacsTree(areaCode, recursion) {
       areaTreeSelect(areaCode, recursion).then(response => {
         this.facsOptions = [{
@@ -669,23 +801,23 @@ export default {
 
     /**地理位置*/
     handleNodeClick(data) {
+      // 不执行任何操作
       if (data.id === 'all') {
-        this.areaOptions = this.areaList.slice(1).map(item => {
-          const { children, ...rest } = item;
-          return rest;
-        });
-      } else {
-        // 判断是否为叶子节点
-        if (!data.children || data.children.length === 0) {
-          // 叶子节点,展示该节点的数据
-          this.areaOptions = [data];
-        } else {
-          this.areaOptions = data.children.map(child => {
-            const { children, ...rest } = child;
-            return rest;
-          });
+        return;
+      }
+      console.log("地理位置!!!!",data)
+      this.queryParams.areaCode = data.areaCode;
+      if (data && data.ancestors) {
+        const ancestorsArray = data.ancestors.split(',');
+        if (ancestorsArray.length > 1) {
+          // 当 ancestors 长度大于 1 时,取第二个 areaCode 作为父级 areaCode
+          this.MeterQueryParams.areaCode= ancestorsArray[1];
+        } else if (ancestorsArray.length === 1) {
+          // 当 ancestors 长度等于 1 时,取该对象本身的 areaCode 作为父级 areaCode
+          this.MeterQueryParams.areaCode = data.areaCode;
         }
       }
+      this.getMeterData(data);
     },
 
     /**组织机构*/
@@ -726,17 +858,11 @@ export default {
         this.tableKey += 1; // 改变 key 值以强制重新渲染
         // 将“全部”节点添加到区域列表的顶部
         this.areaList.unshift(this.allAreaNode);
-        this.loading = false;
-        this.Node();
+        console.log(" this.areaList", this.areaList)
+        this.handleNodeClick(this.areaList[1]);//常泰北区
+       this.loading = false;
       });
     },
-    Node() {
-      if (this.areaList.length > 0) {
-        this.handleNodeClick(this.areaList[0]);}
-      if (this.areaList[0].children && this.areaList[0].children.length > 1) {
-        this.handleNodeClick(this.areaList[0].children[1]);
-      }
-    },
 
     // 递归提取 tagNames
     extractTagNames(list) {
@@ -986,11 +1112,13 @@ export default {
       } else if (this.activeTab === 'device') {
         objType = 3;
       }
+      console.log("this.MeterQueryParams",this.MeterQueryParams)
       listDevice({ ...this.MeterQueryParams, meterCls}).then(response => {
         this.Unbound = response.rows;
         this.total = response.total;
         this.loading = false;
       });
+      console.log("this.queryParams.areaCode",this.queryParams.areaCode)
       listByObj(objType,meterCls,this.queryParams.areaCode).then(response=>{
         this.Bound = response.data;
       })
@@ -998,43 +1126,59 @@ export default {
 
     /**绑定设备*/
     moveToDevice(row) {
-      let objType;
-      if (this.activeTab === 'area') {
-        objType = 1;
-      } else if (this.activeTab === 'organ') {
-        objType = 4;
-      } else if (this.activeTab === 'facs') {
-        objType = 2;
-      } else if (this.activeTab === 'device') {
-        objType = 3;
-      }
-      const index = this.Unbound.indexOf(row);
-      if (index !== -1) {
-        this.Unbound.splice(index, 1);
-      }
-      const boundRow = {
-        meterDeviceName: row.deviceName,
-        meterDevice: row.deviceCode,
-        boundaryObj: this.queryParams.areaCode,
-        objType: objType
-      };
-      this.Bound.push(boundRow);
+      this.$confirm('是否确定绑定?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        let objType;
+        if (this.activeTab === 'area') {
+          objType = 1;
+        } else if (this.activeTab === 'organ') {
+          objType = 4;
+        } else if (this.activeTab === 'facs') {
+          objType = 2;
+        } else if (this.activeTab === 'device') {
+          objType = 3;
+        }
+        const index = this.Unbound.indexOf(row);
+        if (index !== -1) {
+          this.Unbound.splice(index, 1);
+        }
+        const boundRow = {
+          meterDeviceName: row.deviceName,
+          meterDevice: row.deviceCode,
+          boundaryObj: this.queryParams.areaCode,
+          objType: objType
+        };
+        this.Bound.push(boundRow);
+        this.saveByObj();
+      }).catch(() => {
+      });
     },
     isAlreadyBound(deviceCode) {
       return this.Bound.some(boundDevice => boundDevice.meterDevice === deviceCode);
     },
     /**取消绑定设备*/
     downToDevice(row){
-      const index = this.Bound.indexOf(row);
-      if (index !== -1) {
-        this.Bound.splice(index, 1);
-      }
-      this.Unbound.push(row);
-      delete row.boundaryObj;
-      delete row.meterDevice;
-      delete row.objType;
-    },
+      this.$confirm('是否取消绑定?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const index = this.Bound.indexOf(row);
+        if (index !== -1) {
+          this.Bound.splice(index, 1);
+        }
+        this.Unbound.push(row);
+        delete row.boundaryObj;
+        delete row.meterDevice;
+        delete row.objType;
+        this.saveByObj();
+      }).catch(() => {
 
+      });
+    },
     /**保存绑定的设备*/
     saveByObj() {
       // 构建要保存的数据数组
@@ -1143,7 +1287,6 @@ export default {
     },
     getTagStyle(tagName) {
       // 从tagName找到对应的tagCode
-
       const tagCode = this.emsTagOptions.find(tag => tag.label === tagName)?.value;
       const color = this.tagCodeToColorMap[tagCode] || '#FFFFFF'; // 默认白色
       return {
@@ -1161,3 +1304,11 @@ export default {
   }
 }
 </script>
+<style lang="scss" scoped>
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+</style>