浏览代码

Merge branch 'master' of http://182.92.126.35:3000/hrx/mky-vent-base

bobo04052021@163.com 3 天之前
父节点
当前提交
7f1568f98b
共有 31 个文件被更改,包括 1817 次插入28 次删除
  1. 二进制
      src/assets/images/home-green/11.png
  2. 二进制
      src/assets/images/home-green/12.png
  3. 二进制
      src/assets/images/home-green/13.png
  4. 二进制
      src/assets/images/home-green/15.png
  5. 二进制
      src/assets/images/home-green/2-3.png
  6. 二进制
      src/assets/images/home-green/5-1.png
  7. 二进制
      src/assets/images/home-green/5.png
  8. 二进制
      src/assets/images/home-green/green-bd-bottom.png
  9. 二进制
      src/assets/images/home-green/green-bd-left.png
  10. 二进制
      src/assets/images/home-green/green-border.png
  11. 二进制
      src/assets/images/home-green/green-menu-bg.png
  12. 二进制
      src/assets/images/home-green/green-menu-item.png
  13. 二进制
      src/assets/images/home-green/green-nav-bg.png
  14. 二进制
      src/assets/images/home-green/green-select-bg.png
  15. 二进制
      src/assets/images/home-green/green-tag.png
  16. 二进制
      src/assets/images/home-green/green-title-bg.png
  17. 二进制
      src/assets/images/home-green/green-wind-1.png
  18. 二进制
      src/assets/images/home-green/green-wind-tag.png
  19. 3 0
      src/views/vent/gas/gasReport/gas-report.api.ts
  20. 80 13
      src/views/vent/gas/gasReport/index.vue
  21. 71 0
      src/views/vent/home/configurable/components/ModuleOriginal-green.vue
  22. 429 0
      src/views/vent/home/configurable/components/content-green.vue
  23. 286 0
      src/views/vent/home/configurable/components/detail/MiniBoard-green.vue
  24. 218 0
      src/views/vent/home/configurable/components/green-nav.vue
  25. 67 0
      src/views/vent/home/configurable/components/green-right-tag.vue
  26. 118 0
      src/views/vent/home/configurable/components/header-green.vue
  27. 95 0
      src/views/vent/home/configurable/components/original/moduleBottom-green.vue
  28. 98 0
      src/views/vent/home/configurable/components/original/moduleLeft-green.vue
  29. 16 15
      src/views/vent/home/configurable/configurable.data.ts
  30. 324 0
      src/views/vent/home/configurable/vent-Green.vue
  31. 12 0
      src/views/vent/safetyList/common/detail.vue

二进制
src/assets/images/home-green/11.png


二进制
src/assets/images/home-green/12.png


二进制
src/assets/images/home-green/13.png


二进制
src/assets/images/home-green/15.png


二进制
src/assets/images/home-green/2-3.png


二进制
src/assets/images/home-green/5-1.png


二进制
src/assets/images/home-green/5.png


二进制
src/assets/images/home-green/green-bd-bottom.png


二进制
src/assets/images/home-green/green-bd-left.png


二进制
src/assets/images/home-green/green-border.png


二进制
src/assets/images/home-green/green-menu-bg.png


二进制
src/assets/images/home-green/green-menu-item.png


二进制
src/assets/images/home-green/green-nav-bg.png


二进制
src/assets/images/home-green/green-select-bg.png


二进制
src/assets/images/home-green/green-tag.png


二进制
src/assets/images/home-green/green-title-bg.png


二进制
src/assets/images/home-green/green-wind-1.png


二进制
src/assets/images/home-green/green-wind-tag.png


+ 3 - 0
src/views/vent/gas/gasReport/gas-report.api.ts

@@ -9,6 +9,7 @@ enum Api {
   getAllUserInfo = '/safety/gasInsCard/getAllUserInfo',
   exportReportByPoi = '/safety/reportInfo/exportReportByPoi',
  queryUserByRoleCode= '/safety/gasInsCard/queryUserByRoleCode',//通过角色编码查询角色下所有用户
+ gasServerImg='/gasServerImg',//获取瓦斯巡检图片
 }
 /**
  * 获取瓦斯日报区队,检测地点下拉选项
@@ -43,3 +44,5 @@ export const getAllUserInfo = (params) => defHttp.get({ url: Api.getAllUserInfo,
 export const exportReportByPoi = (params) => defHttp.post({ url: Api.exportReportByPoi, params, responseType: 'blob' });
 
 export const queryUserByRoleCode = (params) => defHttp.get({ url: Api.queryUserByRoleCode, params });
+
+export const gasServerImg = (params) => defHttp.post({ url: Api.gasServerImg, params,responseType: 'arraybuffer' });

+ 80 - 13
src/views/vent/gas/gasReport/index.vue

@@ -66,10 +66,11 @@
       </div>
       <a-table :columns="columns" :data-source="tableData" size="small" :scroll="{ y: 500 }" class="tableW"
         :pagination="pagination" @change="pageChange">
-        <template #bodyCell="{ column, text }">
+        <template #bodyCell="{ column, text, record }">
           <template
             v-if="column.dataIndex == 'night1' || column.dataIndex == 'night2' || column.dataIndex == 'early1' || column.dataIndex == 'early2' || column.dataIndex == 'noon1' || column.dataIndex == 'noon2'">
-            <a-button class="img-view" type="text" size="small" @click="imgViewClick(text)">查看</a-button>
+            <a-button class="img-view" type="text" size="small"
+              @click="imgViewClick({ record, dataIndex: column.dataIndex })">查看</a-button>
           </template>
           <template v-if="
             column.dataIndex == 'o2Night1' ||
@@ -129,7 +130,7 @@
     <BasicModal @register="registerModal" @ok="submitHandler">
       <BasicForm @register="registerForm" />
     </BasicModal>
-    //巡检图片预览弹窗
+
     <BasicModal @register="registerPageTypeModal" :showCancelBtn="false" :showOkBtn="false" :footer="null"
       :defaultFullscreen="true" destroyOnClose>
       <img style="width:100%;height:100%" :src="imgSrcView" alt="暂无图片">
@@ -140,7 +141,7 @@
 <script setup lang="ts">
 import { ref, onMounted, reactive } from 'vue';
 import { columns } from './gas-report.data';
-import { getGasAddressList, getList, expComReportByParam, reviewPass, getIsReviewPass, getAllUserInfo, exportReportByPoi, queryUserByRoleCode } from './gas-report.api';
+import { getGasAddressList, getList, expComReportByParam, reviewPass, getIsReviewPass, getAllUserInfo, exportReportByPoi, queryUserByRoleCode, gasServerImg } from './gas-report.api';
 import customHeader from '/@/components/vent/customHeader.vue';
 import { message } from 'ant-design-vue';
 import dayjs from 'dayjs';
@@ -166,10 +167,82 @@ let tableData = ref<any[]>([]);
 const [registerPageTypeModal, pageTypeModalCtx] = useModal();
 const [registerModal, { openModal }] = useModal();
 
-//瓦斯巡检图片预览
-function imgViewClick(img) {
+//打开瓦斯巡检预览弹窗
+function imgViewClick(record) {
+  imgSrcView.value = ''
   pageTypeModalCtx.openModal()
-  imgSrcView.value = img
+  getImage(record)
+}
+//获取瓦斯巡检图片
+function getImage(record) {
+  console.log(record, '000===')
+  let params
+  switch (record.dataIndex) {
+    case 'night1':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '夜班',
+        checkorder: 1,
+      }
+      getImageList(params)
+      break;
+    case 'night2':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '夜班',
+        checkorder: 2,
+      }
+      getImageList(params)
+      break;
+    case 'early1':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '早班',
+        checkorder: 1,
+      }
+      getImageList(params)
+      break;
+    case 'early2':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '早班',
+        checkorder: 2,
+      }
+      getImageList(params)
+      break;
+    case 'noon1':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '中班',
+        checkorder: 1,
+      }
+      getImageList(params)
+      break;
+    case 'noon2':
+      params = {
+        reportdate: record.record.reportTime,
+        checkpath: record.record.strInstallPos,
+        order: '中班',
+        checkorder: 2,
+      }
+      getImageList(params)
+      break;
+  }
+}
+async function getImageList(params) {
+  let res = await gasServerImg(params)
+  let imageData = res.data; // 这里是二进制数据
+  // 接下来你可以使用这个二进制数据来显示图片
+  // 例如,创建一个Image对象并设置src为imageData的URL
+  // let img = new Image();
+  let blob = new Blob([imageData]); // 如果是arraybuffer,需要转换为Blob
+  let url = URL.createObjectURL(blob);
+  imgSrcView.value = url
 }
 //获取日报列表数据
 async function getTableList() {
@@ -181,12 +254,6 @@ async function getTableList() {
     el.jwSdzEarly2 = el.jwSdzEarly2 || '-';
     el.jwSdzNoon1 = el.jwSdzNoon1 || '-';
     el.jwSdzNoon2 = el.jwSdzNoon2 || '-';
-    el.night1 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_夜班_1.png`
-    el.night2 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_夜班_2.png`
-    el.early1 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_早班_1.png`
-    el.early2 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_早班_2.png`
-    el.noon1 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_中班_1.png`
-    el.noon2 = `http://${window.location.hostname}:${window.location.port}/data/py_servers/imgs/${searchData.reportTime}_${el.strInstallPos}_中班_2.png`
   });
   tableData.value = res.records;
 

+ 71 - 0
src/views/vent/home/configurable/components/ModuleOriginal-green.vue

@@ -0,0 +1,71 @@
+<template>
+  <!-- 原版模块 -->
+  <component
+    :is="getModuleComponent(showStyle)"
+    :style="style"
+    :title="moduleName"
+    :visible="visible"
+    :class="{ 'cursor-pointer': !!moduleData.to }"
+    @close="$emit('close')"
+    @click="redirectTo"
+  >
+    <slot>
+      <Header :deviceType="deviceType" :moduleData="moduleData" :data="data" @select="selectedData = $event" />
+      <Content :style="{ height: header.show ? 'calc(100% - 30px)' : '100%' }" :moduleData="moduleData" :data="selectedData" />
+    </slot>
+  </component>
+</template>
+<script lang="ts" setup>
+  import Header from './header-green.vue';
+  import Content from './content-green.vue';
+  import ModuleLeft from './original/moduleLeft-green.vue';
+  import ModuleBottom from './original/moduleBottom-green.vue';
+  import { computed, ref } from 'vue';
+  import { openWindow } from '/@/utils';
+  import { getFormattedText } from '../hooks/helper';
+  // import { ModuleProps } from '../types';
+
+  const props = defineProps<{
+    /** 配置的详细模块信息 */
+    moduleData: any;
+    /** 配置的详细样式信息 */
+    showStyle: any;
+    /** 该模块配置中的设备标识符 */
+    deviceType: string;
+    /** api返回的数据 */
+    data: any;
+    moduleName: string;
+    visible: boolean;
+  }>();
+  defineEmits(['close', 'click']);
+
+  const { header } = props.moduleData;
+  const selectedData = ref();
+
+  const style = computed(() => {
+    const size = props.showStyle.size;
+    const position = props.showStyle.position;
+    return size + position;
+  });
+
+  // 根据配置里的定位判断应该使用哪个module组件
+  function getModuleComponent({ size, position }) {
+    const [_, width] = size.match(/width:([0-9]+)px/) || [];
+    if (position.includes('bottom') || parseInt(width) > 800) {
+      return ModuleBottom;
+    }
+    if (position.includes('left')) {
+      return ModuleLeft;
+    }
+    if (position.includes('right')) {
+      return ModuleLeft;
+    }
+    return ModuleBottom;
+  }
+
+  function redirectTo() {
+    const { to } = props.moduleData;
+    if (!to) return;
+    openWindow(getFormattedText(selectedData.value, to));
+  }
+</script>

