Bladeren bron

+ 模型窗口组件动态生成并添加到模型上
+ 模型窗口数据定时更新

chen.cheng 1 maand geleden
bovenliggende
commit
f2d19f39d6

+ 52 - 0
ems-ui-cloud/src/utils/ComponentHandle.js

@@ -0,0 +1,52 @@
+import store from '@/store';
+import Vue from 'vue';
+
+/**
+ * 组件处理对象,用于创建和管理Vue组件。
+ */
+const ComponentHandle = {
+  /**
+   * 创建一个组件实例。
+   *
+   * @param {Object} options 组件配置对象。
+   * @param {Object} options.component 要创建的组件对象。
+   * @param {Object} options.props 组件的props配置。
+   * @param {Function} options.onclose 组件关闭时的回调函数,默认为空函数。
+   * @returns {Object} 返回创建的组件实例。
+   */
+  createComponent({
+    component,
+    props,
+    onclose = () => {
+      console.log('ComponentHandle onclose');
+    },
+  }) {
+    let comp;
+    // 自定义弹出窗口组件
+    //自定义弹框
+    let Content = Vue.extend({
+      // 自定义模板
+      //自定义模板继承
+      template: `
+        <base-info class='window-content' :propData='nameExtend'
+                   :close='close'></base-info>`,
+      name: 'child',
+      components: {
+        'base-info': component, // 引用传入的组件作为子组件
+      },
+      data() {
+        return {
+          nameExtend: props, // 传递props数据给子组件
+        };
+      },
+      methods: {
+        close(params) {
+          onclose && onclose(params); // 调用关闭回调函数
+        },
+      },
+    });
+    comp = new Content({ store }).$mount();
+    return comp.$el;
+  },
+};
+export default ComponentHandle;

+ 66 - 35
ems-ui-cloud/src/views/largeScreen/center.vue

@@ -11,6 +11,10 @@
 import CusTabs from './components/CusTabs.vue';
 import {mapMutations} from 'vuex';
 import renderModel from './three/renderModel'
+import ComponentHandle from "@/utils/ComponentHandle";
+import pvReal from "@/views/largeScreen/dialog/pv-real.vue";
+import storageReal from "@/views/largeScreen/dialog/storage-real.vue";
+import usageReal from "@/views/largeScreen/dialog/usage-real.vue";
 
 const areaEnums = {
   all: {name: '全部', value: ''},
@@ -70,6 +74,67 @@ export default {
       setTimeout(() => {
         this.modelApi = new renderModel('#model')
         this.modelApi.init()
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: pvReal,
+          props:{
+            areaCode: "321283124S3001"
+          }
+        }), "北区光伏", {
+          x: 0,
+          y: 20,
+          z: 20
+        })
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: storageReal,
+          props:{
+            areaCode: "321283124S3001"
+          }
+        }), "北区储能", {
+          x: 120,
+          y: 120,
+          z: 20
+        })
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: usageReal,
+          props:{
+            areaCode: "321283124S3001"
+          }
+        }), "北区负荷", {
+          x: 0,
+          y: 120,
+          z: 20
+        })
+
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: pvReal,
+          props:{
+            areaCode: "321283124S3002"
+          }
+        }), "南区光伏", {
+          x: -40,
+          y: -80,
+          z: 20
+        })
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: storageReal,
+          props:{
+            areaCode: "321283124S3002"
+          }
+        }), "南区储能", {
+          x: -120,
+          y: -120,
+          z: 20
+        })
+        this.modelApi.addDialog(ComponentHandle.createComponent({
+          component: usageReal,
+          props:{
+            areaCode: "321283124S3002"
+          }
+        }), "南区负荷", {
+          x: 0,
+          y: -140,
+          z: 20
+        })
       }, 100)
 
     }
@@ -83,6 +148,7 @@ export default {
   left: 425px;
   z-index: 50;
 }
