소스 검색

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

lxh 10 달 전
부모
커밋
a86983eeb6
24개의 변경된 파일1294개의 추가작업 그리고 627개의 파일을 삭제
  1. BIN
      public/model/glft/ztfj/ztfj-xj_2024-06-20.glb
  2. 0 1
      src/components/chart/LineMulti.vue
  3. 0 1
      src/layouts/default/sider/bottomSideder.vue
  4. 0 1
      src/router/guard/permissionGuard.ts
  5. 1 0
      src/utils/threejs/main.worker.ts
  6. 50 28
      src/views/vent/deviceManager/configurationTable/adapters.ts
  7. 9 2
      src/views/vent/deviceManager/configurationTable/configuration.api.ts
  8. 32 19
      src/views/vent/deviceManager/configurationTable/configuration.data.ts
  9. 16 1
      src/views/vent/deviceManager/configurationTable/index.vue
  10. 34 62
      src/views/vent/home/configurable/components/AirVolumeMonitor.vue
  11. 23 24
      src/views/vent/home/configurable/components/CostumeHeader.vue
  12. 0 15
      src/views/vent/home/configurable/components/DeviceWarning.vue
  13. 38 64
      src/views/vent/home/configurable/components/MonitorCenter.vue
  14. 8 42
      src/views/vent/home/configurable/components/SubVentilate.vue
  15. 8 42
      src/views/vent/home/configurable/components/Ventilate.vue
  16. 45 58
      src/views/vent/home/configurable/components/VentilateAnalysis.vue
  17. 8 42
      src/views/vent/home/configurable/components/VentilateControl.vue
  18. 53 61
      src/views/vent/home/configurable/components/WorkSurface.vue
  19. 58 0
      src/views/vent/home/configurable/hooks/useInit.ts
  20. 2 1
      src/views/vent/home/configurable/index.vue
  21. 2 0
      src/views/vent/monitorManager/fanLocalMonitor/index.vue
  22. 4 2
      src/views/vent/monitorManager/mainFanMonitor/index.vue
  23. 185 161
      src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts
  24. 718 0
      src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts

BIN
public/model/glft/ztfj/ztfj-xj_2024-06-20.glb


+ 0 - 1
src/components/chart/LineMulti.vue

@@ -103,7 +103,6 @@
         if (props.option) {
           Object.assign(option, props.option);
         }
-        console.log(props.chartData);
 
         //图例类型
         // let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));

+ 0 - 1
src/layouts/default/sider/bottomSideder.vue