+ 429 - 0
src/views/vent/home/configurable/components/content-green.vue

@@ -0,0 +1,429 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <!-- 主体内容部分 -->
+  <div class="content">
+    <!-- 背景 -->
+    <img v-if="background.show && background.type === 'image'" class="content__background" :src="background.link" />
+    <video
+      v-if="background.show && background.type === 'video'"
+      class="content__background content__background_video"
+      width="100%"
+      autoplay
+      loop
+      muted
+      disablepictureinpicture
+      playsinline
+    >
+      <source :src="background.link" />
+      Not Supportted Link Or Browser
+    </video>
+    <div class="flex w-full h-full" :style="{ flexDirection: layout.direction }">
+      <div v-for="config in layoutConfig" :key="config.name" :style="{ flexBasis: config.basis, overflow: config.overflow ? 'auto' : 'none' }">
+        <!-- 告示板部分 -->
+        <template v-if="config.name === 'board'">
+          <div class="content__module flex flex-justify-around flex-items-center flex-wrap">
+            <MiniBoard
+              v-for="item in config.items"
+              :key="item.prop"
+              :label="item.label"
+              :value="item.value"
+              :type="config.type"
+              :layout="config.layout"
+            />
+          </div>
+        </template>
+        <!-- 图表部分,这部分通常需要填充,有告示板、Header等内容需要填充父级 -->
+        <template v-if="config.name === 'chart'">
+          <CustomChart class="content__module" :chart-config="config.config" :chart-data="config.data" />
+        </template>
+        <!-- 通常列表部分 -->
+        <template v-if="config.name === 'list'">
+          <template v-if="config.type === 'timeline'">
+            <TimelineList class="content__module" :list-config="config.items" />
+          </template>
+          <template v-else-if="config.type === 'timelineNew'">
+            <TimelineListNew class="content__module" :list-config="config.items" />
+          </template>
+          <template v-else>
+            <CustomList class="content__module" :type="config.type" :list-config="config.items" />
+          </template>
+        </template>
+        <!-- 画廊部分 -->
+        <template v-if="config.name === 'gallery'">
+          <CustomGallery class="content__module" :type="config.type" :gallery-config="config.items" />
+        </template>
+        <!-- 复杂列表部分 -->
+        <template v-if="config.name === 'gallery_list'">
+          <GalleryList class="content__module" :type="config.type" :list-config="config.items" :gallery-config="config.galleryItems" />
+        </template>
+        <!-- 复杂列表部分 -->
+        <template v-if="config.name === 'complex_list'">
+          <ComplexList class="content__module" :type="config.type" :list-config="config.items" />
+        </template>
+        <!-- 表格部分,这部分通常是占一整个模块的 -->
+        <template v-if="config.name === 'table'">
+          <CustomTable class="content__module text-center overflow-auto" :type="config.type" :columns="config.columns" :data="config.data" />
+        </template>
+        <template v-if="config.name === 'tabs'">
+          <CustomTabs class="content__module" :type="config.type" :tab-config="config.items" :overflow="config.overflow" />
+        </template>
+        <template v-if="config.name === 'blast_delta'">
+          <BlastDelta class="content__module" :pos-monitor="config.data" :canvasSize="{ width: 250, height: 200 }" />
+        </template>
+        <template v-if="config.name === 'qh_curve'">
+          <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
+        </template>
+        <template v-if="config.name === 'ai_chat'">
+          <AIChat class="content__module" />
+        </template>
+        <template v-if="config.name === 'device_alarm'">
+          <DeviceAlarm class="content__module" :devicedata="config.data" />
+        </template>
+        <template v-if="config.name === 'measure_detail'">
+          <MeasureDetail
+            class="content__module"
+            :show-title="false"
+            :composite-data="config.data"
+            :topconfig="config.config.topconfig"
+            :btnconfig="config.config.btnconfig"
+          />
+        </template>
+        <!-- <template v-if="config.key === 'fire_control'">
+        <FIreControl class="content__module" />
+      </template>
+      <template v-if="config.key === 'fire_warn'">
+        <FIreWarn class="content__module" />
+      </template> -->
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { computed } from 'vue';
+  import {
+    CommonItem,
+    Config,
+    // ModuleDataBoard,
+    // ModuleDataChart,
+    // ModuleDataList,
+    // ModuleDataPreset,
+    // ModuleDataTable,
+  } from '../../../deviceManager/configurationTable/types';
+  import MiniBoard from './detail/MiniBoard-green.vue';
+  import TimelineList from './detail/TimelineList.vue';
+  import TimelineListNew from './detail/TimelineListNew.vue';
+  import CustomList from './detail/CustomList.vue';
+  import CustomGallery from './detail/CustomGallery.vue';
+  import ComplexList from './detail/ComplexList.vue';
+  import GalleryList from './detail/GalleryList.vue';
+  import CustomTable from './detail/CustomTable.vue';
+  import CustomChart from './detail/CustomChart.vue';
+  import { clone } from 'lodash-es';
+  import { getData, getFormattedText } from '../hooks/helper';
+  import BlastDelta from '../../../monitorManager/deviceMonitor/components/device/modal/blastDelta.vue';
+  import QHCurve from './preset/QHCurve.vue';
+  import MeasureDetail from './preset/MeasureDetail.vue';
+  import CustomTabs from './preset/CustomTabs.vue';
+  import AIChat from '/@/components/AIChat/MiniChat.vue';
+  import DeviceAlarm from './preset/DeviceAlarm.vue';
+  // import FIreWarn from './preset/FIreWarn.vue';
+  // import FIreControl from './preset/FIreControl.vue';
+
+  const props = defineProps<{
+    data: any;
+    moduleData: Config['moduleData'];
+  }>();
+
+  const { background, layout } = props.moduleData;
+
+  // 获取当原始配置带 items 项时的最终 items 配置
+  function getItems(raw, items: CommonItem[]) {
+    return items.map((i) => {
+      return {
+        ...i,
+        label: getFormattedText(raw, i.label, i.trans),
+        value: getFormattedText(raw, i.value, i.trans),
+      };
+    });
+  }
+
+  // 获取当 List 组件配置带 items 项时的最终 items 配置
+  function getListItems(raw: any, items: CommonItem[], mapFromData?: boolean) {
+    if (mapFromData && Array.isArray(raw)) {
+      return raw.map((data) => {
+        const item = items[0];
+        return {
+          ...item,
+          label: getFormattedText(data, item.label, item.trans),
+          value: getFormattedText(data, item.value, item.trans),
+        };
+      });
+    }
+    return getItems(raw, items);
+  }
+
+  /** 根据配置里的layout将配置格式化为带 key 的具体配置,例如:[{ key: 'list', value: any, ...ModuleDataList }] */
+  const layoutConfig = computed(() => {
+    const refData = props.data;
+    const board = clone(props.moduleData.board) || [];
+    const list = clone(props.moduleData.list) || [];
+    const gallery = clone(props.moduleData.gallery) || [];
+    const complex_list = clone(props.moduleData.complex_list) || [];
+    const gallery_list = clone(props.moduleData.gallery_list) || [];
+    const tabs = clone(props.moduleData.tabs) || [];
+    const chart = clone(props.moduleData.chart) || [];
+    const table = clone(props.moduleData.table) || [];
+    const preset = clone(props.moduleData.preset) || [];
+
+    return layout.items.reduce((arr: any[], item) => {
+      switch (item.name) {
+        case 'board': {
+          const cfg = board.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            overflow: true,
+            ...item,
+            ...cfg,
+            items: getItems(data, cfg.items),
+          });
+          break;
+        }
+        case 'list': {
+          const cfg = list.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            overflow: true,
+            ...item,
+            ...cfg,
+            items: getListItems(data, cfg.items, cfg.mapFromData),
+          });
+          break;
+        }
+        case 'gallery': {
+          const cfg = gallery.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            overflow: true,
+            ...item,
+            ...cfg,
+            items: getItems(data, cfg.items),
+          });
+          break;
+        }
+        case 'complex_list': {
+          const cfg = complex_list.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          if (cfg.mapFromData) {
+            const firstListItem = cfg.items[0];
+            arr.push({
+              overflow: true,
+              ...item,
+              ...cfg,
+              items: (data || []).map((d) => {
+                return {
+                  title: getFormattedText(d, firstListItem.title, firstListItem.trans),
+                  contents: firstListItem.contents.map((e) => {
+                    return {
+                      ...e,
+                      label: getFormattedText(d, e.label, e.trans),
+                      value: getFormattedText(d, e.value, e.trans),
+                    };
+                  }),
+                };
+              }),
+            });
+          } else {
+            arr.push({
+              overflow: true,
+              ...item,
+              ...cfg,
+              items: cfg.items.map((i) => {
+                return {
+                  title: getFormattedText(data, i.title, i.trans),
+                  contents: i.contents.map((e) => {
+                    return {
+                      ...e,
+                      label: getFormattedText(data, e.label, e.trans),
+                      value: getFormattedText(data, e.value, e.trans),
+                    };
+                  }),
+                };
+              }),
+            });
+          }
+          break;
+        }
+        case 'gallery_list': {
+          const cfg = gallery_list.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            overflow: true,
+            ...item,
+            ...cfg,
+            items: getItems(data, cfg.items),
+            galleryItems: getItems(data, cfg.galleryItems),
+          });
+          break;
+        }
+        case 'tabs': {
+          const cfg = tabs.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          if (cfg.mapFromData) {
+            const firstListItem = cfg.items[0];
+            arr.push({
+              overflow: true,
+              ...item,
+              ...cfg,
+              items: (data || []).map((d) => {
+                return {
+                  title: getFormattedText(d, firstListItem.title, firstListItem.trans),
+                  contents: firstListItem.contents.map((e) => {
+                    return {
+                      ...e,
+                      label: getFormattedText(d, e.label, e.trans),
+                      value: getFormattedText(d, e.value, e.trans),
+                    };
+                  }),
+                };
+              }),
+            });
+          } else {
+            arr.push({
+              overflow: true,
+              ...item,
+              ...cfg,
+              items: cfg.items.map((i) => {
+                return {
+                  title: getFormattedText(data, i.title, i.trans),
+                  contents: i.contents.map((e) => {
+                    return {
+                      ...e,
+                      label: getFormattedText(data, e.label, e.trans),
+                      value: getFormattedText(data, e.value, e.trans),
+                    };
+                  }),
+                };
+              }),
+            });
+          }
+          break;
+        }
+        case 'chart': {
+          const cfg = chart.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            ...item,
+            config: cfg,
+            data,
+          });
+          break;
+        }
+        case 'table': {
+          const cfg = table.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            ...cfg,
+            ...item,
+            columns: cfg.columns,
+            data,
+          });
+          break;
+        }
+        default: {
+          const cfg = preset.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
+
+          arr.push({
+            ...item,
+            data,
+            config: cfg,
+          });
+          break;
+        }
+      }
+      return arr;
+    }, []);
+  });
+</script>
+<style lang="less" scoped>
+  @import '@/design/theme.less';
+
+  .content {
+    height: calc(100% - 30px);
+    position: relative;
+    // z-index: -2;
+    display: flex;
+    flex-direction: column;
+  }
+  .content__background {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 0;
+    object-fit: fill;
+  }
+  .content__module {
+    // margin-top: 5px;
+    // margin-bottom: 5px;
+    width: 100%;
+    height: 100%;
+  }
+  // .content__module:first-of-type {
+  //   margin-top: 0;
+  // }
+  // .content__module:last-of-type {
+  //   margin-bottom: 0;
+  // }
+  ::-webkit-scrollbar {
+    width: 5px !important;
+  }
+  ::-webkit-scrollbar-thumb {
+    width: 5px !important;
+  }
+
+  :deep(.zxm-select:not(.zxm-select-customize-input) .zxm-select-selector) {
+    /* background-color: transparent; */
+    color: #fff;
+  }
+  :deep(.zxm-select-arrow) {
+    color: #fff;
+  }
+  :deep(.zxm-select-selection-item) {
+    color: #fff !important;
+  }
+  :deep(.zxm-select-selection-placeholder) {
+    color: #fff !important;
+  }
+  :deep(.dialog-overlay) {
+    width: 100%;
+    height: 100%;
+    position: unset;
+    box-shadow: unset;
+  }
+
+  ::-webkit-scrollbar {
+    width: 5px !important;
+  }
+  ::-webkit-scrollbar-thumb {
+    width: 5px !important;
+  }
+</style>