+
 .center {
   // background: url("~@/assets/images/center.jpg") no-repeat;
   // background-size:cover ;
@@ -94,46 +160,11 @@ export default {
   z-index: 1;
 
 
-
   ::v-deep .tag3d {
     // color: #fff;
     font-weight: 500;
     font-size: 20px;
   }
 
-  ::v-deep .load-container {
-    background-color: rgba(12, 74, 110, 0.5);
-    border: 2px solid rgba(165, 243, 252, 0.8);
-    padding: 10px;
-
-    .title {
-      font-weight: bold;
-      margin-left: 4px;
-      border-left: 4px solid #2dd4bf;
-      padding-left: 2px;
-      margin-top: 3px;
-      margin-bottom: 3px;
-      font-size: 20px;
-    }
-
-    .data-grid {
-      display: grid;
-      grid-template-columns: repeat(2, 1fr);
-      gap: 2px;
-      padding: 0 3px;
-
-      > div {
-        background-color: #0F4565;
-        white-space: nowrap;
-        margin-right: 4px;
-        padding: 5px;
-        margin-top: 4px;
-
-        .value {
-          color: #06E3F9; // 根据需要调整颜色
-        }
-      }
-    }
-  }
 }
 </style>

+ 39 - 0
ems-ui-cloud/src/views/largeScreen/dialog/base.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="load-container">
+    <div class="title">
+      <slot name="title"></slot>
+    </div>
+    <div class="data-grid">
+      <slot name="data"></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "threejs-base-dialog",
+  props: {
+
+  },
+  watch: {},
+  data() {
+    return {};
+  },
+  // 组件卸载前清空图层信息
+  beforeDestroy() {
+  },
+  created() {
+
+  },
+  mounted() {
+    this.init();
+  },
+  methods: {
+    init() {
+
+    },
+  },
+};
+</script>
+<style lang="scss" src="./index.scss">
+</style>

+ 34 - 0
ems-ui-cloud/src/views/largeScreen/dialog/index.scss

@@ -0,0 +1,34 @@
+.load-container {
+  background-color: rgba(12, 74, 110, 0.5);
+  border: 2px solid rgba(165, 243, 252, 0.8);
+  padding: 10px;
+
+  .title {
+    font-weight: bold;
+    margin-left: 4px;
+    border-left: 4px solid #2dd4bf;
+    padding-left: 2px;
+    margin-top: 3px;
+    margin-bottom: 3px;
+    font-size: 20px;
+  }
+
+  .data-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 2px;
+    padding: 0 3px;
+
+    > div {
+      background-color: #0F4565;
+      white-space: nowrap;
+      margin-right: 4px;
+      padding: 5px;
+      margin-top: 4px;
+
+      .value {
+        color: #06E3F9; // 根据需要调整颜色
+      }
+    }
+  }
+}

+ 85 - 0
ems-ui-cloud/src/views/largeScreen/dialog/pv-real.vue