@@ -88,7 +88,6 @@
       }
 
       async function handleMenuClick(path: Menu) {
-        debugger;
         if (path.path == currentRoute.value.fullPath) {
           return;
         }

+ 0 - 1
src/router/guard/permissionGuard.ts

@@ -41,7 +41,6 @@ export function createPermissionGuard(router: Router) {
   const permissionStore = usePermissionStoreWithOut();
 
   router.beforeEach(async (to, from, next) => {
-    debugger;
     RootRoute.redirect = glob.homePath || PageEnum.BASE_HOME;
 
     if (_.isEmpty(history.state.current)) {

+ 1 - 0
src/utils/threejs/main.worker.ts

@@ -54,6 +54,7 @@ export function initModalWorker() {
     'ztfj/fbm_2023-06-02.glb',
     'ztfj/ztfj-fc_2023-06-02.glb',
     'ztfj/ztfj_2023-12-12.glb',
+    'ztfj/ztfj-xj_2024-06-20.glb',
     'fire/laneway_2024-03-04.glb',
     'fire/laneway-device_2024-03-19.glb',
     'fire/chamber_2023-06-02.glb',

+ 50 - 28
src/views/vent/deviceManager/configurationTable/adapters.ts

@@ -1,7 +1,12 @@
 import _ from 'lodash-es';
 
-/** 将 formData 格式化为 api 需要的格式 */
-export function parseFormDataToParams(formData: Record<string, number | string | undefined>) {
+export interface ModuleData {
+  list?: Record<string, string>;
+  chart?: Record<string, string>;
+}
+
+/** 将原本的 formData 格式化为 api.saveOrUpdate 需要的格式 */
+export function parseFormDataToParams(formData: Record<string, number | string | undefined>): ModuleData {
   const params = {};
   _.forEach(formData, (v: string | undefined, k) => {
     // 如果是以 moduleData 打头的数据要特殊处理,因为这是配置的主要项目,表单配置见 ./configuration.data
@@ -15,31 +20,48 @@ export function parseFormDataToParams(formData: Record<string, number | string |
   return params;
 }
 
-/** 将 api 返回的 moduleData 格式化为可用的对象,如果传入了 data,那么会返回带数据的配置,否则返回默认配置 */
-export function parseModuleDataToObject(moduleData: string): { chart: { label: string; prop: string }[]; list: { label: string; prop: string }[] };
-export function parseModuleDataToObject(
-  moduleData: string,
-  data: any
-): { chart: { label: string; value: string }[]; list: { label: string; value: string }[] };
+/** 将 api.list 返回的 moduleData 格式化,格式化之后可以支持对应的表单以用,该方法会修改源数据 */
+export function parseModuleData(listData: { moduleData: ModuleData }) {
+  _.forEach(listData.moduleData, (v, k) => {
+    listData[`moduleData.${k}`] = JSON.stringify(v);
+  });
 
-export function parseModuleDataToObject(moduleData: string, data?: any) {
-  const raw = JSON.parse(moduleData);
-  if (data) {
-    return {
-      chart: _.map(_.get(raw, 'chart', []), (label, prop) => {
-        return { label, value: _.get(data, prop, '/') };
-      }),
-      list: _.map(_.get(raw, 'list', []), (label, prop) => {
-        return { label, value: _.get(data, prop, '/') };
-      }),
-    };
-  }
-  return {
-    chart: _.map(_.get(raw, 'chart', []), (label, prop) => {
-      return { label, prop };
-    }),
-    list: _.map(_.get(raw, 'list', []), (label, prop) => {
-      return { label, prop };
-    }),
-  };
+  return listData;
 }
+
+// export function parseModuleDataToConfig(moduleData: ModuleData): {
+//   chart: { label: string; prop: string }[];
+//   list: { label: string; prop: string }[];
+// };
+// export function parseModuleDataToConfig(
+//   moduleData: ModuleData,
+//   data: any
+// ): {
+//   chart: { label: string; value: string }[];
+//   list: { label: string; value: string }[];
+// };
+
+/** 将 api.list 返回的 moduleData 格式化为配置对象,如果传入了 data,那么会返回带数据的配置,否则返回默认配置,这些配置通常可用于页面展示 */
+// export function parseModuleDataToConfig(moduleData: ModuleData): {
+//   chart: { label: string; prop: string }[];
+//   list: { label: string; prop: string }[];
+// } {
+//   // if (data) {
+//   //   return {
+//   //     chart: _.map(_.get(moduleData, 'chart', []), (label, prop) => {
+//   //       return { label, value: _.get(data, prop, '/') };
+//   //     }),
+//   //     list: _.map(_.get(moduleData, 'list', []), (label, prop) => {
+//   //       return { label, value: _.get(data, prop, '/') };
+//   //     }),
+//   //   };
+//   // }
+//   return {
+//     chart: _.map(_.get(moduleData, 'chart', []), (label, prop) => {
+//       return { label, prop };
+//     }),
+//     list: _.map(_.get(moduleData, 'list', []), (label, prop) => {
+//       return { label, prop };
+//     }),
+//   };
+// }

+ 9 - 2
src/views/vent/deviceManager/configurationTable/configuration.api.ts

@@ -1,4 +1,4 @@
-import { parseFormDataToParams } from './adapters';
+import { parseFormDataToParams, parseModuleData } from './adapters';
 import { defHttp } from '/@/utils/http/axios';
 
 enum Api {
@@ -12,7 +12,14 @@ enum Api {
  * 列表接口
  * @param params
  */
-export const list = (params) => defHttp.post({ url: Api.list, params });
+export const list = (params) =>
+  defHttp.post({ url: Api.list, params }).then((result) => {
+    result.records.forEach((item) => {
+      parseModuleData(item);
+    });
+
+    return result;
+  });
 
 /**
  * 删除配置项

+ 32 - 19
src/views/vent/deviceManager/configurationTable/configuration.data.ts

@@ -1,9 +1,10 @@
 import { BasicColumn } from '/@/components/Table';
 import { FormSchema } from '/@/components/Table';
+import _ from 'lodash-es';
 
 export const columns: BasicColumn[] = [
   {
-    title: '所属模块',
+    title: '所属页面',
     dataIndex: 'pageType',
   },
   {
@@ -11,20 +12,32 @@ export const columns: BasicColumn[] = [
     dataIndex: 'deviceType',
   },
   {
-    title: '所展示点位及名称',
-    dataIndex: 'moduleData',
-    format: (ctx: string) => {
-      try {
-        const json = JSON.parse(ctx);
-        return Object.keys(json)
-          .map((k) => {
-            return `点位:${k};名称:${json[k]}`;
-          })
-          .join('\n');
-      } catch (e) {
-        return '渲染错误';
-      }
-    },
+    title: '主要内容配置',
+    dataIndex: 'moduleData_list',
+    // format: (ctx: any) => {
+    //   try {
+    //     if (!ctx) return '/';
+    //     return _.map(ctx.list, (v, k) => {
+    //       return `点位:${k};名称:${v}`;
+    //     }).join('\n');
+    //   } catch (e) {
+    //     return '渲染错误';
+    //   }
+    // },
+  },
+  {
+    title: '图表内容配置',
+    dataIndex: 'moduleData_chart',
+    //   format: (ctx: any) => {
+    //     try {
+    //       if (!ctx) return '/';
+    //       return _.map(ctx.chart, (v, k) => {
+    //         return `点位:${k};名称:${v}`;
+    //       }).join('<br>');
+    //     } catch (e) {
+    //       return '渲染错误';
+    //     }
+    //   },
   },
 ];
 
@@ -40,12 +53,12 @@ export const searchFormSchema: FormSchema[] = [
     colProps: { span: 6 },
   },
   {
-    label: '页面类型',
+    label: '所属页面',
     field: 'pageType',
     component: 'JDictSelectTag',
     componentProps: {
       dictCode: 'configurable_homepage',
-      placeholder: '请选择页面类型',
+      placeholder: '请选择所属页面',
     },
     colProps: { span: 6 },
   },
@@ -69,13 +82,13 @@ export const formSchema: FormSchema[] = [
     },
   },
   {
-    label: '页面类型',
+    label: '所属页面',
     field: 'pageType',
     component: 'JDictSelectTag',
     required: true,
     componentProps: {
       dictCode: 'configurable_homepage',
-      placeholder: '请选择页面类型',
+      placeholder: '请选择所属页面',
     },
   },
   {

+ 16 - 1
src/views/vent/deviceManager/configurationTable/index.vue

@@ -11,7 +11,22 @@
       title="配置列表"
       :showTab="true"
       :deviceType="deviceType"
-    />
+    >
+      <template #filterCell="{ column, record }">
+        <template v-if="column.key === 'moduleData_list'">
+          <div v-for="(val, key) in record.moduleData.list" :key="key">
+            <span>点位:{{ key }};</span>
+            <span>名称:{{ val }};</span>
+          </div>
+        </template>
+        <template v-if="column.key === 'moduleData_chart'">
+          <div v-for="(val, key) in record.moduleData.chart" :key="key">
+            <span>点位:{{ key }};</span>
+            <span>名称:{{ val }};</span>
+          </div>
+        </template>
+      </template>
+    </NormalTable>
   </div>
 </template>
 

+ 34 - 62
src/views/vent/home/configurable/components/AirVolumeMonitor.vue

@@ -8,78 +8,54 @@
       </div>
     </div>
   </CostumeHeader> -->
-  <PictorialBar
-    :option="chartOption"
-    series-prop-type="valueA"
-    x-axis-prop-type="x"
-    :chart-data="[
-      { valueA: 1, valueB: 1, x: 2 },
-      { valueA: 1, valueB: 1, x: 3 },
-      { valueA: 1, valueB: 1, x: 4 },
-      { valueA: 1, valueB: 1, x: 5 },
-      { valueA: 2, valueB: 2, x: 6 },
-      { valueA: 3, valueB: 1, x: 7 },
-      { valueA: 1, valueB: 1, x: 8 },
-    ]"
-    height="100%"
-  />
+  <PictorialBar :option="chartOption" :series-prop-type="seriesPropType" x-axis-prop-type="x" :chart-data="chartData" height="100%" />
   <!-- <div class="flex justify-around mt-10px">
     <MiniBoard v-for="item in configs" :key="item.prop" :label="item.label" :value="selectedDevice[item.prop]" />
   </div> -->
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
-  import CostumeHeader from './CostumeHeader.vue';
-  import { RightCircleOutlined } from '@ant-design/icons-vue';
+  import { computed, onMounted, ref } from 'vue';
   import PictorialBar from '/@/components/chart/PictorialBar.vue';
   import { EChartsOption, graphic } from 'echarts';
-  // import MiniBoard from './MiniBoard.vue';
+  import { useInitConfig } from '../hooks/useInit';
+  import _ from 'lodash-es';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
   const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
+  const { configs, fetchConfig } = useInitConfig(devicekind);
 
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
-      });
-    });
-  }
+  onMounted(() => {
+    fetchConfig();
+  });
+
+  // 适用于 PictorialBar 的 prop,即从配置中选取第一项作取值依赖
+  const seriesPropType = computed(() => {
+    if (configs.value.chart) {
+      return Object.keys(configs.value.chart)[0];
+    } else {
+      return 'y';
+    }
+  });
+
+  const chartData = ref<any[]>([
+    { valueA: 1, y: 1, x: 2 },
+    { valueA: 1, y: 1, x: 3 },
+    { valueA: 1, y: 1, x: 4 },
+    { valueA: 1, y: 1, x: 5 },
+    { valueA: 2, y: 2, x: 6 },
+    { valueA: 3, y: 1, x: 7 },
+    { valueA: 1, y: 1, x: 8 },
+  ]);
+
+  function fetchChartData() {}
 
-  // 图表相关
+  onMounted(() => {
+    fetchChartData();
+  });
+
+  // 图表的配置项
   const chartOption: EChartsOption = {
     grid: {
       top: 50,
@@ -129,9 +105,5 @@
       color: '#fff',
     },
   };
-
-  onMounted(() => {
-    fetchConfig();
-  });
 </script>
 <style scoped></style>

+ 23 - 24
src/views/vent/home/configurable/components/CostumeHeader.vue

@@ -12,8 +12,8 @@
           <CaretDownOutlined class="w-30px" v-else />
         </div>
         <template #overlay>
-          <Menu :selected-keys="[selectedKey]" @click="selectHandler">
-            <MenuItem v-for="item in options" :key="item.key" :title="item.label">
+          <Menu :selected-keys="[value]" @click="selectHandler">
+            <MenuItem v-for="item in options" :key="item.value" :title="item.label">
               {{ item.label }}
             </MenuItem>
           </Menu>
@@ -24,39 +24,38 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
+  import { computed, ref } from 'vue';
   import { Dropdown, Menu, MenuItem } from 'ant-design-vue';
   import { SwapOutlined, CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons-vue';
 
-  const props = defineProps<{
-    api?: () => Promise<{ label: any; key: any }[]>;
-  }>();
-  const emit = defineEmits(['change']);
+  const props = withDefaults(
+    defineProps<{
+      options: { label: any; value: any }[];
+      value: string;
+    }>(),
+    {
+      options: () => [],
+      value: '',
+    }
+  );
+  const emit = defineEmits(['change', 'update:value']);
 
   // 下拉框是否可见
   const visible = ref(false);
   // 选中的选项
-  const selectedKey = ref('');
-  const selectedLabel = ref('');
-  const options = ref<{ label: any; key: any }[]>([]);
+  const selectedLabel = computed(() => {
+    const res = props.options.find((opt) => {
+      return opt.value === props.value;
+    });
+
+    return res ? res.label : '/';
+  });
 
   // 选择了某一项
-  function selectHandler({ key, item }) {
-    selectedKey.value = key;
-    selectedLabel.value = item.title;
+  function selectHandler({ key }) {
+    emit('update:value', key);
     emit('change', key);
   }
-
-  onMounted(() => {
-    // 获取数据
-    if (!props.api) return;
-    props.api().then((opts) => {
-      options.value = opts;
-      selectHandler({ key: opts[0]?.key, item: { title: opts[0]?.label } });
-      // selectedKey.value = opts[0]?.key;
-      // selectedLabel.value = opts[0]?.label;
-    });
-  });
 </script>
 <style scoped>
   .costume-header__header {

+ 0 - 15
src/views/vent/home/configurable/components/DeviceWarning.vue

@@ -77,21 +77,6 @@
       color: 'blue',
     },
   ]);
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  // function fetchOptions() {
-  //   return list({
-  //     devicekind,
-  //   }).then(({ records }) => {
-  //     devices.value = records;
-  //     selectDeviceByID(records[0]?.id);
-  //     return records.map((e) => {
-  //       return {
-  //         label: e.strinstallpos,
-  //         key: e.id,
-  //       };
-  //     });
-  //   });
-  // }
 
   onMounted(() => {
     // fetchConfig();

+ 38 - 64
src/views/vent/home/configurable/components/MonitorCenter.vue

@@ -3,43 +3,31 @@
   <div class="monitor-center pt-10px pl-2px pr-2px pb-10px">
     <div class="flex">
       <div class="monitor-center__item_A">
-        <span>吨煤通风费用(元)</span>
-        <span class="color-green">32</span>
+        <span>巷道总长度(m)</span>
+        <span class="color-green">{{ data.flength }}</span>
       </div>
       <div class="monitor-center__item_B flex flex-items-center">
         <span>吨煤通风费用(元)</span>
-        <span class="color-yellow">32</span>
+        <span class="color-yellow">{{ data.cost }}</span>
       </div>
     </div>
     <div class="flex">
       <div class="ml-7px text-center">
-        <span>总风量</span>
+        <span>总风量(m³/min)</span>
         <div class="number_grid">
-          <span>1</span>
-          <span>2</span>
-          <span>.</span>
-          <span>3</span>
-          <span>4</span>
+          <span v-for="(c, i) in data.m3Str" :key="`vhccmcc-${i}`">{{ c }}</span>
         </div>
       </div>
       <div class="ml-7px text-center">
-        <span>吨煤通风</span>
+        <span>总阻力(pa)</span>
         <div class="number_grid">
-          <span>1</span>
-          <span>2</span>
-          <span>.</span>
-          <span>3</span>
-          <span>4</span>
+          <span v-for="(c, i) in data.paStr" :key="`vhccmcc-${i}`">{{ c }}</span>
         </div>
       </div>
       <div class="ml-7px text-center">
-        <span>吨煤通风</span>
+        <span>等积孔(m³)</span>
         <div class="number_grid">
-          <span>1</span>
-          <span>2</span>
-          <span>.</span>
-          <span>3</span>
-          <span>4</span>
+          <span v-for="(c, i) in data.eStr" :key="`vhccmcc-${i}`">{{ c }}</span>
         </div>
       </div>
     </div>
@@ -53,60 +41,23 @@
 </template>
 <script lang="ts" setup>
   import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
   import Chart from '/@/components/chart/Chart.vue';
   import { EChartsOption } from 'echarts';
+  import _ from 'lodash-es';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
-  const devicekind = 'fanlocal';
+  // const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
-
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
-      });
-    });
-  }
+  const data = ref<Record<string, any>>({});
 
   // 图表相关
-  const chartOptions = [
+  const chartOptions = ref([
     { title: '外部漏风率', option: getChartOption(40) },
     { title: '有效风量率', option: getChartOption(30) },
     { title: '灵敏度A', option: getChartOption(20) },
     { title: '灵敏度B', option: getChartOption(50) },
-  ];
+  ]);
 
   function getChartOption(percent: number): EChartsOption {
     const data = [
@@ -226,8 +177,31 @@
     };
   }
 
+  function fetchData() {
+    Promise.resolve({
+      flength: 222,
+      m3: 33333,
+      pa: 44444,
+      e: 55555,
+      cost: 111,
+      p1: 40,
+      p2: 20,
+      p3: 30,
+      p4: 10,
+    }).then((r: any) => {
+      data.value = r;
+      const reference = [r.p1, r.p2, r.p3, r.p4];
+      r.eStr = _.pad(r.e.toString(), 5, '0');
+      r.m3Str = _.pad(r.m3.toString(), 5, '0');
+      r.paStr = _.pad(r.pa.toString(), 5, '0');
+      chartOptions.value.forEach((item, i) => {
+        item.option = getChartOption(reference[i]);
+      });
+    });
+  }
+
   onMounted(() => {
-    fetchConfig();
+    fetchData();
   });
 </script>
 <style lang="less" scoped>

+ 8 - 42
src/views/vent/home/configurable/components/SubVentilate.vue

@@ -1,5 +1,5 @@
 <template>
-  <CostumeHeader :api="fetchOptions" @change="selectDeviceByID">
+  <CostumeHeader v-model:value="selectedDeviceID" :options="options">
     <div class="w-200px flex flex-items-center">
       <RightCircleOutlined class="w-30px" />
       <div class="flex-grow-1">
@@ -8,61 +8,27 @@
     </div>
   </CostumeHeader>
   <div class="flex justify-around mt-10px">
-    <MiniBoard v-for="item in configs" :key="item.prop" :label="item.label" :value="selectedDevice[item.prop]" type="A" />
+    <MiniBoard v-for="(label, prop) in configs.list" :key="`vhccsv-${prop}`" :label="label" :value="get(selectedDevice, prop)" type="A" />
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
+  import { onMounted } from 'vue';
   import CostumeHeader from './CostumeHeader.vue';
   import { RightCircleOutlined } from '@ant-design/icons-vue';
   import MiniBoard from './MiniBoard.vue';
+  import { useInitConfig, useInitDevices } from '../hooks/useInit';
+  import { get } from '../../billboard/utils';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
   const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
-
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
-      });
-    });
-  }
+  const { configs, fetchConfig } = useInitConfig(devicekind);
+  const { options, selectedDevice, selectedDeviceID, fetchDevices } = useInitDevices(devicekind);
 
   onMounted(() => {
     fetchConfig();
+    fetchDevices();
   });
 </script>
 <style scoped></style>

+ 8 - 42
src/views/vent/home/configurable/components/Ventilate.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
-  <CostumeHeader :api="fetchOptions" @change="selectDeviceByID">
+  <CostumeHeader v-model:value="selectedDeviceID" :options="options">
     <div class="w-200px flex flex-items-center">
       <RightCircleOutlined class="w-30px" />
       <div class="flex-grow-1">
@@ -9,61 +9,27 @@
     </div>
   </CostumeHeader>
   <div class="flex justify-around mt-10px">
-    <MiniBoard v-for="item in configs" :key="item.prop" :label="item.label" :value="selectedDevice[item.prop]" type="C" />
+    <MiniBoard v-for="(label, prop) in configs.list" :key="`vhccv-${prop}`" :label="label" :value="get(selectedDevice, prop)" type="C" />
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
+  import { onMounted } from 'vue';
   import CostumeHeader from './CostumeHeader.vue';
   import { RightCircleOutlined } from '@ant-design/icons-vue';
   import MiniBoard from './MiniBoard.vue';
+  import { useInitConfig, useInitDevices } from '../hooks/useInit';
+  import { get } from '../../billboard/utils';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
   const devicekind = 'fanmain';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
-
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
-      });
-    });
-  }
+  const { configs, fetchConfig } = useInitConfig(devicekind);
+  const { options, selectedDevice, selectedDeviceID, fetchDevices } = useInitDevices(devicekind);
 
   onMounted(() => {
     fetchConfig();
+    fetchDevices();
   });
 </script>
 <style scoped></style>

+ 45 - 58
src/views/vent/home/configurable/components/VentilateAnalysis.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
-  <CostumeHeader :api="fetchOptions" @change="selectDeviceByID">
+  <CostumeHeader v-model:value="selectedDeviceID" :options="options">
     <!-- <div class="w-200px flex flex-items-center">
       <RightCircleOutlined class="w-30px" />
       <div class="flex-grow-1">
@@ -8,77 +8,68 @@
       </div>
     </div> -->
   </CostumeHeader>
-  <Pie
-    :option="chartOption"
-    :chart-data="[
-      {
-        value: 70,
-        name: 'A',
-      },
-      {
-        value: 30,
-        name: 'B',
-      },
-    ]"
-    height="140px"
-  />
+  <Pie :option="chartOption" :chart-data="chartData" height="140px" />
   <div class="flex justify-around mt-10px">
-    <MiniBoard v-for="item in configs" :key="item.prop" :label="item.label" :value="selectedDevice[item.prop]" layout="label-top" type="D" />
+    <MiniBoard
+      v-for="(label, prop) in configs.list"
+      :key="`vhccva-${prop}`"
+      :label="label"
+      :value="get(selectedDevice, prop)"
+      layout="label-top"
+      type="D"
+    />
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
   import CostumeHeader from './CostumeHeader.vue';
-  import { RightCircleOutlined } from '@ant-design/icons-vue';
-  import MiniBoard from './MiniBoard.vue';
+  import { onMounted, ref } from 'vue';
   import Pie from '/@/components/chart/Pie.vue';
   import { EChartsOption } from 'echarts';
+  import { useInitConfig, useInitDevices } from '../hooks/useInit';
+  import { get } from '../../billboard/utils';
+  import _ from 'lodash-es';
+  import MiniBoard from './MiniBoard.vue';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
-  const devicekind = 'windrect';
+  const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
+  const { configs, fetchConfig } = useInitConfig(devicekind);
+  const { options, selectedDevice, selectedDeviceID, fetchDevices } = useInitDevices(devicekind);
 
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
+  onMounted(() => {
+    fetchConfig();
+    fetchDevices();
+  });
+
+  const chartData = ref<any[]>([
+    {
+      value: 70,
+      name: 'A',
+    },
+    {
+      value: 30,
+      name: 'B',
+    },
+  ]);
+
+  function fetchChartData() {
+    return;
+    Promise.resolve({ a: 3, b: 7 }).then((res) => {
+      chartData.value = _.map(configs.value.chart || [], (label, prop) => {
         return {
-          label: e.strinstallpos,
-          key: e.id,
+          value: _.get(res, prop),
+          name: label,
         };
       });
     });
   }
 
-  // 图标相关
+  onMounted(() => {
+    fetchChartData();
+  });
+
+  // 图表配置项
   const chartOption: EChartsOption = {
     series: [
       {
@@ -100,9 +91,5 @@
     ],
     color: ['#d9a1ff', '#00d1ff', '#82fe78'],
   };
-
-  onMounted(() => {
-    fetchConfig();
-  });
 </script>
 <style scoped></style>

+ 8 - 42
src/views/vent/home/configurable/components/VentilateControl.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
-  <CostumeHeader :api="fetchOptions" @change="selectDeviceByID">
+  <CostumeHeader v-model:value="selectedDeviceID" :options="options">
     <div class="w-200px flex flex-items-center">
       <RightCircleOutlined class="w-30px" />
       <div class="flex-grow-1">
@@ -13,57 +13,23 @@
   </div> -->
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
+  import { onMounted } from 'vue';
   import CostumeHeader from './CostumeHeader.vue';
   import { RightCircleOutlined } from '@ant-design/icons-vue';
   // import MiniBoard from './MiniBoard.vue';
+  import { useInitDevices } from '../hooks/useInit';
+  // import { get } from '../../billboard/utils';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
   const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
-
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
-      });
-    });
-  }
+  // const { configs, fetchConfig } = useInitConfig(devicekind);
+  const { options, selectedDevice, selectedDeviceID, fetchDevices } = useInitDevices(devicekind);
 
   onMounted(() => {
-    fetchConfig();
+    // fetchConfig();
+    fetchDevices();
   });
 </script>
 <style scoped></style>

+ 53 - 61
src/views/vent/home/configurable/components/WorkSurface.vue

@@ -1,6 +1,6 @@
 <!-- eslint-disable vue/multi-word-component-names -->
 <template>
-  <CostumeHeader :api="fetchOptions" @change="selectDeviceByID">
+  <CostumeHeader v-model:value="selectedDeviceID" :options="options">
     <!-- <div class="w-200px flex flex-items-center">
       <RightCircleOutlined class="w-30px" />
       <div class="flex-grow-1">
@@ -10,79 +10,75 @@
   </CostumeHeader>
   <LineMulti
     :option="chartOption"
-    :prop-type-arr="
-      new Map([
-        ['valueA', '值A'],
-        ['valueB', '值B'],
-      ])
-    "
+    :prop-type-arr="propTypeArr"
     x-axis-prop-type="x"
-    :chart-data="[
-      { valueA: 1, valueB: 3, x: 2 },
-      { valueA: 2, valueB: 1, x: 3 },
-      { valueA: 1, valueB: 1, x: 4 },
-      { valueA: 3, valueB: 2, x: 5 },
-      { valueA: 2, valueB: 1, x: 6 },
-      { valueA: 1, valueB: 2, x: 7 },
-    ]"
+    :chart-data="chartData"
     height="140px"
     class="worksurface-chart"
   />
   <div class="flex justify-around">
-    <MiniBoard v-for="item in configs" :key="item.prop" :label="item.label" :value="selectedDevice[item.prop]" layout="label-top" type="B" />
+    <MiniBoard
+      v-for="(label, prop) in configs.list"
+      :key="`vhccws-${prop}`"
+      :label="label"
+      :value="get(selectedDevice, prop)"
+      layout="label-top"
+      type="B"
+    />
   </div>
 </template>
 <script lang="ts" setup>
-  import { onMounted, ref } from 'vue';
-  import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
-  import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
-  import CostumeHeader from './CostumeHeader.vue';
-  import { RightCircleOutlined } from '@ant-design/icons-vue';
-  import MiniBoard from './MiniBoard.vue';
   import LineMulti from '/@/components/chart/LineMulti.vue';
+  import CostumeHeader from './CostumeHeader.vue';
+  import { computed, onMounted, ref } from 'vue';
   import { EChartsOption } from 'echarts';
+  import { useInitConfig, useInitDevices } from '../hooks/useInit';
+  import { get } from '../../billboard/utils';
+  import _ from 'lodash-es';
+  import MiniBoard from './MiniBoard.vue';
   // import mapComponent from './components/3Dmap/index.vue';
 
   // 设备类别,是个枚举 TODO: 将手动换为自动获取类别
   const devicekind = 'fanlocal';
 
-  const configs = ref<{ prop: string; label: string }[]>([]);
-  function fetchConfig() {
-    cfgList({
-      deviceType: 'devicekind',
-    }).then(({ records }) => {
-      const moduleData = JSON.parse(records[0]?.moduleData);
-      configs.value = Object.keys(moduleData).map((k) => {
-        return {
-          prop: k,
-          label: moduleData[k],
-        };
-      });
-    });
-  }
+  const { configs, fetchConfig } = useInitConfig(devicekind);
+  const { options, selectedDevice, selectedDeviceID, fetchDevices } = useInitDevices(devicekind);
 
-  const devices = ref<any[]>([]);
-  const selectedDevice = ref<any>({});
-  function selectDeviceByID(id: string) {
-    selectedDevice.value = devices.value.find((e) => {
-      return e.id === id;
-    });
-  }
-  // 获取全部局扇的数据,并以选项格式返回给 Header 消费
-  function fetchOptions() {
-    return list({
-      devicekind,
-    }).then(({ records }) => {
-      devices.value = records;
-      selectDeviceByID(records[0]?.id);
-      return records.map((e) => {
-        return {
-          label: e.strinstallpos,
-          key: e.id,
-        };
+  onMounted(() => {
+    fetchConfig();
+    fetchDevices();
+  });
+
+  const chartData = ref<any[]>([
+    { valueA: 1, valueB: 3, x: 2 },
+    { valueA: 2, valueB: 1, x: 3 },
+    { valueA: 1, valueB: 1, x: 4 },
+    { valueA: 3, valueB: 2, x: 5 },
+    { valueA: 2, valueB: 1, x: 6 },
+    { valueA: 1, valueB: 2, x: 7 },
+  ]);
+
+  const propTypeArr = computed(() => {
+    if (configs.value.chart) {
+      const map = new Map();
+      _.forEach(configs.value.chart || [], (label, prop) => {
+        map.set(prop, label);
       });
-    });
-  }
+
+      return map;
+    } else {
+      return new Map([
+        ['valueA', '值A'],
+        ['valueB', '值B'],
+      ]);
+    }
+  });
+
+  function fetchChartData() {}
+
+  onMounted(() => {
+    fetchChartData();
+  });
 
   // 图表相关
   const chartOption: EChartsOption = {
@@ -112,10 +108,6 @@
     backgroundColor: '#081f33',
     // backgroundColor: '#0091ff12',
   };
-
-  onMounted(() => {
-    fetchConfig();
-  });
 </script>
 <style scoped lang="less">
   @import '@/design/vent/color.less';

+ 58 - 0
src/views/vent/home/configurable/hooks/useInit.ts

@@ -0,0 +1,58 @@
+import { computed, ref } from 'vue';
+import { list as cfgList } from '@/views/vent/deviceManager/configurationTable/configuration.api';
+import { list } from '@/views/vent/deviceManager/deviceTable/device.api';
+import { ModuleData } from '@/views/vent/deviceManager/configurationTable/adapters';
+// import mapComponent from './components/3Dmap/index.vue';
+
+export function useInitConfig(deviceType: string) {
+  function fetchConfig() {
+    cfgList({
+      deviceType,
+    }).then(({ records }) => {
+      configs.value = records[0]?.moduleData;
+    });
+  }
+  const configs = ref<ModuleData>({});
+
+  return {
+    fetchConfig,
+    configs,
+  };
+}
+
+export function useInitDevices(devicekind: string) {
+  const devices = ref<Record<string, any>[]>([]);
+  const options = ref<{ label: string; value: string }[]>([]);
+  const selectedDeviceID = ref<string>('');
+
+  const selectedDevice = computed(() => {
+    return (
+      devices.value.find((e) => {
+        return e.id === selectedDeviceID.value;
+      }) || {}
+    );
+  });
+
+  // 获取设备数据,赋值并以选项格式返回给 Header 消费
+  function fetchDevices() {
+    return list({
+      devicekind,
+    }).then(({ records }) => {
+      devices.value = records;
+      selectedDeviceID.value = records[0]?.id;
+      options.value = records.map((e) => {
+        return {
+          label: e.strinstallpos,
+          value: e.id,
+        };
+      });
+    });
+  }
+
+  return {
+    fetchDevices,
+    selectedDevice,
+    selectedDeviceID,
+    options,
+  };
+}

+ 2 - 1
src/views/vent/home/configurable/index.vue

@@ -1,3 +1,4 @@
+<!-- eslint-disable vue/multi-word-component-names -->
 <template>
   <div class="company-home">
     <div class="top-bg">
@@ -37,7 +38,7 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { computed, ref } from 'vue';
+  import { ref } from 'vue';
   import ModuleLeft from './components/moduleLeft.vue';
   import ModuleRight from './components/moduleRight.vue';
   import ModuleBottom from './components/moduleBottom.vue';

+ 2 - 0
src/views/vent/monitorManager/fanLocalMonitor/index.vue

@@ -1222,6 +1222,8 @@
             // m3: selectData.m3 || 675.87,
             m3: 675.87,
             frequency: 30,
+            // m3: 525.87,  //5.0测试数据
+            // frequency: 35,
             // frequency: selectData.Fan1fHz || selectData.Fan1FreqHz || selectData.Fan1Loop_Frequency,
             gasWarningVal: gasWarningVal.value,
             isCompute: false,

+ 4 - 2
src/views/vent/monitorManager/mainFanMonitor/index.vue

@@ -649,12 +649,11 @@
     loading.value = true;
     const baseDataIndex: any = dataSource.value.findIndex((baseData: any) => baseData.deviceID === id);
     selectRowIndex.value = baseDataIndex;
-    const type = 'mainWindRect';
+
     for (const key in selectData) {
       selectData[key] = '';
     }
     nextTick(async () => {
-      await setModelType(type);
       loading.value = false;
       if (!headElHeight.value) {
         const headEl = document.querySelector(`.zxm-table-thead`);
@@ -671,6 +670,9 @@
         selectDevice('dataMonitorRowIndex', 1);
       }
       Object.assign(selectData, data);
+      const type = selectData['modalTyoe'] === 'xiejing' ? 'mainXjWindRect' : 'mainWindRect';
+
+      await setModelType(type);
     });
     await getCamera(id, playerRef.value);
     return;

+ 185 - 161
src/views/vent/monitorManager/mainFanMonitor/main.threejs.ts

@@ -2,6 +2,7 @@ import * as THREE from 'three';
 import { animateCamera } from '/@/utils/threejs/util';
 import UseThree from '../../../../utils/threejs/useThree';
 import mainWindRect from './mainWind.threejs';
+import mainXjWindRect from './mainWind.xj.threejs';
 import useEvent from '../../../../utils/threejs/useEvent';
 
 // import * as dat from 'dat.gui';
@@ -13,7 +14,8 @@ let model: UseThree | undefined, //
   group: THREE.Object3D | undefined,
   bgGroup: THREE.Object3D | undefined,
   mainWindObj: mainWindRect | undefined,
-  modalType = 'mainWind',
+  mainXjWindObj: mainXjWindRect | undefined,
+  modalType = 'mainWindRect',
   explosionVentClose = -1,
   explosionVentOpen = -1;
 
@@ -34,29 +36,6 @@ const addLight = () => {
   pointLight7.position.set(21, 64, 75);
   pointLight7.shadow.bias = -0.05;
   model?.scene?.add(pointLight7);
-
-  // const spotLight = new THREE.SpotLight();
-  // spotLight.angle = Math.PI / 16;
-  // spotLight.penumbra = 0;
-  // // spotLight.castShadow = true;
-  // spotLight.position.set(500, 500, 687);
-  // model?.scene?.add(spotLight);
-
-  // spotLight.shadow.camera.near = 0.5; // default
-  // spotLight.shadow.camera.far = 1000; // default
-  // spotLight.shadow.focus = 1;
-  // spotLight.shadow.bias = -0.000002;
-
-  // gui.add(pointLight7.position, 'x', -1000, 1000);
-  // gui.add(pointLight7.position, 'y', -1000, 1000);
-  // gui.add(pointLight7.position, 'z', -1000, 1000);
-  // gui.add(spotLight.shadow, 'bias', -1, 1);
-  // gui.add(spotLight.shadow, 'focus', -2, 2);
-  // gui.add(pointLight7, 'distance', 0, 1000);
-
-  // gui.add(pointLight6.position, 'x', -500, 500);
-  // gui.add(pointLight6.position, 'y', -500, 500);
-  // gui.add(pointLight6.position, 'z', -500, 500);
 };
 
 // 重置摄像头
@@ -88,8 +67,10 @@ const mouseEvent = (event) => {
   if (event.button == 0) {
     model?.canvasContainer?.addEventListener('mousemove', mousemove);
     mouseDownFn(<UseThree>model, <THREE.Object3D>group, event, (intersects) => {
-      if (modalType === 'mainWindRect') {
-        mainWindObj.mousedownModel.call(mainWindObj, intersects);
+      if (modalType === 'mainWindRect' && mainWindObj) {
+        mainWindObj?.mousedownModel.call(mainWindObj, intersects);
+      } else if (modalType === 'mainXjWindRect' && mainXjWindObj) {
+        mainXjWindObj?.mousedownModel.call(mainXjWindObj, intersects);
       }
     });
   }
@@ -110,6 +91,8 @@ export const addText = () => {
   if (!mainWindObj) return;
   if (modalType === 'mainWindRect' && mainWindObj) {
     return mainWindObj.addCssText.call(mainWindObj);
+  } else if (modalType === 'mainXjWindRect' && mainXjWindObj) {
+    return mainXjWindObj.addCssText.call(mainXjWindObj);
   }
 };
 
@@ -118,6 +101,8 @@ export const resetEcharts = (selectData) => {
   if (!mainWindObj) return;
   if (modalType === 'mainWindRect' && mainWindObj) {
     return mainWindObj.addEcharts.call(mainWindObj);
+  } else if (modalType === 'mainXjWindRect' && mainXjWindObj) {
+    return mainXjWindObj.addEcharts.call(mainXjWindObj);
   }
 };
 
@@ -134,84 +119,91 @@ export const play = (controlType, deviceType, frequencyVal?, state?, smokeDirect
   if (!mainWindObj) return;
   if (modalType === 'mainWindRect' && mainWindObj) {
     return mainWindObj.playSmoke.call(mainWindObj, controlType, deviceType, frequencyVal, state, smokeDirection);
+  } else if (modalType === 'mainXjWindRect' && mainXjWindObj) {
+    return mainXjWindObj.playSmoke.call(mainXjWindObj, controlType, deviceType, frequencyVal, state, smokeDirection);
   }
 };
 
 export const playAnimate1 = async (selectData, duration?) => {
   if (!mainWindObj) return;
+  let mainObj: mainWindRect | mainXjWindRect | undefined;
+
   if (modalType === 'mainWindRect') {
-    if (selectData) {
-      if (selectData.Fan1WindowOpen !== undefined) {
-        // 主风机水平窗开启
-        if (selectData.Fan1WindowOpen == 1) mainWindObj?.openOrCloseWindow('front', 'openWindow');
-        if (selectData.Fan1WindowOpen == 0) mainWindObj?.openOrCloseWindow('front', 'closeWindow');
-      }
-      if (selectData.Fan2WindowOpen !== undefined) {
-        // 备风机水平窗开启
-        if (selectData.Fan2WindowOpen == 1) mainWindObj?.openOrCloseWindow('back', 'openWindow');
-        if (selectData.Fan2WindowOpen == 0) mainWindObj?.openOrCloseWindow('back', 'closeWindow');
-      }
-      if (selectData.Fan1ButterflyOpen !== undefined) {
-        if (selectData.Fan1ButterflyOpen == 1) {
-          // 主风机蝶阀打开
-          mainWindObj.openOrCloseValve('front', 'open', duration);
-        } else {
-          // 主风机蝶阀关闭
-          mainWindObj.openOrCloseValve('front', 'close', duration);
-        }
+    mainObj = mainWindObj;
+  } else if (modalType === 'mainXjWindRect') {
+    mainObj = mainXjWindObj;
+  }
+  if (selectData && mainObj) {
+    if (selectData.Fan1WindowOpen !== undefined) {
+      // 主风机水平窗开启
+      if (selectData.Fan1WindowOpen == 1) mainObj?.openOrCloseWindow('front', 'openWindow');
+      if (selectData.Fan1WindowOpen == 0) mainObj?.openOrCloseWindow('front', 'closeWindow');
+    }
+    if (selectData.Fan2WindowOpen !== undefined) {
+      // 备风机水平窗开启
+      if (selectData.Fan2WindowOpen == 1) mainObj?.openOrCloseWindow('back', 'openWindow');
+      if (selectData.Fan2WindowOpen == 0) mainObj?.openOrCloseWindow('back', 'closeWindow');
+    }
+    if (selectData.Fan1ButterflyOpen !== undefined) {
+      if (selectData.Fan1ButterflyOpen == 1) {
+        // 主风机蝶阀打开
+        mainObj.openOrCloseValve('front', 'open', duration);
+      } else {
+        // 主风机蝶阀关闭
+        mainObj.openOrCloseValve('front', 'close', duration);
       }
-      if (selectData.Fan2ButterflyOpen !== undefined) {
-        if (selectData.Fan2ButterflyOpen == 1) {
-          // 主风机蝶阀打开
-          mainWindObj.openOrCloseValve('back', 'open', duration);
-        } else {
-          // 主风机蝶阀关闭
-          mainWindObj.openOrCloseValve('back', 'close', duration);
-        }
+    }
+    if (selectData.Fan2ButterflyOpen !== undefined) {
+      if (selectData.Fan2ButterflyOpen == 1) {
+        // 主风机蝶阀打开
+        mainObj.openOrCloseValve('back', 'open', duration);
+      } else {
+        // 主风机蝶阀关闭
+        mainObj.openOrCloseValve('back', 'close', duration);
       }
+    }
 
-      if (selectData.Fan1FreqHz !== undefined) {
-        // 主风机频率设置
-        mainWindObj.resetSmokeParam('front', selectData.Fan1FreqHz, duration);
-      }
-      if (selectData.Fan2FreqHz !== undefined) {
-        // 主风机频率设置
-        mainWindObj.resetSmokeParam('back', selectData.Fan2FreqHz, duration);
-      }
-      if (selectData.Fan1StartStatus) {
-        if (selectData.Fan1StartStatus == 1) {
-          // 主风机开启
-          mainWindObj.lookMotor('front', 'open', duration);
+    if (selectData.Fan1FreqHz !== undefined) {
+      // 主风机频率设置
+      mainObj.resetSmokeParam('front', selectData.Fan1FreqHz, duration);
+    }
+    if (selectData.Fan2FreqHz !== undefined) {
+      // 主风机频率设置
+      mainObj.resetSmokeParam('back', selectData.Fan2FreqHz, duration);
+    }
+    if (selectData.Fan1StartStatus) {
+      if (selectData.Fan1StartStatus == 1) {
+        // 主风机开启
+        mainObj.lookMotor('front', 'open', duration);
 
-          // if (selectData.Fan1FreqForwardRun && selectData.Fan1FreqForwardRun == 1) {
-          //   // 主风机正转
-          //   await mainWindObj.setSmokeDirection('front', 'tubPositivePath');
-          // } else if (selectData.Fan1FreqReverseRun && selectData.Fan1FreqReverseRun == 1) {
-          //   // 主风机反转
-          //   await mainWindObj.setSmokeDirection('front', 'tubInversePath');
-          // }
-          // 齿轮转动
-          mainWindObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
-          await mainWindObj.setSmokeDirection('front', 'tubPositivePath');
-          if (!mainWindObj.frontSmoke?.frameId) mainWindObj?.frontSmoke?.startSmoke(duration);
-        } else {
-          mainWindObj?.lookMotor('front', 'close', duration);
-        }
+        // if (selectData.Fan1FreqForwardRun && selectData.Fan1FreqForwardRun == 1) {
+        //   // 主风机正转
+        //   await mainWindObj.setSmokeDirection('front', 'tubPositivePath');
+        // } else if (selectData.Fan1FreqReverseRun && selectData.Fan1FreqReverseRun == 1) {
+        //   // 主风机反转
+        //   await mainWindObj.setSmokeDirection('front', 'tubInversePath');
+        // }
+        // 齿轮转动
+        mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
+        await mainObj.setSmokeDirection('front', 'tubPositivePath');
+        if (!mainObj.frontSmoke?.frameId) mainObj?.frontSmoke?.startSmoke(duration);
+      } else {
+        mainObj?.lookMotor('front', 'close', duration);
       }
-      if (selectData.Fan2StartStatus) {
-        if (selectData.Fan2StartStatus == 1) {
-          // 备风机开启
-          mainWindObj.lookMotor('back', 'open', duration);
-          // if (selectData.Fan2FreqForwardRun && selectData.Fan2FreqForwardRun == 1) {
-          //   // 主风机正转
-          // } else if (selectData.Fan2FreqReverseRun && selectData.Fan2FreqReverseRun == 1) {
-          //   // 主风机反转
-          // }
-          await mainWindObj.setSmokeDirection('back', 'tubPositivePath');
-          if (!mainWindObj.backSmoke?.frameId) mainWindObj?.backSmoke?.startSmoke(duration);
-        } else {
-          mainWindObj?.lookMotor('back', 'close', duration);
-        }
+    }
+    if (selectData.Fan2StartStatus) {
+      if (selectData.Fan2StartStatus == 1) {
+        // 备风机开启
+        mainObj.lookMotor('back', 'open', duration);
+        // if (selectData.Fan2FreqForwardRun && selectData.Fan2FreqForwardRun == 1) {
+        //   // 主风机正转
+        // } else if (selectData.Fan2FreqReverseRun && selectData.Fan2FreqReverseRun == 1) {
+        //   // 主风机反转
+        // }
+        await mainObj.setSmokeDirection('back', 'tubPositivePath');
+        if (!mainObj.backSmoke?.frameId) mainObj?.backSmoke?.startSmoke(duration);
+      } else {
+        mainObj?.lookMotor('back', 'close', duration);
       }
     }
   }
@@ -219,84 +211,91 @@ export const playAnimate1 = async (selectData, duration?) => {
 
 export const playAnimate = async (selectData, duration?) => {
   if (!mainWindObj) return;
+
+  let mainObj: mainWindRect | mainXjWindRect | undefined;
+
   if (modalType === 'mainWindRect') {
-    if (selectData) {
-      if (selectData['Fan1FreqHz'] == undefined || selectData['Fan1FreqHz'] == null || selectData['Fan1FreqHz'] == '') selectData['Fan1FreqHz'] = 50;
-      if (selectData['Fan2FreqHz'] == undefined || selectData['Fan2FreqHz'] == null || selectData['Fan2FreqHz'] == '') selectData['Fan2FreqHz'] = 50;
-      mainWindObj.resetSmokeParam('front', selectData.Fan1FreqHz, duration);
-      mainWindObj.resetSmokeParam('back', selectData.Fan2FreqHz, duration);
-      if (selectData.Fan1StartStatus == 1) {
-        // 主风机开启
-        mainWindObj?.lookMotor('front', 'open', duration);
-        mainWindObj?.openOrCloseValve('front', 'open', duration);
-        // 1. 已经运行,首次切入动画
-        // 2. 在页面上,切换动画
-        if (selectData.Fan1FreqForwardRun == 1 && selectData.Fan1FreqReverseRun == 0) {
-          // 主风机正转
-          mainWindObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
-          await mainWindObj.setSmokeDirection('front', 'tubPositivePath');
-        } else if (selectData.Fan1FreqReverseRun == 1 && selectData.Fan1FreqForwardRun == 0) {
-          // 主风机反转
-          mainWindObj.startGearAnimation('front', 'open', 'tubInversePath', selectData.Fan1FreqHz, duration);
-          await mainWindObj.setSmokeDirection('front', 'tubInversePath');
-        } else {
-          // 默认主风机正转
-          mainWindObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
-          await mainWindObj.setSmokeDirection('front', 'tubPositivePath');
-        }
+    mainObj = mainWindObj;
+  } else if (modalType === 'mainXjWindRect') {
+    mainObj = mainXjWindObj;
+  }
 
-        if (!mainWindObj?.frontSmoke?.frameId) mainWindObj?.frontSmoke?.startSmoke(duration);
+  if (selectData && mainObj) {
+    if (selectData['Fan1FreqHz'] == undefined || selectData['Fan1FreqHz'] == null || selectData['Fan1FreqHz'] == '') selectData['Fan1FreqHz'] = 50;
+    if (selectData['Fan2FreqHz'] == undefined || selectData['Fan2FreqHz'] == null || selectData['Fan2FreqHz'] == '') selectData['Fan2FreqHz'] = 50;
+    mainObj.resetSmokeParam('front', selectData.Fan1FreqHz, duration);
+    mainObj.resetSmokeParam('back', selectData.Fan2FreqHz, duration);
+    if (selectData.Fan1StartStatus == 1) {
+      // 主风机开启
+      mainObj?.lookMotor('front', 'open', duration);
+      mainObj?.openOrCloseValve('front', 'open', duration);
+      // 1. 已经运行,首次切入动画
+      // 2. 在页面上,切换动画
+      if (selectData.Fan1FreqForwardRun == 1 && selectData.Fan1FreqReverseRun == 0) {
+        // 主风机正转
+        mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
+        await mainObj.setSmokeDirection('front', 'tubPositivePath');
+      } else if (selectData.Fan1FreqReverseRun == 1 && selectData.Fan1FreqForwardRun == 0) {
+        // 主风机反转
+        mainObj.startGearAnimation('front', 'open', 'tubInversePath', selectData.Fan1FreqHz, duration);
+        await mainObj.setSmokeDirection('front', 'tubInversePath');
       } else {
-        // 主风机停止
-        mainWindObj.closeDevice('front');
+        // 默认主风机正转
+        mainObj.startGearAnimation('front', 'open', 'tubPositivePath', selectData.Fan1FreqHz, duration);
+        await mainObj.setSmokeDirection('front', 'tubPositivePath');
       }
-      if (selectData.Fan2StartStatus == 1) {
-        // 主风机开启
-        mainWindObj.lookMotor('back', 'open', duration);
-        mainWindObj.openOrCloseValve('back', 'open', duration);
-        // 1. 已经运行,首次切入动画
-        // 2. 在页面上,切换动画
-        if (selectData.Fan2FreqForwardRun == 1 && selectData.Fan2FreqReverseRun == 0) {
-          // 主风机正转
-          mainWindObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainWindObj.setSmokeDirection('back', 'tubPositivePath');
-        } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
-          // 主风机反转
-          mainWindObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
-          await mainWindObj.setSmokeDirection('back', 'tubInversePath');
-        } else {
-          mainWindObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
-          await mainWindObj.setSmokeDirection('back', 'tubPositivePath');
-        }
 
-        if (!mainWindObj?.backSmoke?.frameId) mainWindObj?.backSmoke?.startSmoke(duration);
+      if (!mainObj?.frontSmoke?.frameId) mainObj?.frontSmoke?.startSmoke(duration);
+    } else {
+      // 主风机停止
+      mainObj.closeDevice('front');
+    }
+    if (selectData.Fan2StartStatus == 1) {
+      // 主风机开启
+      mainObj.lookMotor('back', 'open', duration);
+      mainObj.openOrCloseValve('back', 'open', duration);
+      // 1. 已经运行,首次切入动画
+      // 2. 在页面上,切换动画
+      if (selectData.Fan2FreqForwardRun == 1 && selectData.Fan2FreqReverseRun == 0) {
+        // 主风机正转
+        mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
+        await mainObj.setSmokeDirection('back', 'tubPositivePath');
+      } else if (selectData.Fan2FreqReverseRun == 1 && selectData.Fan2FreqForwardRun == 0) {
+        // 主风机反转
+        mainObj.startGearAnimation('back', 'open', 'tubInversePath', selectData.Fan2FreqHz, duration);
+        await mainObj.setSmokeDirection('back', 'tubInversePath');
       } else {
-        // 主风机停止
-        mainWindObj.closeDevice('back');
+        mainObj.startGearAnimation('back', 'open', 'tubPositivePath', selectData.Fan2FreqHz, duration);
+        await mainObj.setSmokeDirection('back', 'tubPositivePath');
       }
 
-      // 防爆门动画
-      if (selectData['ExplosionVentOpen'] == 1 && explosionVentOpen !== 1) {
-        if (explosionVentOpen == -1) {
-          // 直接打开
-          mainWindObj.playAnimation('open', 0);
-        } else {
-          mainWindObj.playAnimation('open');
-        }
-        explosionVentOpen = 1;
-        explosionVentClose = 0;
-      }
-      if (selectData['ExplosionVentClose'] == 1 && explosionVentClose !== 1) {
-        if (explosionVentOpen == -1) {
-          // 直接关闭
-          mainWindObj.playAnimation('close', 0);
-        } else {
-          mainWindObj.playAnimation('close');
-        }
+      if (!mainObj?.backSmoke?.frameId) mainObj?.backSmoke?.startSmoke(duration);
+    } else {
+      // 主风机停止
+      mainObj.closeDevice('back');
+    }
 
-        explosionVentClose = 1;
-        explosionVentOpen = 0;
+    // 防爆门动画
+    if (modalType === 'mainWindRect' && selectData['ExplosionVentOpen'] == 1 && explosionVentOpen !== 1) {
+      if (explosionVentOpen == -1) {
+        // 直接打开
+        mainObj.playAnimation('open', 0);
+      } else {
+        mainObj.playAnimation('open');
       }
+      explosionVentOpen = 1;
+      explosionVentClose = 0;
+    }
+    if (modalType === 'mainWindRect' && selectData['ExplosionVentClose'] == 1 && explosionVentClose !== 1) {
+      if (explosionVentOpen == -1) {
+        // 直接关闭
+        mainObj.playAnimation('close', 0);
+      } else {
+        mainObj.playAnimation('close');
+      }
+
+      explosionVentClose = 1;
+      explosionVentOpen = 0;
     }
   }
 };
@@ -308,6 +307,8 @@ export const setModelType = (type) => {
   return new Promise((resolve) => {
     // 停止气流动画
     mainWindObj?.stopSmoke();
+    mainXjWindObj?.stopSmoke();
+    if (group) model?.scene?.remove(group);
     if (modalType === 'mainWindRect' && mainWindObj && mainWindObj.group) {
       (<UseThree>model).startAnimation = mainWindObj.render.bind(mainWindObj);
       group = mainWindObj.group;
@@ -327,6 +328,25 @@ export const setModelType = (type) => {
         if (group) model?.scene?.add(group);
       }, 300);
     }
+    if (modalType === 'mainXjWindRect' && mainXjWindObj && mainXjWindObj.group) {
+      (<UseThree>model).startAnimation = mainXjWindObj.render.bind(mainXjWindObj);
+      group = mainXjWindObj.group;
+      setTimeout(async () => {
+        resolve(null);
+        // const position = mainWindObj.group.position;
+        const position = new THREE.Vector3(-0.45, 0.84, -10.35);
+        const oldCameraPosition = { x: -332.39, y: 283.47, z: 438.61 };
+        await animateCamera(
+          oldCameraPosition,
+          { x: -3.41, y: -29.01, z: 8.84 },
+          { x: -1.23, y: 75.15, z: 118.36 },
+          { x: position.x, y: position.y, z: position.z },
+          model,
+          0.8
+        );
+        if (group) model?.scene?.add(group);
+      }, 300);
+    }
   });
 };
 
@@ -347,6 +367,8 @@ export const mountedThree = (playerVal1) => {
       model?.scene?.add(bgGroup);
       mainWindObj = new mainWindRect(model, playerVal1);
       await mainWindObj.mountedThree();
+      mainXjWindObj = new mainXjWindRect(model, playerVal1);
+      await mainXjWindObj.mountedThree();
       model?.animate();
       resolve(null);
     });
@@ -360,6 +382,8 @@ export const destroy = () => {
     console.log('场景销毁前信息----------->', model.renderer?.info);
     mainWindObj?.destroy();
     mainWindObj = undefined;
+    mainXjWindObj?.destroy();
+    mainXjWindObj = undefined;
     model.clearGroup(bgGroup);
     bgGroup = undefined;
     group = undefined;

+ 718 - 0
src/views/vent/monitorManager/mainFanMonitor/mainWind.xj.threejs.ts

@@ -0,0 +1,718 @@
+import * as THREE from 'three';
+import { CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
+import Smoke from '/@/views/vent/comment/threejs/Smoke';
+import { renderVideo } from '/@/utils/threejs/util';
+import gsap from 'gsap';
+
+class mainXjWindRect {
+  model;
+  modelName = 'mainXjWindRect';
+  group: THREE.Group | null = null; // 主通风机场景
+  motorGroup1: THREE.Group | null = null; //电机
+  motorGroup2: THREE.Group | null = null; //电机
+  gearFront = {
+    gear1: null, //扇叶
+    gear2: null, //扇叶
+    gear1Direction: -1,
+    gear2Direction: 1,
+    gearFrameId: undefined,
+    gearRotateFactor: 0.5,
+    endGearRotateFactor: 3,
+  };
+  gearBack = {
+    gear1: null, //扇叶
+    gear2: null, //扇叶
+    gear1Direction: -1, // 扇叶转动方向
+    gear2Direction: 1, // 扇叶转动方向
+    gearFrameId: undefined,
+    gearRotateFactor: 0.5, // 扇叶转动因素
+    endGearRotateFactor: 3, // 扇叶最终转动速度因素
+  };
+  oldMaterial: THREE.Material = new THREE.MeshStandardMaterial();
+  // smoke;
+  frontSmoke: Smoke | null = null; // 前面风流对象
+  backSmoke: Smoke | null = null; // 后面风流对象
+  player1; // 视频播放器
+  playerStartClickTime1 = new Date().getTime();
+  frontWindowGroup;
+  backWindowGroup;
+  windowAngle = 0;
+  fbmAnimationClip: THREE.AnimationClip | null = null;
+  fbmMixers: THREE.AnimationMixer | null = null;
+  fbmOpenAction: THREE.AnimationAction | null = null;
+
+  constructor(model, playerVal1) {
+    this.model = model;
+    this.player1 = playerVal1;
+  }
+  // 添加 cssObject
+  addCssText() {
+    if (!this.group) {
+      return;
+    }
+    const ztfjGroup = this.group.getObjectByName('ztfj');
+
+    if (!this.group.getObjectByName('monitorText1')) {
+      const worldPosition = new THREE.Vector3();
+      ztfjGroup?.getObjectByName('FengJiWaiKe_1')?.getWorldPosition(worldPosition);
+      const element = document.getElementById('inputBox') as HTMLElement;
+      const mainCSS3D = new CSS3DObject(element);
+      mainCSS3D.name = 'monitorText1';
+      mainCSS3D.scale.set(0.09, 0.09, 0.09);
+      mainCSS3D.position.set(worldPosition.x + 34, worldPosition.y - 5, worldPosition.z - 35);
+      mainCSS3D.lookAt(worldPosition.x + 34, worldPosition.y + 5, worldPosition.z + 2);
+      this.group.add(mainCSS3D);
+    }
+    if (!this.group.getObjectByName('monitorText2')) {
+      const worldPosition = new THREE.Vector3();
+      ztfjGroup?.getObjectByName('FengJiWaiKe_2')?.getWorldPosition(worldPosition);
+      const element = document.getElementById('inputBox1') as HTMLElement;
+      const mainCSS3D = new CSS3DObject(element);
+      mainCSS3D.name = 'monitorText2';
+      mainCSS3D.scale.set(0.09, 0.09, 0.09);
+      mainCSS3D.position.set(worldPosition.x + 34, worldPosition.y - 5, worldPosition.z - 20);
+      mainCSS3D.lookAt(worldPosition.x + 34, worldPosition.y + 5, worldPosition.z + 2);
+      this.group.add(mainCSS3D);
+    }
+
+    if (!this.group.getObjectByName('monitorText3')) {
+      const worldPosition = new THREE.Vector3();
+      const fbmGroup = this.group?.getObjectByName('fbm') as THREE.Group;
+      if (fbmGroup) {
+        fbmGroup?.getObjectByName('Box022')?.getWorldPosition(worldPosition);
+        const element = document.getElementById('fbm') as HTMLElement;
+        if (element) {
+          const mainCSS3D = new CSS3DObject(element);
+          mainCSS3D.name = 'monitorText3';
+          mainCSS3D.scale.set(0.07, 0.07, 0.07);
+          mainCSS3D.position.set(worldPosition.x + 20, worldPosition.y - 8, worldPosition.z - 20);
+          mainCSS3D.lookAt(worldPosition.x + 20, worldPosition.y - 0, worldPosition.z + 2);
+          this.group.add(mainCSS3D);
+        }
+      }
+    }
+  }
+
+  addEcharts() {
+    const echartsBox = document.getElementById('fan-echarts');
+    if (echartsBox) {
+      const canvasObj = echartsBox.getElementsByTagName('canvas')[0];
+      // 将canvas 纹理转换为材质
+      const echartsMap = new THREE.CanvasTexture(canvasObj); // 关键一步
+      const echartsMaterial = new THREE.MeshBasicMaterial({
+        map: echartsMap, // 设置纹理贴图
+        transparent: true,
+        side: THREE.FrontSide, // 这里是双面渲染的意思
+      });
+      echartsMaterial.blending = THREE.CustomBlending;
+      const monitorPlane = this.group?.getObjectByName('monitorEcharts');
+      if (monitorPlane) {
+        monitorPlane.material = echartsMaterial;
+      } else {
+        const planeGeometry = new THREE.PlaneGeometry(17.6, 9.9); // 平面3维几何体PlaneGeometry
+        const planeMesh = new THREE.Mesh(planeGeometry, echartsMaterial);
+        planeMesh.name = 'monitorEcharts';
+        planeMesh.scale.set(1, 1, 1);
+        planeMesh.position.set(-47.38, 13.227, -21.79);
+        this.group?.add(planeMesh);
+      }
+    }
+  }
+
+  initAnimation() {}
+
+  startAnimation() {}
+
+  /* 更新动画 */
+  render() {
+    if (!this.model) {
+      return;
+    }
+    if (this.fbmMixers) this.fbmMixers?.update(1 / 25);
+  }
+
+  /* 点击风窗,风窗全屏 */
+  mousedownModel(intersects: THREE.Intersection<THREE.Object3D<THREE.Event>>[]) {
+    // 判断是否点击到视频
+    intersects.find((intersect) => {
+      const mesh = intersect.object;
+      if (mesh.name === 'player1') {
+        if (new Date().getTime() - this.playerStartClickTime1 < 400) {
+          // 双击,视频放大
+          if (this.player1) {
+            this.player1.requestFullscreen();
+          }
+        }
+        this.playerStartClickTime1 = new Date().getTime();
+        return true;
+      }
+      return false;
+    });
+  }
+
+  mouseUpModel() {}
+
+  async setDeviceFrequency(deviceType, state, frequencyVal?) {
+    // 调节频率
+    if (frequencyVal) {
+      this.resetSmokeParam(deviceType, frequencyVal, 0);
+    }
+    // this.openOrCloseValve(deviceType, state, 0);
+    this.startGearAnimation(deviceType, state, '', 0);
+    if (deviceType === 'front') {
+      this.frontSmoke?.startSmoke();
+    } else {
+      this.backSmoke?.startSmoke();
+    }
+    setTimeout(() => {
+      this.lookMotor(deviceType, state, 10);
+    }, 2000);
+  }
+
+  async openDevice(deviceType, smokeDirection, frequencyVal, duration?) {
+    if (smokeDirection) {
+      this.setSmokeDirection(deviceType, smokeDirection);
+    }
+    let smoke;
+    if (deviceType === 'front') {
+      smoke = this.frontSmoke;
+    } else {
+      smoke = this.backSmoke;
+    }
+    if (!smoke.frameId) {
+      await this.lookMotor(deviceType, 'open', duration);
+      // await this.openOrCloseValve(deviceType, 'open', duration);
+      this.startGearAnimation(deviceType, 'open', smokeDirection, frequencyVal, duration);
+      smoke.startSmoke(duration);
+    }
+  }
+
+  async closeDevice(deviceType, flag = true) {
+    let smoke;
+    if (deviceType === 'front') {
+      smoke = this.frontSmoke;
+    } else if (deviceType === 'back') {
+      smoke = this.backSmoke;
+    }
+    if (smoke && smoke.frameId) {
+      // console.log('风机关闭', deviceType);
+      if (flag) {
+        smoke.stopSmoke();
+        // await this.openOrCloseValve(deviceType, 'close');
+        this.startGearAnimation(deviceType, 'close', '', null);
+        await this.lookMotor(deviceType, 'close');
+      } else {
+        smoke.stopSmoke(0);
+        // await this.openOrCloseValve(deviceType, 'close', 0);
+        this.startGearAnimation(deviceType, 'close', '', null, 0);
+        await this.lookMotor(deviceType, 'close', 0);
+      }
+    }
+  }
+
+  async setSmokeDirection(deviceType, smokeDirection) {
+    const windowPositivePath = [
+      {
+        path0: new THREE.Vector3(4.441, 20.267, 3.614),
+        path1: new THREE.Vector3(5.041, 6.806, 3.614),
+        isSpread: true,
+        spreadDirection: -1, //
+      },
+      {
+        path0: new THREE.Vector3(7.441, 0.806, 3.614),
+        path1: new THREE.Vector3(41.583, 1.485, 3.614),
+        isSpread: false,
+        spreadDirection: 0, //
+      },
+      {
+        path0: new THREE.Vector3(41.583, 1.485, 3.614),
+        path1: new THREE.Vector3(42.741, 5.364, 3.614),
+        isSpread: false,
+        spreadDirection: 0,
+      },
+      {
+        path0: new THREE.Vector3(42.741, 5.364, 3.614),
+        path1: new THREE.Vector3(44.741, 17.267, 3.614),
+        isSpread: true,
+        spreadDirection: 1, // 1是由小变大(出),-1是由大变小(进)
+      },
+    ];
+    const windowInversePath = [
+      {
+        path0: new THREE.Vector3(44.741, 17.267, 3.614),
+        path1: new THREE.Vector3(42.741, 5.364, 3.614),
+        isSpread: true,
+        spreadDirection: -1, //
+      },
+      {
+        path0: new THREE.Vector3(42.741, 5.364, 3.614),
+        path1: new THREE.Vector3(41.583, 1.485, 3.614),
+        isSpread: false,
+        spreadDirection: 0, //
+      },
+      {
+        path0: new THREE.Vector3(41.583, 1.485, 3.614),
+        path1: new THREE.Vector3(7.441, 0.806, 3.614),
+        isSpread: false,
+        spreadDirection: 0, // 1是由小变大,-1是由大变小
+      },
+      {
+        path0: new THREE.Vector3(4.441, 17.267, 3.614),
+        path1: new THREE.Vector3(5.041, 6.806, 3.614),
+        isSpread: true,
+        spreadDirection: 1, //
+      },
+    ];
+    const tubPositivePath = [
+      {
+        path0: new THREE.Vector3(7.441, 0.806, 3.614),
+        path1: new THREE.Vector3(44.583, 1.485, 3.614),
+        isSpread: false,
+        spreadDirection: 0, //
+      },
+      {
+        path0: new THREE.Vector3(44.583, 1.485, 3.614),
+        path1: new THREE.Vector3(45.741, 5.364, 3.614),
+        isSpread: false,
+        spreadDirection: 0,
+      },
+      {
+        path0: new THREE.Vector3(45.741, 5.364, 3.614),
+        path1: new THREE.Vector3(47.741, 17.267, 3.614),
+        isSpread: true,
+        spreadDirection: 1, // 1是由小变大(出),-1是由大变小(进)
+      },
+    ];
+    const tubInversePath = [
+      {
+        path0: new THREE.Vector3(47.741, 17.267, 3.614),
+        path1: new THREE.Vector3(45.741, 5.364, 3.614),
+        isSpread: true,
+        spreadDirection: -1, //
+      },
+      {
+        path0: new THREE.Vector3(45.741, 5.364, 3.614),
+        path1: new THREE.Vector3(44.583, 1.485, 3.614),
+        isSpread: false,
+        spreadDirection: 0, //
+      },
+      {
+        path0: new THREE.Vector3(44.583, 1.485, 3.614),
+        path1: new THREE.Vector3(7.441, 0.806, 3.614),
+        isSpread: false,
+        spreadDirection: 0, // 1是由小变大,-1是由大变小
+      },
+    ];
+    let smoke;
+    if (deviceType === 'front') {
+      smoke = this.frontSmoke;
+    } else if (deviceType === 'back') {
+      smoke = this.backSmoke;
+    }
+    switch (smokeDirection) {
+      case 'tubPositivePath': // 风筒正
+        smoke.setPath(tubPositivePath);
+        break;
+      case 'tubInversePath': // 风筒反
+        smoke.setPath(tubInversePath);
+        break;
+      case 'windowPositivePath': // 风窗正
+        smoke.setPath(windowPositivePath);
+        break;
+      case 'windowInversePath': // 风窗反
+        smoke.setPath(windowInversePath);
+        break;
+    }
+  }
+
+  /* 播放气流动画 */
+  /**
+   *
+   * @param controlType // 设备控制类型
+   * @param deviceType //前后风机
+   * @param frequencyVal // 风机运行频率
+   * @param state // 打开、关闭状态
+   */
+  async playSmoke(controlType, deviceType, frequencyVal, state, smokeDirection) {
+    if (frequencyVal) {
+      this.resetSmokeParam(deviceType, frequencyVal);
+    }
+    if (controlType === 'startSmoke') {
+      if (state === 'stop') {
+        await this.closeDevice(deviceType);
+      } else {
+        // 开启时需要设置方向
+        await this.openDevice(deviceType, smokeDirection, frequencyVal);
+      }
+    } else if (controlType === 'changeDirection') {
+      // 改变扇叶转动方向、反风
+      this.startGearAnimation(deviceType, 'changeDirection', smokeDirection, frequencyVal);
+      let smoke;
+      if (deviceType === 'front') {
+        smoke = this.frontSmoke;
+      } else {
+        smoke = this.backSmoke;
+      }
+      if (smoke && smoke.frameId) {
+        await smoke.stopSmoke();
+        await this.setSmokeDirection(deviceType, smokeDirection);
+        smoke.startSmoke();
+      }
+    } else if (controlType === 'frequency') {
+      this.startGearAnimation(deviceType, 'frequency', smokeDirection, frequencyVal);
+    } else if (controlType === 'initiatePlay') {
+      this.openDevice(deviceType, smokeDirection, frequencyVal, 0);
+    } else if (controlType === 'changeSmoke') {
+      //
+    }
+  }
+
+  stopSmoke() {
+    this.closeDevice('front', false);
+    this.closeDevice('back', false);
+  }
+
+  /* 打开或关闭蝶阀 */
+  openOrCloseValve(deviceType, flag, duration = 3) {
+    const ztfjGroup = this.group?.getObjectByName('ztfj')?.getObjectByName('WaiKe');
+    return new Promise((resolve) => {
+      let diefa;
+      if (deviceType == 'front') {
+        diefa = ztfjGroup?.getObjectByName('Cylinder1206') as THREE.Mesh;
+      } else {
+        diefa = ztfjGroup?.getObjectByName('Cylinder1041') as THREE.Mesh;
+      }
+      let rotationY;
+      if (flag == 'open') {
+        rotationY = 0;
+      } else {
+        rotationY = Math.PI / 2;
+      }
+      if (diefa) {
+        gsap.to(diefa.rotation, {
+          y: rotationY,
+          duration: duration,
+          ease: 'none',
+          onComplete: function () {
+            resolve(null);
+          },
+        });
+      }
+    });
+  }
+
+  /* 风流调频, 范围1-50 */
+  // opacityFactor (0.4 300)
+  // life 最小 300, 最大 50
+  // speedFactor 最大0, 最小100
+  resetSmokeParam(deviceType, frequency, duration = 5) {
+    if (frequency < 1) frequency = 1;
+    if (frequency > 50) frequency = 50;
+    let smoke;
+    if (deviceType === 'front') {
+      smoke = this.frontSmoke;
+    } else {
+      smoke = this.backSmoke;
+    }
+    const opacityFactor = (frequency / 50) * 0.8;
+    duration = (Number(Math.abs(smoke.opacityFactor - opacityFactor).toFixed(1)) / 0.8) * 5;
+    const life = (-250 / 50) * frequency + 300;
+    gsap.to(smoke, {
+      opacityFactor: opacityFactor,
+      life: life,
+      duration: duration,
+      ease: 'easeInCirc',
+      overwrite: true,
+    });
+  }
+
+  /* 显示电机 */
+  lookMotor(deviceType, flag, duration = 5) {
+    return new Promise((resolve) => {
+      const ztfjGroup = this.group?.getObjectByName('ztfj');
+      let fengJiWaiKeGoup1, fengJiWaiKeGoup2, mesh, mesh1, mesh2, motorGroup;
+
+      fengJiWaiKeGoup1 = ztfjGroup?.getObjectByName('FengJiWaiKe_1'); //前
+      fengJiWaiKeGoup2 = ztfjGroup?.getObjectByName('FengJiWaiKe_2'); //前
+
+      mesh1 = fengJiWaiKeGoup1?.getObjectByName('transparent_shell02'); //前
+      mesh2 = fengJiWaiKeGoup2?.getObjectByName('transparent_shell00'); //后
+      if (deviceType == 'front') {
+        mesh = mesh1;
+        motorGroup = this.motorGroup2;
+      } else {
+        mesh = mesh2;
+        motorGroup = this.motorGroup1;
+      }
+      if (mesh && motorGroup) {
+        if (flag == 'open') {
+          mesh.material.depthWrite = false;
+          mesh.material.depthTest = false;
+
+          motorGroup.visible = true;
+          gsap.to(mesh.material, {
+            opacity: 0.1,
+            duration: duration,
+            overwrite: true,
+            onComplete: function () {
+              // mesh.material.color = '#000';
+              resolve(null);
+            },
+          });
+        } else {
+          const opacity = mesh.material.opacity;
+          Object.assign(mesh.material, this.oldMaterial, { opacity: opacity });
+          mesh.material.depthWrite = true;
+          mesh.material.depthTest = true;
+          gsap.to(mesh.material, {
+            opacity: 1,
+            duration: 1,
+            overwrite: true,
+            onComplete: function () {
+              resolve(null);
+            },
+          });
+        }
+      }
+    });
+  }
+
+  /* 齿轮转动动画 1 - 50  最大3 */
+  startGearAnimation(deviceType, flag, smokeDirection, frequencyVal, duration = 8) {
+    console.log(deviceType, flag);
+    debugger;
+    let gearObj, gearDirection;
+    if (deviceType === 'front') {
+      gearObj = this.gearFront;
+    } else {
+      gearObj = this.gearBack;
+    }
+    if (smokeDirection === 'tubPositivePath') {
+      gearDirection = 1;
+    } else if (smokeDirection === 'tubInversePath') {
+      gearDirection = -1;
+    }
+    if (frequencyVal) {
+      const endGearRotateFactor = (3 / 50) * frequencyVal;
+      duration = (8 / 3) * Math.abs(gearObj.endGearRotateFactor - endGearRotateFactor);
+      gearObj.endGearRotateFactor = endGearRotateFactor;
+    }
+
+    const gearAnimation = () => {
+      gsap.to(gearObj, {
+        gearRotateFactor: gearObj.endGearRotateFactor,
+        duration: duration,
+        ease: 'easeInCubic',
+        repeat: 0,
+        overwrite: true,
+      });
+
+      const clock = new THREE.Clock(); // 时钟
+      const h = () => {
+        if (gearObj.gear1 && gearObj.gear2) {
+          gearObj.gearFrameId = requestAnimationFrame(h);
+
+          const dt = clock.getDelta();
+          gearObj.gear1.rotation.x += dt * gearObj.gearRotateFactor * gearObj.gear1Direction;
+          gearObj.gear2.rotation.x += dt * gearObj.gearRotateFactor * gearObj.gear2Direction;
+        }
+      };
+      h();
+    };
+    if (flag === 'changeDirection') {
+      if (gearDirection == -1 * gearObj.gear1Direction) {
+        // 齿轮正在转,需要停止后再反方向转
+        gsap.to(gearObj, {
+          gearRotateFactor: 0,
+          duration: duration,
+          ease: 'easeInCubic',
+          repeat: 0,
+          onComplete: function () {
+            window.cancelAnimationFrame(gearObj.gearFrameId);
+            gearObj.gearFrameId = undefined;
+            gearObj.gear1Direction = -1 * gearObj.gear1Direction;
+            gearObj.gear2Direction = -1 * gearObj.gear2Direction;
+            gearAnimation();
+          },
+        });
+      }
+    } else if (flag === 'open') {
+      gearObj.gear1Direction = gearDirection;
+      gearObj.gear2Direction = -1 * gearDirection;
+      gearAnimation();
+    } else if (flag === 'close') {
+      gsap.to(gearObj, {
+        gearRotateFactor: 0,
+        duration: duration,
+        ease: 'easeInCubic',
+        repeat: 0,
+        overwrite: true,
+        onComplete: function () {
+          window.cancelAnimationFrame(gearObj.gearFrameId);
+          gearObj.gearFrameId = undefined;
+        },
+      });
+    } else if (flag === 'frequency') {
+      gsap.to(gearObj, {
+        gearRotateFactor: gearObj.endGearRotateFactor,
+        duration: duration,
+        ease: 'easeInCubic',
+        repeat: 0,
+        overwrite: true,
+      });
+    }
+  }
+
+  /* 初始化口上面的气体 */
+  initSmokeMass() {
+    if (!this.frontSmoke) {
+      this.frontSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.38, 1.8, 100);
+    }
+    if (!this.backSmoke) {
+      this.backSmoke = new Smoke('/model/img/texture-smoke.png', '#ffffff', 0, 0.38, 1.8, 100);
+    }
+  }
+
+  /* 设置气流位置 */
+  async setSmokePosition() {
+    if (this.frontSmoke) {
+      await this.frontSmoke.setPoints();
+      this.frontSmoke.points.name = 'frontSmoke';
+      this.group?.add(this.frontSmoke.points);
+      // this.frontSmoke.points.position.set(-2.51, 2.51, 8.25);
+      this.frontSmoke.points.position.set(3.18, 4.43, 2.31);
+    }
+    if (this.backSmoke) {
+      await this.backSmoke.setPoints();
+      this.backSmoke.points.name = 'backSmoke';
+      this.group?.add(this.backSmoke.points);
+      // this.backSmoke.points.position.set(-2.2, 3.13, -7.8);
+      this.backSmoke.points.position.set(3.18, 4.36, -13.99);
+    }
+  }
+
+  /** 初始化电机 */
+  async initMotor() {
+    // 前电机
+    const motorGltf1 = await this.model.setGLTFModel('dj1');
+    this.motorGroup1 = motorGltf1[0] as THREE.Group;
+    this.motorGroup1?.position.set(10, 1, -6);
+    // this.motorGroup1.visible = false;
+    this.motorGroup1.traverse((item) => {
+      if (item instanceof THREE.Object3D) {
+        item.renderOrder = -1;
+        if (item.name === 'fan_blade003') {
+          // @ts-ignore
+          this.gearBack.gear1 = item as THREE.Group;
+        } else if (item.name === 'fan_blade005') {
+          // @ts-ignore
+          this.gearBack.gear2 = item as THREE.Group;
+        }
+      }
+    });
+    this.motorGroup1.renderOrder = -1;
+    this.group?.add(this.motorGroup1);
+
+    // 后电机
+    const motorGltf2 = await this.model.setGLTFModel('dj2');
+    this.motorGroup2 = motorGltf2[0] as THREE.Group;
+    this.motorGroup2?.position.set(10, 1, -6);
+    // this.motorGroup2.visible = false;
+    this.motorGroup2.traverse((item) => {
+      if (item instanceof THREE.Object3D) {
+        item.renderOrder = -1;
+        if (item.name === 'fan_blade007') {
+          // @ts-ignore
+          this.gearFront.gear1 = item as THREE.Group;
+        } else if (item.name === 'fan_blade006') {
+          // @ts-ignore
+          this.gearFront.gear2 = item as THREE.Group;
+        }
+      }
+    });
+    this.motorGroup2.renderOrder = -1;
+    this.group?.add(this.motorGroup2);
+  }
+
+  openOrCloseWindow(deviceType, flag) {
+    const _this = this;
+    let endAngle = 0,
+      windowGroup;
+    if (deviceType === 'front') {
+      windowGroup = this.frontWindowGroup;
+    }
+    if (deviceType === 'back') {
+      windowGroup = this.backWindowGroup;
+    }
+    if (flag == 'openWindow') {
+      // 打开风窗
+      endAngle = 1;
+    } else {
+      // 关闭风窗
+      endAngle = 0;
+    }
+    if (windowGroup)
+      gsap.to(this, {
+        windowAngle: endAngle,
+        duration: Math.abs(endAngle - this.windowAngle) * 10,
+        ease: 'none',
+        onUpdate: function () {
+          windowGroup.children.forEach((mesh) => {
+            mesh.rotation.z = _this.windowAngle;
+          });
+        },
+      });
+  }
+
+  playAnimation(flag, duration?) {}
+
+  mountedThree() {
+    this.group = new THREE.Group();
+    debugger;
+    return new Promise(async (resolve) => {
+      this.model.setGLTFModel(['ztfj-xj']).then(async (gltf) => {
+        const ztfjModal = gltf[0].children[0];
+        ztfjModal.name = 'ztfj';
+        ztfjModal.position.set(4.64, 4.11, 1.52);
+        this.group?.add(gltf[0].children[0]);
+        // this.group?.position.set(4.77, 3.63, 0.63);
+        this.group?.position.set(-0.44, 19.88, 22.37);
+        this.initSmokeMass();
+        await this.setSmokePosition();
+
+        const ztfjGroup = this.group?.getObjectByName('ztfj');
+        const fengJiWaiKeGoup1 = ztfjGroup?.getObjectByName('FengJiWaiKe_1'); //前
+        const mesh = fengJiWaiKeGoup1?.getObjectByName('transparent_shell02'); //前
+        if (mesh && mesh.material) this.oldMaterial = mesh.material as THREE.MeshStandardMaterial;
+        await this.initMotor();
+        resolve(null);
+      });
+    });
+  }
+
+  destroy() {
+    this.frontSmoke.clearSmoke();
+    this.backSmoke.clearSmoke();
+
+    this.model.clearGroup(this.motorGroup1);
+    this.model.clearGroup(this.motorGroup2);
+    this.model.clearGroup(this.group);
+
+    this.motorGroup1 = undefined;
+    this.motorGroup2 = undefined;
+
+    this.gearFront.gear1 = undefined;
+    this.gearFront.gear2 = undefined;
+
+    this.gearBack.gear1 = undefined;
+    this.gearBack.gear2 = undefined;
+
+    this.frontSmoke = undefined;
+    this.backSmoke = undefined;
+
+    this.model = undefined;
+    this.group = undefined;
+  }
+}
+
+export default mainXjWindRect;