+ 286 - 0
src/views/vent/home/configurable/components/detail/MiniBoard-green.vue

@@ -0,0 +1,286 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div class="mini-board" :class="`mini-board_${type} mini-board_${type}_${getValueDecoClass(value)}`">
+    <template v-if="layout === 'val-top'">
+      <slot name="label">
+        <div class="mini-board__label" :class="`mini-board__label_${type}`">
+          {{ label }}
+        </div>
+      </slot>
+       <slot name="value">
+        <div class="mini-board__value" :class="`mini-board__value_${type}`">
+          {{ value }}
+        </div>
+      </slot>
+    </template>
+    <template v-if="layout === 'label-top'">
+      <slot name="value">
+        <div class="mini-board__value" :class="`mini-board__value_${type}`">
+          {{ value }}
+        </div>
+      </slot>
+       <slot name="label">
+        <div class="mini-board__label" :class="`mini-board__label_${type}`">
+          {{ label }}
+        </div>
+      </slot>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  withDefaults(
+    defineProps<{
+      label: string;
+      value?: string;
+      // 告示牌布局,类型为:'val-top' | 'label-top'
+      layout: string;
+      // 告示牌类型,类型为:'A' | 'B' | 'C' | 'D' | 'E' | 'F'
+      type?: string;
+    }>(),
+    {
+      value: '/',
+      type: 'A',
+      layout: 'val-top',
+    }
+  );
+
+  // 获取某些 value 对应的特殊的 装饰用的类名
+  function getValueDecoClass(value) {
+    switch (value) {
+      case '低风险':
+        return 'low_risk';
+      case '一般风险':
+        return 'risk';
+      case '较大风险':
+        return 'high_risk';
+      case '报警':
+        return 'warning';
+      default:
+        return '';
+    }
+  }
+
+  defineEmits(['click']);
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+  @import '/@/design/theme.less';
+
+  @{theme-deepblue} {
+    .mini-board {
+      --image-area3: url('/@/assets/images/themify/deepblue/company/area3.png');
+      // --image-value-bg: url('/@/assets/images/themify/deepblue/vent/value-bg.png');
+      --image-vent-param-bg: url('/@/assets/images/themify/deepblue/vent/vent-param-bg.png');
+    //   --image-mini-board-1: url('/@/assets/images/themify/deepblue/vent/home/mini-board-1.png');
+       --image-mini-board-1: url('/@/assets/images/themify/deepblue/vent/home/mini-board-1.png');
+      --image-board_bg_1: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_1.png');
+      --image-miehuo: url('/@/assets/images/themify/deepblue/home-container/configurable/firehome/miehuo.png');
+      --image-value-bg-2: url('/@/assets/images/themify/deepblue/vent/value-bg-2.png');
+      --image-board_bg_3: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_3.png');
+      --image-board_bg_2: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_2.png');
+      --image-board_bg_5: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_5.png');
+      --image-board_bg_4: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_4.png');
+    }
+  }
+
+  .mini-board {
+    --image-area3: url('/@/assets/images/company/area3.png');
+    // --image-value-bg: url('/@/assets/images/vent/value-bg.png');
+     --image-value-bg: url('/@/assets/images/home-green/green-tag.png');
+    --image-vent-param-bg: url('/@/assets/images/vent/vent-param-bg.png');
+    // --image-mini-board-1: url('/@/assets/images/vent/home/mini-board-1.png');
+      --image-mini-board-1: url('/@/assets/images/home-green/green-tag.png');
+    --image-board_bg_1: url('/@/assets/images/home-container/configurable/board_bg_1.png');
+    --image-miehuo: url('/@/assets/images/home-container/configurable/firehome/miehuo.png');
+    --image-value-bg-2: url('/@/assets/images/vent/value-bg-2.png');
+    --image-board_bg_3: url('/@/assets/images/home-container/configurable/board_bg_3.png');
+    --image-board_bg_2: url('/@/assets/images/home-container/configurable/board_bg_2.png');
+    --image-board_bg_5: url('/@/assets/images/home-container/configurable/board_bg_5.png');
+    --image-board_bg_4: url('/@/assets/images/home-container/configurable/board_bg_4.png');
+    --image-board_bg_6: url('/@/assets/images/home-container/configurable/board_bg_6.png');
+
+    --image-hycd: url(/@/assets/images/home-container/configurable/dusthome/hycd.png);
+    --image-dyfl: url(/@/assets/images/home-container/configurable/dusthome/dyfl.png);
+    --image-jdjl: url(/@/assets/images/home-container/configurable/dusthome/jdjl.png);
+    height: 52px;
+    line-height: 25px;
+    width: 110px;
+    padding: 0 5px 0 5px;
+    text-align: center;
+    background-size: 100% 100%;
+    position: relative;
+  }
+
+  .mini-board_A {
+    width: 120px;
+    height: 60px;
+    background-image: var(--image-area3);
+    background-size: 100% 100%;
+  }
+  .mini-board_B {
+    width: 115px;
+    height: 50px;
+    background-image: var(--image-value-bg);
+    background-size: auto 40px;
+    background-position: center bottom;
+    background-repeat: no-repeat;
+    background-size:100% 100%
+  }
+  .mini-board_C {
+    width: 121px;
+    height: 69px;
+    background-image: var(--image-vent-param-bg);
+  }
+  .mini-board_D {
+    // width: 105px;
+    height: 50px;
+    background-image: var(--image-mini-board-1);
+    background-position: center bottom;
+    background-size:100% 100%;
+    background-repeat: no-repeat;
+  }
+  .mini-board_E {
+    width: 30%;
+    height: 180px;
+    padding: 20px 5px;
+    background-image: var(--image-board_bg_1);
+    background-position: center bottom;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+  }
+  .mini-board_F {
+    width: 140px;
+    height: 70px;
+    background-image: var(--image-miehuo);
+    background-size: 100% 80%;
+    background-position: center bottom;
+    background-repeat: no-repeat;
+  }
+  .mini-board_G {
+    width: 98px;
+    height: 70px;
+    background-image: var(--image-value-bg-2);
+    background-size: 100% auto;
+    background-position: center bottom;
+    background-repeat: no-repeat;
+  }
+  .mini-board_H {
+    width: 174px;
+    height: 104px;
+    background-image: var(--image-board_bg_3);
+    background-size: 100% auto;
+    background-position: center bottom;
+    background-repeat: no-repeat;
+    padding: 45px 0 0 0;
+  }
+  .mini-board_I {
+    width: 139px;
+    height: 67px;
+    background-image: var(--image-board_bg_6);
+    background-size: 100% 100%;
+  }
+
+  .mini-board__value_A {
+    color: @vent-gas-primary-text;
+    font-size: 20px;
+    font-weight: bold;
+    height: 30px;
+    line-height: 30px;
+  }
+
+  .mini-board__value_B {
+    // color: @vent-gas-primary-text;
+    color:#3ecca7;
+    font-size: 16px;
+    font-weight: bold;
+    height: 28px;
+    line-height: 28px;
+  }
+  .mini-board__label_B {
+    line-height: 16px;
+    height: 16px;
+    font-size:12px;
+  }
+
+  .mini-board__value_C {
+    color: @vent-gas-primary-text;
+    height: 40px;
+    line-height: 40px;
+    font-size: 20px;
+    font-weight: bold;
+  }
+
+  .mini-board__value_D {
+    font-size: 16px;
+    font-weight: bold;
+    height: 28px;
+    line-height: 32px;
+    color:#3ecca7;
+  }
+  .mini-board__label_D {
+    line-height: 18px;
+    height: 18px;
+    font-size:12px;
+  }
+  .mini-board__value_E {
+    font-size: 20px;
+    font-weight: bold;
+  }
+  .mini-board__label_E {
+    line-height: 20px;
+    height: 90px;
+    padding-top: 30%;
+    background-repeat: no-repeat;
+    background-position: center top;
+  }
+
+  .mini-board__value_F {
+    font-size: 20px;
+    font-weight: bold;
+    color: @vent-gas-primary-text;
+  }
+  .mini-board__label_F {
+    line-height: 50px;
+  }
+
+  .mini-board__value_G {
+    color: @vent-gas-primary-text;
+    font-size: 20px;
+    font-weight: bold;
+    height: 42px;
+    line-height: 42px;
+  }
+  .mini-board__label_G {
+    line-height: 20px;
+    height: 20px;
+  }
+
+  .mini-board_E:nth-child(1) {
+    .mini-board__label_E {
+      background-image: var(--image-hycd);
+    }
+  }
+  .mini-board_E:nth-child(2) {
+    .mini-board__label_E {
+      background-image: var(--image-dyfl);
+    }
+  }
+  .mini-board_E:nth-child(3) {
+    .mini-board__label_E {
+      background-image: var(--image-jdjl);
+    }
+  }
+
+  .mini-board_H_low_risk {
+    background-image: var(--image-board_bg_3);
+  }
+  .mini-board_H_risk {
+    background-image: var(--image-board_bg_2);
+  }
+  .mini-board_H_high_risk {
+    background-image: var(--image-board_bg_5);
+  }
+  .mini-board_H_warning {
+    background-image: var(--image-board_bg_4);
+  }
+</style>