@@ -0,0 +1,85 @@
+<template>
+  <threejs-base-dialog>
+    <template #title>
+      光伏实时数据
+    </template>
+    <template #data>
+      <div>
+        <span>发电量:</span>
+        <span class="value">{{ todayEnergy.gen }} kW·h</span>
+      </div>
+      <div>
+        <span>自用电量:</span>
+        <span class="value">{{ todayEnergy.use }} kW·h</span>
+      </div>
+      <div>
+        <span>上网电量:</span>
+        <span class="value">{{ todayEnergy.upload }} kW·h</span>
+      </div>
+      <div>
+        <span>上网收益:</span>
+        <span class="value">{{ todayEnergy.earn }} 元</span>
+      </div>
+    </template>
+  </threejs-base-dialog>
+</template>
+
+<script>
+import threejsBaseDialog from './base.vue'
+import {pgSupplyTotalPv} from "@/api/screen";
+import {numToStr} from "@/utils";
+
+let timer = null
+export default {
+  components: {
+    threejsBaseDialog,
+  },
+  props: {
+    propData: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
+  },
+  watch: {},
+  data() {
+    return {
+      todayEnergy: {}
+    };
+  },
+  // 组件卸载前清空图层信息
+  beforeDestroy() {
+    timer && clearInterval(timer)
+  },
+  created() {
+
+  },
+  mounted() {
+    this.init();
+  },
+  methods: {
+    init() {
+      this.getPgSupply()
+      timer = setInterval(this.getPgSupply, 1000 * 60 * 3)
+    },
+    async getPgSupply() {
+      const {data: pgPvSupplyTotalPv} = await pgSupplyTotalPv({
+        areaCode: this.propData.areaCode
+      })
+      if (!pgPvSupplyTotalPv) {
+        return
+      }
+      const {
+        thisDay,
+      } = pgPvSupplyTotalPv
+      this.todayEnergy = {
+        gen: numToStr(thisDay.genElecQuantity),
+        use: numToStr(thisDay.useElecQuantity),
+        upload: numToStr(thisDay.upElecQuantity),
+        earn: numToStr(thisDay.upElecEarn),
+      }
+    },
+  },
+};
+</script>

+ 107 - 0
ems-ui-cloud/src/views/largeScreen/dialog/storage-real.vue

@@ -0,0 +1,107 @@
+<template>
+  <threejs-base-dialog>
+    <template #title>
+      储能实时数据
+    </template>
+    <template #data>
+      <div>
+        <span>实时充电量:</span>
+        <span class="value">{{ energy }} %</span>
+      </div>
+      <div v-for="item in storageData">
+        <span>{{ item.name }}:</span>
+        <span class="value">{{ item.value }}{{ item.unit }}</span>
+      </div>
+    </template>
+  </threejs-base-dialog>
+</template>
+
+<script>
+import threejsBaseDialog from './base.vue'
+import {listConfig} from "@/api/system/config";
+import {DateTool} from "@/utils/DateTool";
+import {getSumHStorage, getSumRealTimeStorage} from "@/api/mgr/elecStoreH";
+import {numToStr} from "@/utils";
+
+let timer = null
+export default {
+  components: {
+    threejsBaseDialog,
+  },
+  props: {
+    propData: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
+  },
+  watch: {},
+  data() {
+    return {
+      cfg: {},
+      energy: 0,
+      storageData: [
+        {
+          name: "今日充电",
+          unit: "kW·h",
+        },
+        {
+          name: "今日放电",
+          unit: "kW·h",
+        }
+      ]
+    };
+  },
+  // 组件卸载前清空图层信息
+  beforeDestroy() {
+    timer && clearInterval(timer)
+  },
+  created() {
+
+  },
+  mounted() {
+    this.init();
+  },
+  methods: {
+    async init() {
+      const {rows} = await listConfig({
+        configKey: "storage-equipped-capacity",
+      })
+      if (!rows || !rows.length) {
+        return;
+      }
+      this.cfg = JSON.parse(rows[0].remark)
+      await this.getStorageData()
+      timer = setInterval(this.getStorageData, 1000 * 60 * 3)
+    },
+    async getStorageData() {
+      const {data: thisDay} = await getSumHStorage({
+        areaCode: this.propData.areaCode,
+        startRecTime: DateTool.now(),
+      })
+      const {data: hisTotal} = await getSumHStorage({
+        areaCode: this.propData.areaCode
+      })
+      const {data: realtimeStorage = {}} = await getSumRealTimeStorage({
+        areaCode: this.propData.areaCode,
+        startRecTime: DateTool.now()
+      })
+      let equipedStorage = this.cfg[this.propData.areaCode] ? this.cfg[this.propData.areaCode].store : 0
+      if (!this.propData.areaCode || this.propData.areaCode === '-1') {
+        for (let item in this.cfg) {
+          equipedStorage += this.cfg[item].store
+        }
+      }
+      const realTimeStorage = realtimeStorage.currentCapacity || 0
+      this.energy = (realTimeStorage / equipedStorage * 100).toFixed(2)
+      const [thisDayCharge, thisDayDischarge, totalCharge, totalDischarge] = this.storageData
+      thisDayCharge.value = numToStr(thisDay.chargeElecQuantity)
+      thisDayDischarge.value = numToStr(thisDay.dischargeElecQuantity)
+      totalCharge.value = numToStr(hisTotal.chargeElecQuantity)
+      totalDischarge.value = numToStr(hisTotal.dischargeElecQuantity)
+      this.storageData = [thisDayCharge, thisDayDischarge, totalCharge, totalDischarge]
+    },
+  },
+};
+</script>