+ 218 - 0
src/views/vent/home/configurable/components/green-nav.vue

@@ -0,0 +1,218 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div class="green-nav">
+    <div class="main-title">{{ Title }}</div>
+    <!-- menu区域 -->
+    <div class="nav-menu">
+      <div :class="activeIndex == index ? 'nav-menu-active' : 'nav-menu-unactive'" v-for="(item, index) in menuList"
+        :key="index" @click="menuClick(index)">
+        <div>{{ item.name }}</div>
+        <!-- menu-item -->
+        <div v-if="activeIndex == index && isShowMenuItem" class="nav-menu-item">
+          <div class="nav-menu-content">
+            <div :class="menuItemActive == ind ? 'menu-item-active' : 'menu-item-unactive'"
+              v-for="(ite, ind) in item.MenuItemList" :key="ind" @click.stop="menuItemClick(ind)">{{ ite.name }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
+
+let props = defineProps({
+  Title: {
+    type: String,
+    default: ''
+  }
+})
+
+let menuList = ref<any[]>([
+  { name: '灾害预警',MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+] },
+  { name: '智能通风',MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+] },
+  { name: '火灾监控' ,MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+]},
+  { name: '粉尘监控' ,MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+]},
+  { name: '瓦斯监控' ,MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+]},
+  { name: '综合管控',MenuItemList:[
+  { name: '光纤测温监测' },
+  { name: '束管监测' },
+  { name: '智能注氮系统' },
+  { name: '智能注浆系统' },
+  { name: '火灾监测预警' },
+  { name: '色谱仪报表分析' },
+] },
+])//一级菜单列表
+let activeIndex = ref(0)//当前激活menu索引
+
+let isShowMenuItem=ref(false)//是否显示menuItem下拉选项菜单
+let menuItemActive = ref(0)//menuItem当前激活选项
+
+
+//menu选项切换
+function menuClick(index) {
+  activeIndex.value = index
+  isShowMenuItem.value=true
+}
+//menuItem选项切换
+function menuItemClick(ind) {
+  menuItemActive.value = ind
+  isShowMenuItem.value=false
+}
+</script>
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+@font-face {
+  font-family: 'douyuFont';
+  src: url('/@/assets/font/douyuFont.otf');
+}
+
+.green-nav {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background: url('../../../../../assets/images/home-green/green-nav-bg.png') no-repeat;
+  background-size: 100% 100%;
+
+  .main-title {
+    width: 518px;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    padding-left: 70px;
+    font-family: 'douyuFont';
+    font-size: 20px;
+    letter-spacing: 2px;
+
+  }
+
+  .nav-menu {
+    position: absolute;
+    top: 0;
+    left: 675px;
+    width: 880px;
+    height: 100%;
+    display: flex;
+    justify-content: flex-start;
+
+    .nav-menu-active {
+      position: relative;
+      width: 120px;
+      height: 60px;
+      line-height: 60px;
+      text-align: center;
+      margin: 0px 20px;
+      font-size: 16px;
+      letter-spacing: 2px;
+      background: url('../../../../../assets/images/home-green/green-menu-bg.png') no-repeat;
+      background-size: 100% 100%;
+    }
+
+    .nav-menu-unactive {
+      position: relative;
+      width: 120px;
+      height: 60px;
+      line-height: 60px;
+      text-align: center;
+      margin: 0px 10px;
+      font-size: 16px;
+      letter-spacing: 2px;
+      background-size: 100% 100%;
+      cursor: pointer;
+    }
+
+    .nav-menu-item {
+      position: absolute;
+      left: -34px;
+      top: 56px;
+      width: 186px;
+      height: 273px;
+      padding: 28px 12px 12px 12px;
+      background: url(/src/assets/images/home-green/green-menu-item.png) no-repeat;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      box-sizing: border-box;
+   
+
+      .nav-menu-content {
+        width: 100%;
+        height: 100%;
+        overflow-y: auto;
+
+        .menu-item-active {
+          width: 100%;
+          height: 36px;
+          line-height: 36px;
+          font-size: 14px;
+          background: linear-gradient(to right, transparent, rgba(47, 132, 111), transparent);
+        }
+
+        .menu-item-unactive {
+          width: 100%;
+          height: 40px;
+          line-height: 40px;
+          font-size: 14px;
+        }
+      }
+    }
+
+    @keyframes fadeIn {
+      from {
+        opacity: 0;
+      }
+
+      to {
+        opacity: 1;
+      }
+    }
+
+    /* 定义淡出动画 */
+    @keyframes fadeOut {
+      from {
+        opacity: 1;
+      }
+
+      to {
+        opacity: 0;
+      }
+    }
+  }
+}
+</style>