+ 73 - 0
ems-ui-cloud/src/views/largeScreen/dialog/usage-real.vue

@@ -0,0 +1,73 @@
+<template>
+  <threejs-base-dialog>
+    <template #title>
+      负荷实时数据
+    </template>
+    <template #data>
+      <div v-for="item in socList">
+        <span>{{ item.name }}:</span>
+        <span class="value">{{ item.value }}{{ item.unit }}</span>
+      </div>
+    </template>
+  </threejs-base-dialog>
+</template>
+
+<script>
+import threejsBaseDialog from './base.vue'
+import {DateTool} from "@/utils/DateTool";
+import {numToStr} from "@/utils";
+import {qryElecMeterByDate, qryWaterMeterByDate} from "@/api/device/elecMeterH";
+
+let timer = null
+export default {
+  components: {
+    threejsBaseDialog,
+  },
+  props: {
+    propData: {
+      type: Object,
+      default: () => {
+        return {};
+      },
+    },
+  },
+  watch: {},
+  data() {
+    return {
+      socList: [
+        {
+          name: "用电量",
+          unit: "kW·h",
+        },
+        {
+          name: "用水量",
+          unit: "吨",
+        },
+      ],
+    };
+  },
+  // 组件卸载前清空图层信息
+  beforeDestroy() {
+    timer && clearInterval(timer)
+  },
+  created() {
+  },
+  mounted() {
+    this.init();
+  },
+  methods: {
+    async init() {
+      await this.getMeterElecWater()
+      timer = setInterval(this.getStorageData, 1000 * 60 * 3)
+    },
+    async getMeterElecWater() {
+      const {data: elecMeter} = await qryElecMeterByDate(DateTool.now(), this.propData.areaCode)
+      const {data: waterMeter} = await qryWaterMeterByDate(DateTool.now(), this.propData.areaCode)
+      const [elec, water] = this.socList
+      elec.value = numToStr(elecMeter.quantity)
+      water.value = numToStr(waterMeter.quantity)
+      this.socList = [elec, water]
+    },
+  },
+};
+</script>

+ 10 - 93
ems-ui-cloud/src/views/largeScreen/three/renderModel.js

@@ -270,7 +270,7 @@ class renderModel {
     //div元素包装为CSS3模型对象CSS3DSprite
     const element = document.createElement('div');
     element.className = 'customDialog';
-    element.innerHTML = html;
+    element.appendChild(html);
     const dialog = new CSS3DSprite(element);
     element.style.pointerEvents = 'none'; //避免HTML标签遮挡三维场景的鼠标事件
     // 设置HTML元素标签在three.js世界坐标中位置
@@ -331,9 +331,7 @@ class renderModel {
       //   }
       // });
       // this.initGUI();
-      this.createGuangfuLabel();
-      this.createFuheLabel();
-      this.createStorageLabel();
+
     });
   }
 
@@ -363,97 +361,16 @@ class renderModel {
     group.position.z = 17;
   }
 
-  createGuangfuLabel() {
-    const label3D = this.tag3D('光伏');
-    const dialog3D = this.createDialog(`
-      <div class="load-container" >
-<div class="title">光伏</div>
-<div class="data-grid">
-  <div class="data-item">
-    <span>装机容量:</span>
-    <span class="value">461kW·h</span>
-  </div>
-  <div class="data-item">
-    <span>发电量:</span>
-    <span class="value">1465kW·h</span>
-  </div>
-  <div class="data-item">
-    <span>发电功率:</span>
-    <span class="value">245kW·h</span>
-  </div>
-  <div class="data-item">
-    <span>上网功率:</span>
-    <span class="value">245kW·h</span>
-  </div>
-</div>
-</div>`); //设置标签名称
-    label3D.position.y += 0;
-    label3D.position.x += 93;
-    label3D.position.z += 30;
-    dialog3D.position.copy(label3D.position);
-    dialog3D.position.z = 50;
-    this.scene.add(label3D);
-    this.scene.add(dialog3D);
-  }
-
-  createStorageLabel() {
-    const label3D = this.tag3D('储能');
-    const dialog3D = this.createDialog(`
-      <div class="load-container" >
-<div class="title">储能</div>
-<div class="data-grid">
-     <div>
-        <span>电池容量:</span>
-        <span class="value">200kW·h</span>
-      </div>
-      <div>
-        <span>电池电量:</span>
-        <span class="value">17kW·h</span>
-      </div>
-      <div>
-        <span>有功功率:</span>
-        <span class="value">0.00kW·h</span>
-      </div>
-      <div>
-        <span>储能soc:</span>
-        <span class="value">9%</span>
-      </div>
-</div>
-</div>`); //设置标签名称
-    label3D.position.y += -30;
-    label3D.position.x += -34;
-    label3D.position.z += 30;
-    dialog3D.position.copy(label3D.position);
-    dialog3D.position.z = 50;
-    this.scene.add(label3D);
-    this.scene.add(dialog3D);
-  }
 
-  createFuheLabel() {
-    const label3D = this.tag3D('负荷');
-    const dialog3D = this.createDialog(`
-      <div class="load-container" >
-<div class="title">负荷</div>
-<div class="data-grid">
-      <div>
-        <span>用电负荷:</span>
-        <span class="value">461kW·h</span>
-      </div>
-      <div>
-        <span>总用电量:</span>
-        <span class="value">1465kW·h</span>
-      </div>
-      <div>
-        <span>自发自用:</span>
-        <span class="value">245kW·h</span>
-      </div>
-</div>
-</div>`); //设置标签名称
-    label3D.position.y += 0;
-    label3D.position.x += -115;
-    label3D.position.z += 30;
+  addDialog(html, labelName, position) {
+    const {x, y, z} = position
+    const label3D = this.tag3D(labelName);
+    const dialog3D = this.createDialog(html); //设置标签名称
+    label3D.position.y += y;
+    label3D.position.x += x;
+    label3D.position.z += z;
     dialog3D.position.copy(label3D.position);
-    dialog3D.position.z = 50;
+    dialog3D.position.z = z + 20;
     this.scene.add(label3D);
     this.scene.add(dialog3D);
   }

+ 1 - 1
ems-ui-cloud/src/views/mgr/powerstore.vue

@@ -9,7 +9,7 @@
         </el-statistic>
         <el-statistic :title="`光伏${item.name}装机`" v-for="(item,index) in pvCfg.areas" :key="`pv_${index}`">
           <template slot="formatter">
-            {{ numToStr(item.pv) }}kW
+            {{ numToStr(item.pv) }}kW·h
           </template>
         </el-statistic>
       </div>

+ 1 - 0
ems-ui-cloud/vue.config.js

@@ -15,6 +15,7 @@ const port = process.env.port || process.env.npm_config_port || 80 // 端口
 //官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
 // 这里只列一部分,具体配置参考文档
 module.exports = {
+  runtimeCompiler: true,
   // 部署生产环境和开发环境下的URL。
   // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
   // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。