+ 67 - 0
src/views/vent/home/configurable/components/green-right-tag.vue

@@ -0,0 +1,67 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div class="greenRightTag">
+    <div class="wind-tag" v-for="(item, index) in tagList">
+      <div class="wind-tag-label">{{ item.label }}</div>
+      <div class="wind-tag-val">{{ item.value }}</div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
+
+let tagList = ref<any[]>([
+  { label: '总回风量(m³/min)', value: '25870' },
+  { label: '总进风量(m³/min)', value: '24989' },
+  { label: '计划风量(m³/min)', value: '348' },
+  { label: '有效风量率', value: '87.3%' },
+
+])
+
+</script>
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+@font-face {
+  font-family: 'douyuFont';
+  src: url('/@/assets/font/douyuFont.otf');
+}
+
+.greenRightTag {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-items: center;
+
+  .wind-tag {
+    position: relative;
+    width: 110px;
+    height: 124px;
+    background: url('../../../../../assets//images/home-green/green-wind-tag.png') no-repeat;
+    background-size: 100% 100%;
+
+    .wind-tag-label {
+      position: absolute;
+      left: 0;
+      bottom: 34px;
+      width: 100%;
+      text-align: center;
+      font-size: 12px;
+    }
+
+    .wind-tag-val {
+      position: absolute;
+    left: 0;
+    bottom: 5px;
+    width: 100%;
+    text-align: center;
+    font-size: 16px;
+    font-weight: 700;
+    color: #39f4e0;
+    }
+  }
+}
+</style>

+ 118 - 0
src/views/vent/home/configurable/components/header-green.vue

@@ -0,0 +1,118 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <!-- Header部分 -->
+  <div v-if="headerConfig.show" class="w-100% flex costume-header">
+    <!-- 选择下拉框,自动填充剩余空间,这种实现是因为 Select 不支持 suffix -->
+    <Dropdown
+      v-if="headerConfig.selector.show"
+      class="flex-grow-1 "
+      :class="headerConfig.slot.show ? 'costume-header_left' : 'costume-header_left1' "
+      :trigger="['click']"
+      :bordered="false"
+      @open-change="visible = $event"
+    >
+      <div class="flex-basis-60% flex flex-items-center" @click.prevent>
+        <!-- <SwapOutlined class="w-30px" /> -->
+        <div class="w-100px flex-grow-1 overflow-hidden whitespace-nowrap text-ellipsis select-text">
+          {{ selectedDeviceLabel }}
+        </div>
+        <CaretUpOutlined class="w-30px" v-if="visible" />
+        <CaretDownOutlined class="w-30px" v-else />
+      </div>
+      <template #overlay>
+        <Menu :selected-keys="[selectedDeviceID]" @click="selectHandler">
+          <MenuItem v-for="item in options" :key="item.value" :title="item.label">
+            {{ item.label }}
+          </MenuItem>
+        </Menu>
+      </template>
+    </Dropdown>
+    <template v-if="headerConfig.slot.show">
+      <div class="flex-basis-40% flex flex-items-center flex-grow-1 costume-header_right select-text">
+        <!-- <SwapOutlined class="w-30px" /> -->
+        <div class="flex-grow-1">
+          {{ selectedDeviceSlot }}
+        </div>
+      </div>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { ref, watch } from 'vue';
+  import { Config } from '../../../deviceManager/configurationTable/types';
+  import { useInitModule } from '../hooks/useInit';
+  import { MenuItem, Menu, Dropdown } from 'ant-design-vue';
+  import { SwapOutlined, CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons-vue';
+
+  const props = defineProps<{
+    moduleData: Config['moduleData'];
+    deviceType: Config['deviceType'];
+    data: any;
+  }>();
+
+  const emit = defineEmits(['select']);
+
+  const visible = ref(false);
+  const headerConfig = props.moduleData.header;
+  const { selectedDeviceID, selectedDevice, selectedDeviceSlot, selectedDeviceLabel, options, init } = useInitModule(
+    props.deviceType,
+    props.moduleData
+  );
+
+  function selectHandler({ key }) {
+    selectedDeviceID.value = key;
+    emit('select', selectedDevice.value);
+  }
+
+  watch(
+    () => props.data,
+    (d) => {
+      init(d);
+      emit('select', selectedDevice.value);
+    },
+    {
+      immediate: true,
+    }
+  );
+</script>
+<style scoped>
+  @import '/@/design/theme.less';
+
+  .costume-header {
+    height: 30px;
+    /* background-image: linear-gradient(90deg, var(--vent-base-light-bg-opcity), transparent 20%, transparent 80%, var(--vent-base-light-bg-opcity)); */
+    background:url('../../../../../assets/images/home-green/green-select-bg.png') no-repeat;
+    background-size:100% 100%;
+  }
+  .costume-header_left {
+    /* border-left: 3px solid; */
+    border-right: 3px solid;
+    border-image-source: linear-gradient(to top, #00000033, var(--vent-base-light-bg), #00000033);
+    border-image-slice: 1;
+  }
+  .costume-header_left1 {
+    border-image-slice: 1;
+  }
+  /* .costume-header_right {
+    border-right: 3px solid;
+    border-image-source: linear-gradient(to top, #00000033, var(--vent-base-light-bg), #00000033);
+    border-image-slice: 1;
+  } */
+   .select-text{
+    padding-left:20px;
+   }
+
+  ::v-deep .zxm-select:not(.zxm-select-customize-input) .zxm-select-selector {
+    /* background-color: transparent; */
+    color: #fff;
+  }
+  ::v-deep .zxm-select-arrow {
+    color: #fff;
+  }
+  ::v-deep .zxm-select-selection-item {
+    color: #fff !important;
+  }
+  ::v-deep .zxm-select-selection-placeholder {
+    color: #fff !important;
+  }
+</style>

+ 95 - 0
src/views/vent/home/configurable/components/original/moduleBottom-green.vue

@@ -0,0 +1,95 @@
+<template>
+  <div v-if="visible" class="module-content">
+    <div v-if="title" class="module-content__title__expand">
+      <span class="action-btn close-btn" @click="closeModel"></span>
+      <span @click="clickHandler">{{ title }}</span>
+    </div>
+    <div class="module-slot">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  defineProps<{ title: string; visible: boolean }>();
+  const emit = defineEmits(['close', 'click']);
+
+  function closeModel() {
+    emit('close');
+  }
+  function clickHandler() {
+    emit('click');
+  }
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+
+  @{theme-deepblue} {
+    .module-content {
+    //   --image-model_original_title_bg_expand: url('@/assets/images/themify/deepblue/home-container/configurable/model_original_title_bg_expand.png');
+    }
+  }
+
+  .module-content {
+    // --image-model_original_title_bg_expand: url('@/assets/images/home-container/configurable/model_original_title_bg_expand.png');
+      --image-model_original_title_bg_expand: url('@/assets/images/home-green/green-title-bg.png');
+    --bg-height: 33px;
+    color: #fff;
+    box-sizing: border-box;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+  }
+
+  .module-content__title__expand {
+    width: 100%;
+    height: var(--bg-height);
+    background: var(--image-model_original_title_bg_expand) no-repeat;
+    background-size: 100% 100%;
+    position: relative;
+    text-align: center;
+    line-height: var(--bg-height);
+  }
+
+  // .module-content__title {
+  //   width: 50%;
+  //   height: var(--bg-height);
+  //   background: url('../../../../../assets/images/home-container/configurable/model_bottom_title_bg.png') no-repeat;
+  //   background-size: 100% 100%;
+  //   position: relative;
+  //   text-align: left;
+  //   padding-left: 5%;
+  // }
+
+  // 固定在父容器右上角的按钮图标
+  // .action-btn {
+  //   width: 18px;
+  //   height: 18px;
+  //   background: url('../../../../../assets/images/home-container/configurable/expand.svg') no-repeat center;
+  //   position: absolute;
+  //   left: 0;
+  //   top: 0;
+  // }
+  // .show-btn {
+  //   transform: rotate(-90deg);
+  // }
+
+  .module-slot {
+    height: calc(100% - 33px);
+    width: calc(100% - 15px);
+    backdrop-filter: blur(5px);
+    // background-color: var(--vent-configurable-original-module-bg);
+    margin-left: 5px;
+  }
+
+  // Transition动画相关
+  .v-enter-active,
+  .v-leave-active {
+    transition: all 0.3s ease;
+  }
+
+  .v-enter-from,
+  .v-leave-to {
+    opacity: 0;
+    transform: translateY(-33px);
+  }
+</style>

+ 98 - 0
src/views/vent/home/configurable/components/original/moduleLeft-green.vue

@@ -0,0 +1,98 @@
+<template>
+  <div v-if="visible" class="module-content">
+    <div v-if="title" class="module-content__title__expand">
+      <span class="action-btn close-btn" @click="closeModel"></span>
+      <span @click="clickHandler">{{ title }}</span>
+    </div>
+    <div class="module-slot">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+  defineProps<{ title: string; visible: boolean }>();
+  const emit = defineEmits(['close', 'click']);
+
+  function closeModel() {
+    emit('close');
+  }
+  function clickHandler() {
+    emit('click');
+  }
+</script>
+<style lang="less" scoped>
+  @import '/@/design/theme.less';
+
+  @{theme-deepblue} {
+    .module-content {
+      --image-model_original_title_bg: url('@/assets/images/themify/deepblue/home-container/configurable/model_original_title_bg.png');
+    }
+  }
+
+  .module-content {
+    --image-model_original_title_bg: url('@/assets/images/home-green/green-title-bg.png');
+    --bg-height: 33px;
+    color: #fff;
+    box-sizing: border-box;
+    position: absolute;
+    width: 100%;
+    height: 100%;
+  
+  }
+
+  .module-content__title__expand {
+    width: 100%;
+    height: var(--bg-height);
+    background: var(--image-model_original_title_bg) no-repeat;
+    background-size: 100% 100%;
+    position: relative;
+    text-align: center;
+    line-height: var(--bg-height);
+  }
+
+  // .module-content__title {
+  //   width: 50%;
+  //   height: var(--bg-height);
+  //   background: url('@/assets/images/home-container/configurable/model_left_title_bg.png') no-repeat;
+  //   background-size: 100% 100%;
+  //   position: relative;
+  //   text-align: right;
+  //   padding: 4px 10% 0 0;
+  // }
+
+  // 固定在父容器右上角的按钮图标
+  // .action-btn {
+  //   width: 18px;
+  //   height: 18px;
+  //   background: url('@/assets/images/home-container/configurable/expand.svg') no-repeat center;
+  //   position: absolute;
+  //   right: 0;
+  //   top: 0;
+  // }
+  // .close-btn {
+  //   transform: rotate(-90deg);
+  // }
+
+  .module-slot {
+    height: calc(100% - 33px);
+    width: calc(100% - 20px);
+    backdrop-filter: blur(5px);
+    // #182d47
+    // background-color: var(--vent-configurable-original-module-bg);
+    margin-left: 10px;
+  }
+
+  // Transition动画相关
+  .v-enter-active,
+  .v-leave-active {
+    transition: all 0.3s ease;
+  }
+
+  .v-enter-from,
+  .v-leave-to {
+    // opacity: 1;
+    transform: translateX(-100%);
+    // transform: scaleY(0);
+    // transform-origin: center top;
+  }
+</style>

+ 16 - 15
src/views/vent/home/configurable/configurable.data.ts

@@ -79,9 +79,9 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=fanmain',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:60px;left:0;',
+      position: 'top:15px;left:15px;',
     },
   },
   {
@@ -139,9 +139,9 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=fanlocal',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:350px;left:0;',
+      position: 'top:295px;left:15px;',
     },
   },
   {
@@ -191,11 +191,12 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=gate_xinJianFuXieJin',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:640px;left:0;',
+      position: 'top:575px;left:15px;',
     },
   },
+   
   {
     deviceType: 'sys_wind',
     moduleName: '风量监测',
@@ -259,9 +260,9 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=windrect',
     },
     showStyle: {
-      size: 'width:1000px;height:280px;',
+      size: 'width:460px;height:260px;',
       version: '原版',
-      position: 'top:640px;left:460px;',
+      position: 'bottom:15px;left:15px;',
     },
   },
   {
@@ -339,9 +340,9 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=sys_majorpath&deviceid=${deviceID}',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:460px;height:260px;',
       version: '原版',
-      position: 'top:60px;right:0;',
+      position: 'bottom:15px;left:495px;',
     },
   },
   {
@@ -422,12 +423,12 @@ export const testConfigVent: Config[] = [
       to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=sys_surface_caimei&deviceid=${deviceID}',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:460px;height:260px;',
       version: '原版',
-      position: 'top:350px;right:0;',
+      position: 'bottom:15px;left:975px;',
     },
   },
-  {
+ {
     // deviceType: 'warn',
     // moduleName: '预警监测',
     deviceType: '',
@@ -509,9 +510,9 @@ export const testConfigVent: Config[] = [
       to: '/monitorChannel/monitor-alarm-home',
     },
     showStyle: {
-      size: 'width:470px;height:280px;',
+      size: 'width:390px;height:260px;',
       version: '原版',
-      position: 'top:640px;right:0;',
+      position: 'top:855px;left:15px',
     },
   },
 ];

+ 324 - 0
src/views/vent/home/configurable/vent-Green.vue

@@ -0,0 +1,324 @@
+<!-- eslint-disable vue/multi-word-component-names -->
+<template>
+  <div class="company-home">
+    <div style="width: 100%; height: 100%; position: absolute; left: 0; top: 0; z-index: 0">
+      <VentModal />
+    </div>
+    <!-- 如果是有 deviceType、type 等 query,认为是详情页,不需要展示普通模块,只需要模型 -->
+    <template v-if="!route.query.deviceType">
+      <div v-if="!route.query.embed" class="top-bg">
+        <greenNav :Title="mainTitle"></greenNav>
+      </div>
+
+      <div class="main-container">
+        <div class="left-area">
+          <!-- 采用定位方式以避免出现各个模块隐藏时其他模块下移的问题 -->
+          <template v-if="isOriginal">
+            <ModuleOriginal v-for="cfg in configsLeft" :key="cfg.deviceType" :show-style="cfg.showStyle"
+              :module-data="cfg.moduleData" :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="data"
+              :visible="true" />
+          </template>
+        </div>
+        <div class="bottom-area">
+          <!-- 采用定位方式以避免出现各个模块隐藏时其他模块下移的问题 -->
+          <template v-if="isOriginal">
+            <ModuleOriginal v-for="cfg in configsBottom" :key="cfg.deviceType" :show-style="cfg.showStyle"
+              :module-data="cfg.moduleData" :module-name="cfg.moduleName" :device-type="cfg.deviceType" :data="data"
+              :visible="true" />
+          </template>
+        </div>
+        <div class="right-area">
+          <greenRightTag></greenRightTag>
+        </div>
+
+
+
+        <!-- <div
+        v-if="sysDataType === 'all'"
+        :class="{ 'realtime-mode': isDataRealTime }"
+        alt="切换数据模式"
+        class="switch-button report-mode right-525px"
+        @click="switchDataMode"
+      ></div> -->
+        <div class="switch-button icon-goto right-475px" @click="goMicroApp()"></div>
+      </div>
+
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch, computed } from 'vue';
+// import { CaretDownOutlined } from '@ant-design/icons-vue';
+// import MonitorBar from './components/MonitorBar.vue';
+import { useInitConfigs, useInitPage } from './hooks/useInit';
+import ModuleOriginal from './components/ModuleOriginal-green.vue';
+import greenNav from './components/green-nav.vue'
+import greenRightTag from './components/green-right-tag.vue'
+// import { useRoute } from 'vue-router';
+import VentModal from '/@/components/vent/micro/ventModal.vue';
+import { list } from './configurable.api';
+import { useRoute, useRouter } from 'vue-router';
+import { useGlobSetting } from '/@/hooks/setting';
+import { testConfigVent, testConfigVentRealtime } from './configurable.data';
+
+const { sysDataType = 'monitor', title = '智能通风管控系统' } = useGlobSetting();
+//   const { configs, isOriginal, isCommon, fetchConfigs } = useInitConfigs();
+const { isOriginal, isCommon, fetchConfigs } = useInitConfigs();
+const { mainTitle, enhancedConfigs, hiddenList, data, updateData, updateEnhancedConfigs } = useInitPage(title);
+const route = useRoute();
+const router = useRouter();
+const isDataRealTime = ref(sysDataType === 'monitor');
+// const showBar = ref(true);
+let interval: number | undefined;
+let configs = ref<any[]>([])
+// function switchDataMode() {
+//   isDataRealTime.value = !isDataRealTime.value;
+//   refresh();
+// }
+let configsLeft = computed(() => {
+  return configs.value.filter(v => v.showStyle.position.includes('top'))
+})
+let configsBottom = computed(() => {
+  return configs.value.filter(v => v.showStyle.position.includes('bottom'))
+})
+function refresh() {
+  fetchConfigs(isDataRealTime.value ? 'vent_realtime' : 'vent').then(() => {
+    configs.value = isDataRealTime.value ? testConfigVentRealtime : testConfigVent;
+    updateEnhancedConfigs(configs.value);
+
+    // 测风装置	windrect
+    // 自动风窗	window
+    // 自动风门	gate
+    // 传感器	modelsensor
+    // 局部通风机	fanlocal
+    // 主通风机	fanmain
+    // 密闭	obfurage
+    // 安全监控	safetymonitor
+    // 光纤测温	fiber
+    // 束管监测	bundletube
+    // 制氮	nitrogen
+    // 制浆	pulping
+    // 喷淋	spray
+    // 喷粉	dustdev
+    // 喷雾设备	atomizing
+    // 除尘风机	dedustefan
+    // 粉尘传感器	dustsensor
+    // 转载点	transferpoint
+    // 瓦斯抽采泵	pump
+    // 粉尘	dusting
+    // 瓦斯监测	gasmonitor
+    // 球阀	ballvalve
+    // 压风机	forcFan
+    // 瓦斯巡检	gaspatrol
+    // 防火门	firedoor
+    // 隔爆设施	explosionProof
+    // 瓦斯管道阀门	gasvalve
+    list({
+      types: configs.value
+        .filter((e) => e.deviceType)
+        .map((e) => e.deviceType)
+        .join(','),
+    }).then(updateData);
+  });
+}
+
+function initInterval() {
+  setInterval(() => {
+    list({
+      types: configs.value
+        .filter((e) => e.deviceType)
+        .map((e) => e.deviceType)
+        .join(','),
+    }).then(updateData);
+  }, 60000);
+}
+
+function goMicroApp() {
+  router.push({
+    path: route.path,
+    query: {
+      ...route.query,
+      type: 'model3D',
+      deviceType: 'model3D',
+    },
+  });
+}
+
+watch(
+  () => route.query,
+  () => {
+    if (route.query.deviceType) {
+      // 仅需要展示子应用,模拟 unmounted
+      clearInterval(interval);
+    } else {
+      // 模拟 mounted
+      refresh();
+      initInterval();
+    }
+  }
+);
+
+onMounted(() => {
+  refresh();
+  initInterval();
+});
+
+onUnmounted(() => {
+  clearInterval(interval);
+});
+</script>
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+@font-face {
+  font-family: 'douyuFont';
+  src: url('/@/assets/font/douyuFont.otf');
+}
+
+@{theme-deepblue} {
+  .company-home {
+    --image-modal-top: url('/@/assets/images/themify/deepblue/vent/home/modal-top.png');
+  }
+}
+
+.company-home {
+  --image-modal-top: url('/@/assets/images/vent/home/modal-top.png');
+  --image-monitor-realtime: url('/@/assets/images/company/monitor-realtime.png');
+  --image-monitor-doc: url('/@/assets/images/company/monitor-doc.png');
+  --image-monitor-goto: url('/@/assets/images/company/monitor-goto.png');
+
+  width: 100%;
+  height: 100%;
+  color: @white;
+  position: relative;
+  background: #181b24;
+
+  .top-bg {
+    width: 100%;
+    height: 96px;
+    // background: var(--image-modal-top) no-repeat center;
+    position: absolute;
+    z-index: 1;
+  }
+
+  .main-container {
+    position: absolute;
+    top: 96px;
+    width: calc(100% - 30px);
+    height: calc(100% - 96px);
+    margin: 0px 15px;
+    box-sizing: border-box;
+
+    .left-area {
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 420px;
+      height: 100%;
+      padding: 15px;
+      background: url('../../../../assets/images/home-green/green-bd-left.png') no-repeat;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+      overflow-y: auto
+    }
+
+    .bottom-area {
+      position: absolute;
+      left: 435px;
+      bottom: 0;
+      width: calc(100% - 435px);
+      height: 290px;
+      padding: 15px;
+      background: url('../../../../assets/images/home-green/green-bd-bottom.png') no-repeat;
+      background-size: 100% 100%;
+      box-sizing: border-box;
+    }
+    .right-area{
+      position:absolute;
+      right:0px;
+      top:0px;
+      width:120px;
+      height:calc(100% - 305px);
+    }
+  }
+
+  // .module-left {
+  //   position: absolute;
+  //   width: 450px;
+  //   height: 280px;
+  //   left: 0;
+  // }
+  // .module-right {
+  //   position: absolute;
+  //   width: 450px;
+  //   height: 280px;
+  //   right: 0;
+  // }
+  // .module-bottom {
+  //   position: absolute;
+  //   width: 1000px;
+  //   height: 280px;
+  // }
+  .module-dropdown {
+    padding: 5px;
+    background-image: @vent-configurable-dropdown;
+    border-bottom: 2px solid @vent-configurable-home-light-border;
+    color: @vent-font-color;
+    position: absolute;
+    top: 60px;
+    right: 480px;
+  }
+
+  .module-dropdown-original {
+    padding: 10px;
+    background-image: @vent-configurable-dropdown;
+    border-bottom: 2px solid @vent-configurable-home-light-border;
+    color: @vent-font-color;
+    position: absolute;
+    top: 70px;
+    right: 460px;
+  }
+
+  .module-trigger-button {
+    color: @vent-font-color;
+    background-image: @vent-configurable-dropdown;
+    border: none;
+    border-bottom: 2px solid @vent-configurable-home-light-border;
+  }
+
+  .switch-button {
+    width: 34px;
+    height: 34px;
+    position: absolute;
+    // right: 5px;
+    bottom: 300px;
+    z-index: 5;
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+  }
+
+  .report-mode {
+    background-image: var(--image-monitor-doc);
+  }
+
+  .realtime-mode {
+    background-image: var(--image-monitor-realtime);
+  }
+
+  .icon-goto {
+    background-image: var(--image-monitor-goto);
+  }
+
+  .module-monitor-bar {
+    position: absolute;
+    top: 100px;
+    width: 1000px;
+    height: 200px;
+    left: calc(50% - 500px);
+  }
+}
+
+:deep(.loading-box) {
+  position: unset;
+}
+</style>

+ 12 - 0
src/views/vent/safetyList/common/detail.vue

@@ -368,6 +368,10 @@
                 <a-input v-model:value="formEdit.sgzjbcmm" placeholder="请输入" style="width: 260px; margin-right: 10px" />
                 <a-button class="down-btn" type="primary" @click="handleClick('四个字节保存密码')">下发</a-button>
               </a-form-item>
+              <a-form-item label="复位:">
+                <a-input v-model:value="formEdit.fw" placeholder="请输入" style="width: 260px; margin-right: 10px" />
+                <a-button class="down-btn" type="primary" @click="handleClick('复位')">下发</a-button>
+              </a-form-item>
             </a-form>
           </a-modal>
         </div>
@@ -463,6 +467,7 @@ let formEdit = reactive({
   bjsn: '',
   dyl485btl: '',
   sgzjbcmm: '',
+  fw: '',
 });
 let formView = reactive({
   dylfsfx: '',
@@ -654,6 +659,7 @@ function cancenModal() {
   formEdit.bjsn = ''
   formEdit.dyl485btl = ''
   formEdit.sgzjbcmm = ''
+  formEdit.fw = ''
 
   formView.dylfsfx = ''
   formView.dylbjzt = ''
@@ -832,6 +838,12 @@ async function handleClick(data) {
       cancenModal()
       getStationList();
       break;
+    case '复位':
+      await set158StationData({ stationId: devStationId.value, deviceId: formEdit.cgq, plcCode: 'fw', value: formEdit.fw });
+      visibleModalEdit1.value = false;
+      cancenModal()
+      getStationList();
+      break;
   }
 }