Kaynağa Gözat

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

hongrunxia 5 gün önce
ebeveyn
işleme
8a77cede64
58 değiştirilmiş dosya ile 3196 ekleme ve 713 silme
  1. BIN
      src/assets/images/themify/green/company/content-label.png
  2. BIN
      src/assets/images/themify/green/company/content-text.png
  3. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/img-9.png
  4. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/list-head.png
  5. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/miehuo.png
  6. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/module-title-long.png
  7. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/module-title.png
  8. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/qkjaq.png
  9. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/znzdxt.png
  10. BIN
      src/assets/images/themify/green/home-container/configurable/firehome/znzjxt.png
  11. BIN
      src/assets/images/themify/green/home-container/configurable/list_bg_f.png
  12. BIN
      src/assets/images/themify/green/home-container/configurable/list_bg_h.png
  13. BIN
      src/assets/images/themify/green/home-container/configurable/list_bg_i.png
  14. BIN
      src/assets/images/themify/green/home-container/configurable/partition-bg-a.png
  15. BIN
      src/assets/images/themify/green/vent/vent-header0.png
  16. BIN
      src/assets/images/themify/green/vent/vent-header1.png
  17. BIN
      src/assets/images/themify/green/vent/vent-header2.png
  18. BIN
      src/assets/images/themify/green/vent/vent-header3.png
  19. BIN
      src/assets/images/themify/green/vent/vent-header4.png
  20. BIN
      src/assets/images/themify/green/vent/vent-header5.png
  21. BIN
      src/assets/images/themify/green/vent/vent-header6.png
  22. BIN
      src/assets/images/themify/green/vent/vent-header7.png
  23. 220 0
      src/hooks/core/useAutoScroll.ts
  24. 16 4
      src/hooks/system/useListPage.ts
  25. 1 1
      src/views/sys/login/Login.vue
  26. 47 0
      src/views/vent/dataCenter/APICenter/ApiAddModal.vue
  27. 31 0
      src/views/vent/dataCenter/APICenter/apiManger.api.ts
  28. 61 0
      src/views/vent/dataCenter/APICenter/apiManger.data.ts
  29. 90 0
      src/views/vent/dataCenter/APICenter/index.vue
  30. 80 129
      src/views/vent/dataCenter/deviceCenter/index.vue
  31. 45 12
      src/views/vent/dataCenter/infoCenter/index.vue
  32. 5 75
      src/views/vent/dataCenter/infoCenter/infoCenter.data.ts
  33. 0 8
      src/views/vent/dataCenter/stationCenter/device.api.ts
  34. 84 79
      src/views/vent/dataCenter/stationCenter/index.vue
  35. 42 0
      src/views/vent/dataCenter/user/PasswordModal.vue
  36. 45 0
      src/views/vent/dataCenter/user/UserAgentModal.vue
  37. 136 0
      src/views/vent/dataCenter/user/UserDrawer.vue
  38. 77 0
      src/views/vent/dataCenter/user/UserPermissionDrawer.vue
  39. 138 0
      src/views/vent/dataCenter/user/UserRecycleBinModal.vue
  40. 253 0
      src/views/vent/dataCenter/user/index.vue
  41. 177 0
      src/views/vent/dataCenter/user/user.api.ts
  42. 246 0
      src/views/vent/dataCenter/user/user.data.ts
  43. 54 0
      src/views/vent/dataCenter/user/userDetails.vue
  44. 5 2
      src/views/vent/deviceManager/configurationTable/types.ts
  45. 7 0
      src/views/vent/home/configurable/components/ModuleBD.vue
  46. 1 1
      src/views/vent/home/configurable/components/MonitorBar.vue
  47. 402 368
      src/views/vent/home/configurable/components/content.vue
  48. 9 1
      src/views/vent/home/configurable/components/detail/ComplexList.vue
  49. 17 1
      src/views/vent/home/configurable/components/detail/CustomList.vue
  50. 17 2
      src/views/vent/home/configurable/components/detail/CustomTable.vue
  51. 23 0
      src/views/vent/home/configurable/components/detail/MiniBoard.vue
  52. 10 1
      src/views/vent/home/configurable/components/preset/partition.vue
  53. 2 2
      src/views/vent/home/configurable/configurable.data.tashan.ts
  54. 508 12
      src/views/vent/home/configurable/configurable.data.ts
  55. 11 0
      src/views/vent/home/configurable/fireBD.vue
  56. 23 14
      src/views/vent/home/configurable/fireTS.vue
  57. 10 1
      src/views/vent/home/configurable/fireWZ.vue
  58. 303 0
      src/views/vent/home/configurable/ventV6.vue

BIN
src/assets/images/themify/green/company/content-label.png


BIN
src/assets/images/themify/green/company/content-text.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/img-9.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/list-head.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/miehuo.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/module-title-long.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/module-title.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/qkjaq.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/znzdxt.png


BIN
src/assets/images/themify/green/home-container/configurable/firehome/znzjxt.png


BIN
src/assets/images/themify/green/home-container/configurable/list_bg_f.png


BIN
src/assets/images/themify/green/home-container/configurable/list_bg_h.png


BIN
src/assets/images/themify/green/home-container/configurable/list_bg_i.png


BIN
src/assets/images/themify/green/home-container/configurable/partition-bg-a.png


BIN
src/assets/images/themify/green/vent/vent-header0.png


BIN
src/assets/images/themify/green/vent/vent-header1.png


BIN
src/assets/images/themify/green/vent/vent-header2.png


BIN
src/assets/images/themify/green/vent/vent-header3.png


BIN
src/assets/images/themify/green/vent/vent-header4.png


BIN
src/assets/images/themify/green/vent/vent-header5.png


BIN
src/assets/images/themify/green/vent/vent-header6.png


BIN
src/assets/images/themify/green/vent/vent-header7.png


+ 220 - 0
src/hooks/core/useAutoScroll.ts

@@ -0,0 +1,220 @@
+import { ref, watch, onUnmounted, unref, type Ref, onMounted } from 'vue';
+import { useScroll, type UseScrollReturn } from '@vueuse/core';
+import gsap from 'gsap';
+const ticker = gsap.ticker;
+
+export interface AutoScrollOptions {
+  /** 延迟(刻),滚动启动、反转、用户交互后的重新开始的延迟 */
+  delay?: number;
+  /** 滚动到底部后是否回滚(否则是回到顶部重新滚动) */
+  rollBack?: boolean;
+  /** 每一刻滚动的像素数 */
+  step?: number;
+  /** 是否自动开始 */
+  autoStart?: boolean;
+  /** 滚动方向 */
+  direction?: 'x' | 'y';
+}
+
+export interface AutoScrollReturn {
+  /** 开始/恢复滚动 */
+  start: () => void;
+  /** 暂停滚动 */
+  pause: () => void;
+  /** 重置到初始状态 */
+  reset: () => void;
+  /** 反转滚动 */
+  reverse: () => void;
+  /** 恢复滚动 */
+  resume: () => void;
+}
+
+export function useAutoScroll(container: Ref<HTMLElement | null> | HTMLElement | null, options: AutoScrollOptions = {}): AutoScrollReturn {
+  const {
+    delay = 300, // 默认60帧(约1秒)
+    rollBack = false,
+    step = 1,
+    autoStart = true,
+    direction = 'y',
+  } = options;
+
+  let cleanupListeners: (() => void) | null = null;
+
+  // 状态管理
+  const isActive = ref(false);
+  const currentDirection = ref(1); // 1: 正向, -1: 反向
+  const delayFrames = ref(0);
+
+  // 使用 VueUse 的 useScroll
+  const { arrivedState, x, y } = useScroll(container, {
+    behavior: 'smooth',
+  }) as UseScrollReturn;
+
+  // 检查是否到达边界
+  const checkBoundary = (): boolean => {
+    if (direction === 'y') {
+      return currentDirection.value > 0 ? arrivedState.bottom : arrivedState.top;
+    } else {
+      return currentDirection.value > 0 ? arrivedState.right : arrivedState.left;
+    }
+  };
+
+  // 执行滚动
+  const performScroll = () => {
+    if (!isActive.value) return;
+    if (delayFrames.value > 0) {
+      delayFrames.value--;
+      return;
+    }
+
+    // 检查边界
+    if (checkBoundary()) {
+      if (rollBack) {
+        // 回滚模式:反转方向
+        reverse();
+      } else {
+        // 循环模式:回到开始位置
+        reset();
+      }
+      return;
+    }
+
+    // 执行滚动
+    if (direction === 'y') {
+      y.value += step * currentDirection.value;
+    } else {
+      x.value += step * currentDirection.value;
+    }
+  };
+
+  // 开始/恢复滚动
+  const start = () => {
+    if (!isActive.value) {
+      ticker.remove(performScroll);
+      ticker.add(performScroll);
+      isActive.value = true;
+      delayFrames.value = delay;
+    }
+  };
+
+  // 暂停滚动
+  const pause = () => {
+    delayFrames.value = Number.MAX_SAFE_INTEGER;
+  };
+
+  // 重置到初始状态
+  const reset = () => {
+    if (direction === 'y') {
+      y.value = 0;
+    } else {
+      x.value = 0;
+    }
+    currentDirection.value = 1;
+    delayFrames.value = delay;
+  };
+
+  // 反转滚动方向
+  const reverse = () => {
+    currentDirection.value *= -1;
+    delayFrames.value = delay;
+  };
+
+  const resume = () => {
+    delayFrames.value = delay;
+  };
+
+  // 监听用户交互事件
+  const setupUserInteractionListeners = (el: HTMLElement) => {
+    // 鼠标滚轮事件
+    const handleWheel = () => {
+      resume();
+    };
+
+    // 鼠标按下事件(用于拖动滚动条)
+    const handleMouseDown = () => {
+      pause();
+    };
+
+    // 鼠标抬起事件
+    const handleMouseUp = () => {
+      resume();
+    };
+
+    // 触摸事件
+    const handleTouchStart = () => {
+      pause();
+    };
+
+    const handleTouchEnd = () => {
+      resume();
+    };
+
+    // 键盘事件(PageUp/PageDown/方向键)
+    const handleKeyDown = (e: KeyboardEvent) => {
+      const scrollKeys = ['PageUp', 'PageDown', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'];
+      if (scrollKeys.includes(e.key)) {
+        pause();
+      }
+    };
+
+    const handleKeyUp = () => {
+      resume();
+    };
+
+    // 添加事件监听
+    el.addEventListener('wheel', handleWheel, { passive: true });
+    el.addEventListener('mousedown', handleMouseDown);
+    el.addEventListener('mouseup', handleMouseUp);
+    el.addEventListener('touchstart', handleTouchStart, { passive: true });
+    el.addEventListener('touchend', handleTouchEnd);
+    el.addEventListener('keydown', handleKeyDown);
+    el.addEventListener('keyup', handleKeyUp);
+
+    // 返回清理函数
+    return () => {
+      el.removeEventListener('wheel', handleWheel);
+      el.removeEventListener('mousedown', handleMouseDown);
+      el.removeEventListener('mouseup', handleMouseUp);
+      el.removeEventListener('touchstart', handleTouchStart);
+      el.removeEventListener('touchend', handleTouchEnd);
+      el.removeEventListener('keydown', handleKeyDown);
+      el.removeEventListener('keyup', handleKeyUp);
+    };
+  };
+
+  // 监听容器变化
+  watch(
+    () => unref(container),
+    (newContainer) => {
+      if (cleanupListeners) {
+        cleanupListeners();
+        cleanupListeners = null;
+      }
+      if (newContainer) {
+        cleanupListeners = setupUserInteractionListeners(newContainer);
+        // 容器变化时重置状态
+        reset();
+      }
+    }
+  );
+
+  onMounted(() => {
+    // 自动开始
+    if (autoStart) {
+      start();
+    }
+  });
+
+  // 清理
+  onUnmounted(() => {
+    ticker.remove(performScroll);
+  });
+
+  return {
+    start,
+    pause,
+    reset,
+    reverse,
+    resume,
+  };
+}

+ 16 - 4
src/hooks/system/useListPage.ts

@@ -337,14 +337,26 @@ export function useListTable(tableProps: TableProps): [
     return Object.assign({ column: 'createTime' }, params);
   }
 
-  // 合并方法
+  // // 合并方法
+  // Object.assign(defaultTableProps, { beforeFetch });
+  // if (typeof tableProps.beforeFetch === 'function') {
+  //   defaultTableProps.beforeFetch = function (params) {
+  //     params = beforeFetch(params);
+  //     // @ts-ignore
+  //     tableProps.beforeFetch(params);
+  //     return params;
+  //   };
+  // }
+
+  // 合并方法 - 修改版本
   Object.assign(defaultTableProps, { beforeFetch });
   if (typeof tableProps.beforeFetch === 'function') {
     defaultTableProps.beforeFetch = function (params) {
       params = beforeFetch(params);
-      // @ts-ignore
-      tableProps.beforeFetch(params);
-      return params;
+      // 调用自定义的beforeFetch并获取其返回值
+      const customParams = tableProps.beforeFetch(params);
+      // 如果自定义函数返回了值,使用该值;否则使用原始params
+      return customParams !== undefined ? customParams : params;
     };
   }
 

+ 1 - 1
src/views/sys/login/Login.vue

@@ -92,7 +92,7 @@
       --image-icon: url('/@/assets/images/themify/green/vent/login/icon.png');
       --image-down: url('/@/assets/images/themify/green/vent/login/down.png');
       --image-bg: none;
-      --container-bg: #181B24;
+      --container-bg: #181b24;
     }
     .login-icon {
       width: 325px !important;

+ 47 - 0
src/views/vent/dataCenter/APICenter/ApiAddModal.vue

@@ -0,0 +1,47 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :width="800" :title="isAdd ? '添加API信息' : '编辑API信息'" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+import { ref, computed, unref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm, useForm } from '/@/components/Form/index';
+import { formAddSchema } from './apiManger.data';
+import { apiManageList, AddOrEdit, apiManageQueryByID } from './apiManger.api';
+const isAdd = ref(true);
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+//表单配置
+const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+  schemas: formAddSchema,
+  showActionButtonGroup: false,
+});
+//表单赋值
+const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+  //重置表单
+  await resetFields();
+  setModalProps({ confirmLoading: false });
+  //获取详情
+  data.record = await apiManageQueryByID({ id: data?.record.id });
+  isAdd.value = !!data?.isUpdate;
+  await setFieldsValue({
+    ...data.record,
+  });
+});
+//表单提交事件
+async function handleSubmit() {
+  try {
+    let values = await validate();
+    setModalProps({ confirmLoading: true });
+    //提交表单
+    await AddOrEdit(values, isAdd.value);
+    //关闭弹窗
+    closeModal();
+    //刷新列表
+    emit('success');
+  } finally {
+    setModalProps({ confirmLoading: false });
+  }
+}
+</script>

+ 31 - 0
src/views/vent/dataCenter/APICenter/apiManger.api.ts

@@ -0,0 +1,31 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  apiManageList = '/dataCenter/apimanage/list',
+  apiManageAdd = '/dataCenter/apimanage/add',
+  apiManageEdit = '/dataCenter/apimanage/edit',
+  apiManageDelete = '/dataCenter/apimanage/delete',
+  apiManageQueryByID = '/dataCenter/apimanage/queryById',
+}
+// 分站查询接口
+export const apiManageList = (params) =>
+  defHttp.get({
+    url: Api.apiManageList,
+    params,
+  });
+// API接口添加
+// API接口编辑
+export const AddOrEdit = (params, isAdd) => {
+  const url = isAdd ? Api.apiManageAdd : Api.apiManageEdit;
+  return isAdd ? defHttp.post({ url: url, params }) : defHttp.post({ url: url, params });
+};
+// API接口通过id删除
+export const apiManageDelete = (params) => {
+  return defHttp.delete({ url: Api.apiManageDelete, params }, { joinParamsToUrl: true });
+};
+// API接口通过id查询
+export const apiManageQueryByID = (params) =>
+  defHttp.get({
+    url: Api.apiManageQueryByID,
+    params,
+  });

+ 61 - 0
src/views/vent/dataCenter/APICenter/apiManger.data.ts

@@ -0,0 +1,61 @@
+import { FormSchema, BasicColumn } from '/@/components/Table';
+export const tableColumns: BasicColumn[] = [
+  {
+    title: 'API名称',
+    dataIndex: 'apiName',
+    key: 'apiName',
+    align: 'center',
+  },
+  {
+    title: '请求路径',
+    dataIndex: 'reqUrl',
+    key: 'reqUrl',
+    align: 'center',
+  },
+  {
+    title: 'HTTP方法',
+    dataIndex: 'reqMethod',
+    key: 'reqMethod',
+    align: 'center',
+  },
+  {
+    title: '参数',
+    dataIndex: 'reqParam',
+    key: 'reqParam',
+    align: 'center',
+  },
+  {
+    title: '状态',
+    dataIndex: 'apiStatus',
+    key: 'apiStatus',
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    width: 120,
+    align: 'center',
+    slots: { customRender: 'action' },
+  },
+];
+
+export const formAddSchema: FormSchema[] = [
+  {
+    field: 'apiName',
+    label: '接口名称',
+    required: true,
+    component: 'Input',
+  },
+  {
+    field: 'reqUrl',
+    label: '请求路径',
+    required: true,
+    component: 'Input',
+  },
+  {
+    field: 'reqMethod',
+    label: '请求方式',
+    required: true,
+    component: 'Input',
+  },
+];

+ 90 - 0
src/views/vent/dataCenter/APICenter/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="safetyList">
+    <customHeader>数据中心-API管理</customHeader>
+    <div class="device-manager-box">
+      <!-- 分站监测 -->
+      <BasicTable @register="registerTable">
+        <template #tableTitle>
+          <a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
+        </template>
+        <template #action="{ record }">
+          <a class="table-action-link" @click="handlerEdit(record)">编辑</a>
+          <a-popconfirm title="删除内容无法恢复,是否删除" ok-text="确定" cancel-text="取消" @confirm="handleDelete(record)">
+            <a class="table-action-link">删除</a>
+          </a-popconfirm>
+        </template>
+      </BasicTable>
+    </div>
+    <ApiAddModal @register="registerAddModal" @success="reload" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, nextTick, computed, reactive, onMounted, onUnmounted, inject } from 'vue';
+import { BasicTable } from '/@/components/Table';
+import { usePermission } from '/@/hooks/web/usePermission';
+import customHeader from '/@/components/vent/customHeader.vue';
+import { apiManageList, apiManageDelete } from './apiManger.api';
+import { useModal } from '/@/components/Modal';
+import { tableColumns } from './apiManger.data';
+import ApiAddModal from './ApiAddModal.vue';
+import { useListPage } from '/@/hooks/system/useListPage';
+const { hasPermission } = usePermission();
+const [registerAddModal, { openModal: openAddModal }] = useModal();
+// 列表页面公共参数、方法
+const { prefixCls, tableContext } = useListPage({
+  designScope: 'API-list',
+  tableProps: {
+    title: 'Api列表',
+    api: apiManageList,
+    columns: tableColumns,
+    size: 'small',
+    actionColumn: {
+      width: 120,
+    },
+    beforeFetch(params) {
+      return Object.assign(
+        {
+          column: 'createTime',
+        },
+        params
+      );
+    },
+    showActionColumn: false,
+  },
+});
+const [registerTable, { reload, updateTableDataRecord }, { selectedRows }] = tableContext;
+// 注册table数据
+
+// async function getAPIList(params) {
+//   const result = await apiManageList(params);
+//   // tableData.value = result.records;
+// }
+function handleCreate() {
+  openAddModal(true, { isAdd: true });
+  reload();
+}
+async function handlerEdit(record) {
+  openAddModal(true, { record, isAdd: false });
+}
+async function handleDelete(record) {
+  await apiManageDelete({ id: record.id });
+  reload();
+}
+onMounted(async () => {
+  // await getAPIList({
+  //   pageNo: 1,
+  //   pageSize: 1000,
+  // });
+});
+onUnmounted(() => {});
+</script>
+
+<style lang="less" scoped>
+.safetyList {
+  width: calc(100% - 20px);
+  height: calc(100% - 80px);
+  position: relative;
+  margin: 40px 10px 10px 10px;
+}
+</style>

+ 80 - 129
src/views/vent/dataCenter/deviceCenter/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="safetyList">
-    <customHeader>数据中心-设备管理</customHeader>
+    <customHeader>数据中心-设备监测</customHeader>
     <div class="content">
       <a-tabs class="tab-box" v-model:activeKey="activeKey" @change="onChangeTab">
         <a-tab-pane tab="设备监测" key="device" />
@@ -13,11 +13,12 @@
             <div class="device-select">
               <div class="device-select-box">
                 <a-tree
+                  v-if="treeData && treeData.length > 0"
                   :show-line="true"
                   :tree-data="treeData"
                   v-model:selectedKeys="selectedKeys"
-                  :autoExpandParent="true"
                   v-model:expandedKeys="expandedKeys"
+                  :defaultExpandAll="true"
                   @select="onSelect"
                 >
                 </a-tree>
@@ -30,8 +31,8 @@
               size="small"
               :scroll="{ y: 650 }"
               :columns="outerColumns"
-              :data-source="paginatedData2"
-              :pagination="paginationConfig2"
+              :data-source="deviceList"
+              :pagination="false"
               :row-key="(record) => record.id"
               :expand-row-by-click="true"
               :expanded-row-keys="expandedRowKeys"
@@ -59,8 +60,8 @@
                 <a-table
                   size="small"
                   :columns="innerColumns"
-                  :data-source="paginatedData"
-                  :pagination="paginationConfig"
+                  :data-source="monitorList"
+                  :pagination="false"
                   :loading="loadingMap[record.id]"
                   bordered
                   :scroll="{ y: 410 }"
@@ -97,12 +98,14 @@ import { getDeviceTypeList, getDeviceListByType, getDevMonitorListById } from '.
 import { RightCircleTwoTone, DownCircleTwoTone } from '@ant-design/icons-vue';
 import { alignElement } from 'dom-align';
 import { active } from 'sortablejs';
+import { stubTrue } from 'lodash';
 
 const { hasPermission } = usePermission();
 let activeKey = ref('device');
 const treeData = ref<TreeProps['treeData']>([]);
 const selectedKeys = ref<string[]>([]);
 const expandedKeys = ref<string[]>([]);
+const AllNodeKeys = ref<string[]>([]);
 const deviceType = ref(''); // 监测设备类型
 const systemType = ref('');
 const systemID = ref(''); // 系统监测时,系统id
@@ -142,46 +145,46 @@ const paginatedData2 = computed(() => {
   return deviceList.value.slice(start, end);
 });
 // 分页器配置 - 修复响应式问题
-const paginationConfig = computed(() => {
-  return {
-    current: paginationState.value.current,
-    pageSize: paginationState.value.pageSize,
-    total: monitorList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState.value.current = page;
-      paginationState.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState.value.current = 1;
-      paginationState.value.pageSize = size;
-    },
-  };
-});
-const paginationConfig2 = computed(() => {
-  return {
-    current: paginationState2.value.current,
-    pageSize: paginationState2.value.pageSize,
-    total: deviceList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState2.value.current = page;
-      paginationState2.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState2.value.current = 1;
-      paginationState2.value.pageSize = size;
-    },
-  };
-});
+// const paginationConfig = computed(() => {
+//   return {
+//     current: paginationState.value.current,
+//     pageSize: paginationState.value.pageSize,
+//     total: monitorList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState.value.current = page;
+//       paginationState.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState.value.current = 1;
+//       paginationState.value.pageSize = size;
+//     },
+//   };
+// });
+// const paginationConfig2 = computed(() => {
+//   return {
+//     current: paginationState2.value.current,
+//     pageSize: paginationState2.value.pageSize,
+//     total: deviceList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState2.value.current = page;
+//       paginationState2.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState2.value.current = 1;
+//       paginationState2.value.pageSize = size;
+//     },
+//   };
+// });
 // 切换tab页面
 async function onChangeTab(tab) {
   activeKey.value = tab;
@@ -207,96 +210,44 @@ const onSelect: TreeProps['onSelect'] = (keys, e) => {
   }
   getDeviceList(deviceType.value);
 };
-
-async function findTreeDataValue(obj) {
-  const findDeviceType = (data: any[], obj, flag = true) => {
-    return data.find((item: any) => {
-      if (item.children.length > 0) {
-        findDeviceType(item.children, obj);
+// 获取所有节点key的函数
+const getAllNodeKeys = (nodes) => {
+  const keys = [];
+  const traverse = (nodeList) => {
+    nodeList.forEach((node) => {
+      keys.push(node.key);
+      if (node.children && node.children.length > 0) {
+        traverse(node.children);
       }
-      // debugger;
-      if (obj.deviceType && obj.deviceType.startsWith('sys_')) {
-        // debugger;
-        if (item.type == obj.deviceid) {
-          deviceType.value = 'sys';
-          systemID.value = obj.deviceid;
-          selectedKeys.value = [item.key];
-          expandedKeys.value = [item.key];
-          treeNodeTitle.value = item.title;
-        }
-      } else {
-        if (!flag) {
-          if (obj.deviceType && item.type.startsWith(obj.deviceType)) {
-            deviceType.value = item.type;
-            selectedKeys.value = [item.key];
-            expandedKeys.value = [item.key];
-            treeNodeTitle.value = item.title;
-            return true;
-          }
-          return false;
-        } else {
-          if (obj.deviceType && item.type == obj.deviceType) {
-            deviceType.value = item.type;
-            selectedKeys.value = [item.key];
-            expandedKeys.value = [item.key];
-            treeNodeTitle.value = item.title;
-            return true;
-          }
-          return false;
-        }
-      }
-      return false;
     });
   };
-  const flag = findDeviceType(treeData.value, obj);
-  if (!flag) {
-    findDeviceType(treeData.value, obj, false);
-  }
-  // 无类型时
-  if (!treeNodeTitle.value && treeData.value && treeData.value[0] && treeData.value[0]['children']) {
-    const defaultData = treeData.value[0]['children'][0];
-    if (deviceType.value !== defaultData.type) deviceType.value = defaultData.type;
-    selectedKeys.value = [defaultData.key as string];
-    expandedKeys.value = [defaultData.key as string];
-    treeNodeTitle.value = defaultData.title;
-  }
-}
+  traverse(nodes);
+  return keys;
+};
+
 // 获取树形菜单数据
-async function getDeviceType(sysType?) {
-  // if (treeData.value?.length > 0) return;
+async function getDeviceType() {
   const result = await getDeviceTypeList({});
   if (result.length > 0) {
-    const dataSource = <TreeProps['treeData']>[];
+    const dataSource = [];
     let key = '0';
     const getData = (resultList, dataSourceList, keyVal) => {
       resultList.forEach((item, index) => {
-        if (item.deviceType != 'sys' && item.children && item.children.length > 0) {
-          const children = getData(item.children, [], `${keyVal}-${index}`);
-          // 判断关键阻力路线
-          if (item.itemValue.startsWith(sysType) && children[0]) {
-            systemID.value = item.children[0]['itemValue'];
-          }
-
-          dataSourceList.push({
-            children: children,
-            title: item.itemText,
-            key: `${keyVal}-${index}`,
-            type: item.itemValue,
-            parentKey: `${keyVal}`,
-          });
-        } else {
-          dataSourceList.push({
-            children: [],
-            title: item.itemText,
-            key: `${keyVal}-${index}`,
-            type: item.itemValue,
-            parentKey: `${keyVal}`,
-          });
-        }
+        const children = item.children ? getData(item.children, [], `${keyVal}-${index}`) : [];
+        dataSourceList.push({
+          children: children,
+          title: item.itemText,
+          key: `${keyVal}-${index}`,
+          type: item.itemValue,
+          parentKey: `${keyVal}`,
+        });
       });
       return dataSourceList;
     };
+
     treeData.value = getData(result, dataSource, key);
+    // 数据就绪后设置展开key数组
+    expandedKeys.value = getAllNodeKeys(treeData.value);
   }
 }
 // 根据选择设备获取设备列表
@@ -418,8 +369,8 @@ async function refreshData(deviceId: string) {
   monitorList.value = Object.values(result.readData);
 }
 
-onMounted(async () => {
-  await getDeviceType();
+onMounted(() => {
+  getDeviceType();
 });
 onUnmounted(() => {
   if (timer) {
@@ -500,7 +451,7 @@ watch(
         align-items: center;
 
         .left-box {
-          width: 25%;
+          width: 20%;
           height: 100%;
           margin-right: 15px;
           padding: 10px;
@@ -510,7 +461,7 @@ watch(
         }
 
         .right-box {
-          width: calc(75% - 15px);
+          width: calc(80% - 15px);
           height: 100%;
           padding: 10px;
           box-sizing: border-box;
@@ -607,7 +558,7 @@ watch(
 
 .device-select-box {
   margin-top: 60px;
-  width: 208px;
+  width: 220px;
   height: calc(100% - 70px);
   overflow-y: auto;
   color: #fff;
@@ -645,7 +596,7 @@ watch(
   }
 }
 .device-select {
-  width: 250px;
+  width: 280px !important;
   height: calc(100% - 70px);
   background: var(--image-tree-bg) no-repeat;
   position: fixed;

+ 45 - 12
src/views/vent/dataCenter/infoCenter/index.vue

@@ -32,14 +32,14 @@
                 <div class="item-icon icon4"></div>
                 <div>
                   <div class="label">消息总数量(条)</div>
-                  <div class="value status-normal">{{ deviceData.collectTotalNum }}</div>
+                  <div class="value status-normal">{{ formatNumber(deviceData.collectTotalNum) }}</div>
                 </div>
               </div>
               <div class="data-item">
                 <div class="item-icon icon5"></div>
                 <div>
                   <div class="label">共享接口数量</div>
-                  <div class="value status-normal">2</div>
+                  <div class="value status-normal">{{ deviceData.summaryShareAPINum }}</div>
                 </div>
               </div>
             </div>
@@ -59,7 +59,7 @@
           <template #title> 系统数据量排名 </template>
           <template #container>
             <div class="content-wrapper">
-              <a-table size="small" :dataSource="deviceData.collectDataByStationList" :columns="sysDataColumn" :pagination="false" />
+              <a-table size="small" :dataSource="deviceData.collectGroupByDevKindList" :columns="sysDataColumn" :pagination="false" />
             </div>
           </template>
         </infoBox>
@@ -111,8 +111,27 @@
       total_size_gb: 0,
     },
     collectTotalNum: 0,
-    collectDataByStationList: [],
-    collectDataByDayList: [],
+    summaryShareAPINum: 0,
+    collectDataByStationList: [
+      {
+        sub_id: '1',
+        strName: '测试分站',
+        total_num: 0,
+      },
+    ],
+    collectDataByDayList: [
+      {
+        day: '2025-10-29',
+        total_count: 0,
+      },
+    ],
+    collectGroupByDevKindList: [
+      {
+        devKind: 'bundletube',
+        devNum: 0,
+        dataCount: 0,
+      },
+    ],
   });
   // 查看数据
   function viewData(record) {
@@ -132,20 +151,34 @@
   // 获取设备数据接口
   const fetchDeviceData = async () => {
     try {
-      // 获取首页汇总指标数据
-      deviceData.value = await getHomepageSummaryIndexes();
-      // 获取设备接入情况数据
-      const response = await getDeviceAll({
+      const summaryRes = await getHomepageSummaryIndexes();
+      deviceData.value = summaryRes;
+    } catch (error) {
+      console.error('获取首页汇总指标数据失败:', error);
+    }
+
+    try {
+      const deviceRes = await getDeviceAll({
         pageNo: pagination.current,
         pageSize: pagination.pageSize,
       });
-      accessStatusData.value = response.records; // 赋值给响应式变量
-      pagination.total = response.total; // 更新总条数
+      accessStatusData.value = deviceRes.records;
+      pagination.total = deviceRes.total;
     } catch (error) {
-      console.error('获取设备数据失败:', error);
+      console.error('获取设备接入情况数据失败:', error);
     }
   };
 
+  // 数字千位分隔格式化函数
+  const formatNumber = (num: number | string): string => {
+    // 处理非数字或空值的情况
+    if (!num && num !== 0) return '0';
+    // 转换为数字后再处理,避免字符串类型的数字出现问题
+    const number = typeof num === 'number' ? num : parseFloat(num);
+    // 利用正则表达式实现千位分隔
+    return number.toLocaleString('zh-CN');
+  };
+
   // 页面挂载时调用接口获取数据
   onMounted(async () => {
     await fetchDeviceData();

+ 5 - 75
src/views/vent/dataCenter/infoCenter/infoCenter.data.ts

@@ -58,19 +58,18 @@ export const sysDataColumn: BasicColumn[] = [
     },
   },
   {
-    title: '分站id',
-    dataIndex: 'sub_id',
+    title: '设备类别',
+    dataIndex: 'devKind',
     align: 'center',
-    ifShow: false,
   },
   {
-    title: '分站名称',
-    dataIndex: 'strName',
+    title: '设备数量',
+    dataIndex: 'devNum',
     align: 'center',
   },
   {
     title: '数据采集量',
-    dataIndex: 'total_num',
+    dataIndex: 'dataCount',
     align: 'center',
   },
 ];
@@ -87,7 +86,6 @@ export const accessStatusColumn: BasicColumn[] = [
     dataIndex: 'devicekind_dictText',
     align: 'center',
   },
-
   {
     title: '设备接入时间',
     dataIndex: 'createTime',
@@ -131,71 +129,3 @@ export const dailyNumOption: ModuleDataChart = {
   yAxis: [{ show: true, name: '', position: 'left' }],
   series: [{ readFrom: 'collectDataByDayList', xprop: 'day', yprop: 'total_count', label: '' }],
 };
-
-// 模拟设备数据
-export const deviceData = {
-  monitorParamsCount: 1727,
-  deviceCount: 4,
-  databaseDiskUsage: 10165,
-  collectDataByStationList: [
-    {
-      sub_id: '1696417397868986370',
-      strName: '调试http协议束管',
-      total_num: 258,
-    },
-    {
-      sub_id: '1696417397868986370',
-      strName: '调试http协议束管',
-      total_num: 256,
-    },
-    {
-      sub_id: '1696417397868986370',
-      strName: '调试http协议束管',
-      total_num: 254,
-    },
-    {
-      sub_id: '1696417397868986370',
-      strName: '调试http协议束管',
-      total_num: 252,
-    },
-  ],
-  collectTotalNum: 258,
-  collectDataByDayList: [
-    {
-      day: '2025-10-29',
-      total_count: '30',
-    },
-    {
-      day: '2025-10-30',
-      total_count: '165',
-    },
-    {
-      day: '2025-10-31',
-      total_count: '63',
-    },
-    {
-      day: '2025-11-01',
-      total_count: '30',
-    },
-    {
-      day: '2025-11-02',
-      total_count: '165',
-    },
-    {
-      day: '2025-11-03',
-      total_count: '63',
-    },
-    {
-      day: '2025-11-04',
-      total_count: '30',
-    },
-    {
-      day: '2025-11-05',
-      total_count: '165',
-    },
-    {
-      day: '2025-11-06',
-      total_count: '63',
-    },
-  ],
-};

+ 0 - 8
src/views/vent/dataCenter/stationCenter/device.api.ts

@@ -10,20 +10,12 @@ export const subStationList = (params) => defHttp.get({ url: Api.subStationList,
 //根据设备类型获取设备列表
 export const getDeviceListByType = (params) =>
   defHttp.post({
-    headers: {
-      'X-Access-Token':
-        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5ODQxMTcwfQ.fHaM3l-d88tUSLOs8sstXsDuSCn6Agbj_EMZMgV6waw',
-    },
     url: Api.deviceList,
     params,
   });
 //根据设备id获取设备监控数据
 export const getDevMonitorListById = (params) =>
   defHttp.post({
-    headers: {
-      'X-Access-Token':
-        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5ODQxMTcwfQ.fHaM3l-d88tUSLOs8sstXsDuSCn6Agbj_EMZMgV6waw',
-    },
     url: Api.devMonitorList,
     params,
   });

+ 84 - 79
src/views/vent/dataCenter/stationCenter/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="safetyList">
-    <customHeader>数据中心-分站管理</customHeader>
+    <customHeader>数据中心-分站监测</customHeader>
     <div class="content">
       <a-tabs class="tab-box" v-model:activeKey="activeKey" @change="onChangeTab">
         <a-tab-pane tab="分站监测" key="device" />
@@ -23,12 +23,12 @@
               size="small"
               :scroll="{ y: 650 }"
               :columns="outerColumns"
-              :data-source="paginatedData2"
-              :pagination="paginationConfig2"
+              :data-source="deviceList"
+              :pagination="false"
               :row-key="(record) => record.id"
               :expand-row-by-click="true"
-              :expanded-row-keys="expandedRowKeys"
-              @expand="onExpand"
+              :expandedRowKeys="expandedRowKeys"
+              @expand="handleExpand"
             >
               <!-- 自定义展开图标 -->
               <template #expandIcon="{ expanded, onExpand, record }">
@@ -52,8 +52,8 @@
                 <a-table
                   size="small"
                   :columns="innerColumns"
-                  :data-source="paginatedData"
-                  :pagination="paginationConfig"
+                  :data-source="monitorList"
+                  :pagination="false"
                   :loading="loadingMap[record.id]"
                   bordered
                   :scroll="{ y: 410 }"
@@ -93,70 +93,71 @@ const deviceList = ref<any[]>([]);
 const openNum = ref(0);
 const clsoeNum = ref(0);
 const monitorList = ref<any[]>([]); // 监测数据列表
-// 分页参数
-const paginationState = ref({
-  current: 1,
-  pageSize: 10,
-  total: 0,
-});
-const paginationState2 = ref({
-  current: 1,
-  pageSize: 10,
-  total: 0,
-});
-// 计算分页后的数据
-const paginatedData = computed(() => {
-  const start = (paginationState.value.current - 1) * paginationState.value.pageSize;
-  const end = start + paginationState.value.pageSize;
-  return monitorList.value.slice(start, end);
-});
-// 计算分页后的数据
-const paginatedData2 = computed(() => {
-  const start = (paginationState2.value.current - 1) * paginationState2.value.pageSize;
-  const end = start + paginationState2.value.pageSize;
-  return deviceList.value.slice(start, end);
-});
-// 分页器配置 - 修复响应式问题
-const paginationConfig = computed(() => {
-  return {
-    current: paginationState.value.current,
-    pageSize: paginationState.value.pageSize,
-    total: monitorList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState.value.current = page;
-      paginationState.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState.value.current = 1;
-      paginationState.value.pageSize = size;
-    },
-  };
-});
-const paginationConfig2 = computed(() => {
-  return {
-    current: paginationState2.value.current,
-    pageSize: paginationState2.value.pageSize,
-    total: deviceList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState2.value.current = page;
-      paginationState2.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState2.value.current = 1;
-      paginationState2.value.pageSize = size;
-    },
-  };
-});
+const expandedRowKeys = ref([]);
+// // 分页参数
+// const paginationState = ref({
+//   current: 1,
+//   pageSize: 10,
+//   total: 0,
+// });
+// const paginationState2 = ref({
+//   current: 1,
+//   pageSize: 10,
+//   total: 0,
+// });
+// // 计算分页后的数据
+// const paginatedData = computed(() => {
+//   const start = (paginationState.value.current - 1) * paginationState.value.pageSize;
+//   const end = start + paginationState.value.pageSize;
+//   return monitorList.value.slice(start, end);
+// });
+// // 计算分页后的数据
+// const paginatedData2 = computed(() => {
+//   const start = (paginationState2.value.current - 1) * paginationState2.value.pageSize;
+//   const end = start + paginationState2.value.pageSize;
+//   return deviceList.value.slice(start, end);
+// });
+// // 分页器配置 - 修复响应式问题
+// const paginationConfig = computed(() => {
+//   return {
+//     current: paginationState.value.current,
+//     pageSize: paginationState.value.pageSize,
+//     total: monitorList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState.value.current = page;
+//       paginationState.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState.value.current = 1;
+//       paginationState.value.pageSize = size;
+//     },
+//   };
+// });
+// const paginationConfig2 = computed(() => {
+//   return {
+//     current: paginationState2.value.current,
+//     pageSize: paginationState2.value.pageSize,
+//     total: deviceList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState2.value.current = page;
+//       paginationState2.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState2.value.current = 1;
+//       paginationState2.value.pageSize = size;
+//     },
+//   };
+// });
 //获取分站信息
 async function getSubStationList() {
   let res = await subStationList({ pageSize: 1000, pageNo: 1 });
@@ -178,8 +179,6 @@ function cardClick(item, ind) {
   activeIndex1.value = ind;
   getDeviceList(item.id);
 }
-// 当前展开的行key数组
-const expandedRowKeys = ref([]);
 
 // 加载状态映射
 const loadingMap = reactive({});
@@ -258,16 +257,22 @@ const innerColumns = [
 ];
 
 // 切换展开状态
-const toggleExpand = (deviceId) => {
-  const index = expandedRowKeys.value.indexOf(deviceId);
+const toggleExpand = (id) => {
+  const index = expandedRowKeys.value.indexOf(id);
 
   if (index > -1) {
-    // 如果已经展开,则关闭
     expandedRowKeys.value.splice(index, 1);
   } else {
-    // 如果未展开,则打开
-    expandedRowKeys.value.push(deviceId);
-    loadMonitoringData(deviceId);
+    expandedRowKeys.value = [id];
+    loadMonitoringData(id);
+  }
+};
+const handleExpand = (expanded, record) => {
+  // 确保展开状态与 expandedRowKeys 同步
+  if (expanded) {
+    expandedRowKeys.value = [record.id];
+  } else {
+    expandedRowKeys.value = [];
   }
 };
 // 加载监测数据

+ 42 - 0
src/views/vent/dataCenter/user/PasswordModal.vue

@@ -0,0 +1,42 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="修改密码" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" name="PassWordModal" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formPasswordSchema } from './user.data';
+  import { changePassword } from './user.api';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    schemas: formPasswordSchema,
+    showActionButtonGroup: false,
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    //表单赋值
+    await setFieldsValue({ ...data });
+  });
+  //表单提交事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await changePassword(values);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 45 - 0
src/views/vent/dataCenter/user/UserAgentModal.vue

@@ -0,0 +1,45 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :width="800" title="用户代理" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formAgentSchema } from './user.data';
+  import { getUserAgent, saveOrUpdateAgent } from './user.api';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    schemas: formAgentSchema,
+    showActionButtonGroup: false,
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    //查询获取表单数据
+    const res = await getUserAgent({ userName: data.userName });
+    data = res.result ? res.result : data;
+    //表单赋值
+    await setFieldsValue({ ...data });
+  });
+  //表单提交事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await saveOrUpdateAgent(values);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 136 - 0
src/views/vent/dataCenter/user/UserDrawer.vue

@@ -0,0 +1,136 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="adaptiveWidth"
+    @ok="handleSubmit"
+    :showFooter="showFooter"
+    destroyOnClose
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
+import { BasicForm, useForm } from '/@/components/Form/index';
+import { formSchema } from './user.data';
+import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+import { saveOrUpdateUser, getUserRoles, getUserDepartList } from './user.api';
+import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+const attrs = useAttrs();
+const isUpdate = ref(true);
+const rowId = ref('');
+const departOptions = ref([]);
+//表单配置
+const [registerForm, { setProps, resetFields, setFieldsValue, validate, updateSchema }] = useForm({
+  labelWidth: 90,
+  schemas: formSchema,
+  showActionButtonGroup: false,
+});
+// TODO [VUEN-527] https://www.teambition.com/task/6239beb894b358003fe93626
+const showFooter = ref(true);
+//表单赋值
+const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+  await resetFields();
+  showFooter.value = data?.showFooter ?? true;
+  setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
+  isUpdate.value = !!data?.isUpdate;
+  if (unref(isUpdate)) {
+    rowId.value = data.record.id;
+    //租户信息定义成数组
+    if (data.record.relTenantIds && !Array.isArray(data.record.relTenantIds)) {
+      data.record.relTenantIds = data.record.relTenantIds.split(',');
+    } else {
+      //【issues/I56C5I】用户管理中连续点两次编辑租户配置就丢失了
+      //data.record.relTenantIds = [];
+    }
+
+    //查角色/赋值/try catch 处理,不然编辑有问题
+    try {
+      const userRoles = await getUserRoles({ userid: data.record.id });
+      if (userRoles && userRoles.length > 0) {
+        data.record.selectedroles = userRoles;
+      }
+    } catch (error) {}
+
+    //查所属部门/赋值
+    const userDepart = await getUserDepartList({ userId: data.record.id });
+    if (userDepart && userDepart.length > 0) {
+      data.record.selecteddeparts = userDepart;
+      let selectDepartKeys = Array.from(userDepart, ({ key }) => key);
+      data.record.selecteddeparts = selectDepartKeys.join(',');
+      departOptions.value = userDepart.map((item) => {
+        return { label: item.title, value: item.key };
+      });
+    }
+    //负责部门/赋值
+    data.record.departIds && !Array.isArray(data.record.departIds) && (data.record.departIds = data.record.departIds.split(','));
+    //update-begin---author:zyf   Date:20211210  for:避免空值显示异常------------
+    data.record.departIds = data.record.departIds == '' ? [] : data.record.departIds;
+    //update-begin---author:zyf   Date:20211210  for:避免空值显示异常------------
+  }
+  //处理角色用户列表情况(和角色列表有关系)
+  data.selectedroles && (await setFieldsValue({ selectedroles: data.selectedroles }));
+  //编辑时隐藏密码/角色列表隐藏角色信息/我的部门时隐藏所属部门
+  updateSchema([
+    {
+      field: 'password',
+      show: !unref(isUpdate),
+    },
+    {
+      field: 'confirmPassword',
+      ifShow: !unref(isUpdate),
+    },
+    {
+      field: 'selectedroles',
+      show: !data.isRole,
+    },
+    {
+      field: 'departIds',
+      componentProps: { options: departOptions },
+    },
+    {
+      field: 'selecteddeparts',
+      show: !data?.departDisabled ?? false,
+    },
+    {
+      field: 'selectedroles',
+      show: !data?.departDisabled ?? false,
+    },
+  ]);
+  // 无论新增还是编辑,都可以设置表单值
+  if (typeof data.record === 'object') {
+    setFieldsValue({
+      ...data.record,
+    });
+  }
+  // 隐藏底部时禁用整个表单
+  //update-begin-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
+  setProps({ disabled: !showFooter.value });
+  //update-end-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
+});
+//获取标题
+const getTitle = computed(() => (!unref(isUpdate) ? '新增用户' : '编辑用户'));
+const { adaptiveWidth } = useDrawerAdaptiveWidth();
+
+//提交事件
+async function handleSubmit() {
+  try {
+    let values = await validate();
+    setDrawerProps({ confirmLoading: true });
+    values.userIdentity === 1 && (values.departIds = '');
+    let isUpdateVal = unref(isUpdate);
+    //提交表单
+    await saveOrUpdateUser(values, isUpdateVal);
+    //关闭弹窗
+    closeDrawer();
+    //刷新列表
+    emit('success', { isUpdateVal, values });
+  } finally {
+    setDrawerProps({ confirmLoading: false });
+  }
+}
+</script>

+ 77 - 0
src/views/vent/dataCenter/user/UserPermissionDrawer.vue

@@ -0,0 +1,77 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    title="API授权"
+    :width="adaptiveWidth"
+    @ok="handleSubmit"
+    :showFooter="showFooter"
+    destroyOnClose
+  >
+    <BasicTree
+      :checkedKeys="checkedKeys"
+      :expandedKeys="expandedKeys"
+      :selectedKeys="selectedKeys"
+      checkable
+      :tree-data="treeData"
+      ref="treeRef"
+      :clickRowToExpand="false"
+      @check="onCheck"
+      @select="onTreeNodeSelect"
+    >
+      <template #title="{ title, key }">
+        <span v-if="key === '0-0-1-0'" style="color: #1890ff">{{ title }}</span>
+        <template v-else>{{ title }}</template>
+      </template>
+    </BasicTree>
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
+import { BasicTree, TreeItem } from '/src/components/Tree';
+import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+import { getAPILIst } from './user.api';
+import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+const showFooter = ref(true);
+const selectedKeys = ref([]);
+const expandedKeys = ref([]);
+const checkedKeys = ref([]);
+const treeData = [
+  {
+    title: 'parent 1',
+    key: '0-0',
+  },
+  {
+    title: '测试api',
+    key: '0-1',
+  },
+  {
+    title: '测试api1',
+    key: '0-2',
+  },
+  {
+    title: '测试api2',
+    key: '0-3',
+  },
+];
+//表单赋值
+const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+  showFooter.value = data?.showFooter ?? true;
+  setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
+});
+const { adaptiveWidth } = useDrawerAdaptiveWidth();
+
+//提交事件
+async function handleSubmit() {
+  try {
+    setDrawerProps({ confirmLoading: true });
+    closeDrawer();
+    //刷新列表
+    emit('success');
+  } finally {
+    setDrawerProps({ confirmLoading: false });
+  }
+}
+</script>

+ 138 - 0
src/views/vent/dataCenter/user/UserRecycleBinModal.vue

@@ -0,0 +1,138 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="用户回收站" :showOkBtn="false" width="1000px" destroyOnClose>
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <!--插槽:table标题-->
+      <template #tableTitle>
+        <a-dropdown v-if="checkedKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined"></Icon>
+                批量删除
+              </a-menu-item>
+              <a-menu-item key="1" @click="batchHandleRevert">
+                <Icon icon="ant-design:redo-outlined"></Icon>
+                批量还原
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon icon="ant-design:down-outlined"></Icon>
+          </a-button>
+        </a-dropdown>
+      </template>
+      <!--操作栏-->
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction :actions="getTableAction(record)" />
+        </template>
+      </template>
+    </BasicTable>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, toRaw, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { recycleColumns } from './user.data';
+  import { getRecycleBinList, putRecycleBin, deleteRecycleBin } from './user.api';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createConfirm } = useMessage();
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  const checkedKeys = ref<Array<string | number>>([]);
+  const [registerModal] = useModalInner(() => {
+    checkedKeys.value = [];
+  });
+  //注册table数据
+  const [registerTable, { reload }] = useTable({
+    api: getRecycleBinList,
+    columns: recycleColumns,
+    rowKey: 'id',
+    striped: true,
+    useSearchForm: false,
+    showTableSetting: false,
+    clickToRowSelect: false,
+    bordered: true,
+    showIndexColumn: false,
+    pagination: true,
+    tableSetting: { fullScreen: true },
+    canResize: false,
+    actionColumn: {
+      width: 150,
+      title: '操作',
+      dataIndex: 'action',
+      // slots: { customRender: 'action' },
+      fixed: undefined,
+    },
+  });
+  /**
+   * 选择列配置
+   */
+  const rowSelection = {
+    type: 'checkbox',
+    columnWidth: 50,
+    selectedRowKeys: checkedKeys,
+    onChange: onSelectChange,
+  };
+  /**
+   * 选择事件
+   */
+  function onSelectChange(selectedRowKeys: (string | number)[]) {
+    checkedKeys.value = selectedRowKeys;
+  }
+  /**
+   * 还原事件
+   */
+  async function handleRevert(record) {
+    await putRecycleBin({ userIds: record.id }, reload);
+    emit('success');
+  }
+  /**
+   * 批量还原事件
+   */
+  function batchHandleRevert() {
+    handleRevert({ id: toRaw(unref(checkedKeys)).join(',') });
+  }
+  /**
+   * 删除事件
+   */
+  async function handleDelete(record) {
+    await deleteRecycleBin({ userIds: record.id }, reload);
+  }
+  /**
+   * 批量删除事件
+   */
+  function batchHandleDelete() {
+    createConfirm({
+      iconType: 'warning',
+      title: '删除',
+      content: '确定要永久删除吗?删除后将不可恢复!',
+      onOk: () => handleDelete({ id: toRaw(unref(checkedKeys)).join(',') }),
+      onCancel() {},
+    });
+  }
+  //获取操作栏事件
+  function getTableAction(record) {
+    return [
+      {
+        label: '取回',
+        icon: 'ant-design:redo-outlined',
+        popConfirm: {
+          title: '是否确认还原',
+          confirm: handleRevert.bind(null, record),
+        },
+      },
+      {
+        label: '彻底删除',
+        icon: 'ant-design:scissor-outlined',
+        popConfirm: {
+          title: '是否确认删除',
+          confirm: handleDelete.bind(null, record),
+        },
+      },
+    ];
+  }
+</script>

+ 253 - 0
src/views/vent/dataCenter/user/index.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="device-manager-box">
+    <!--引用表格-->
+    <BasicTable @register="registerTable">
+      <!--插槽:table标题-->
+      <template #tableTitle>
+        <a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
+        <!-- <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
+        <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button> -->
+        <!-- <a-button type="primary" @click="handleSyncUser" preIcon="ant-design:sync-outlined"> 同步流程</a-button> -->
+        <!-- <a-button type="primary" @click="openModal(true, {})" preIcon="ant-design:hdd-outlined"> 回收站</a-button> -->
+        <JThirdAppButton biz-type="user" :selected-row-keys="selectedRowKeys" syncToApp syncToLocal @sync-finally="onSyncFinally" />
+        <a-dropdown v-if="selectedRowKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined" />
+                删除
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon icon="mdi:chevron-down" />
+          </a-button>
+        </a-dropdown>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+    </BasicTable>
+    <!--用户抽屉-->
+    <UserDrawer @register="registerDrawer" @success="handleSuccess" />
+    <!--修改密码-->
+    <PasswordModal @register="registerPasswordModal" @success="reload" />
+    <!--用户代理-->
+    <UserAgentModal @register="registerAgentModal" @success="reload" />
+    <!--回收站-->
+    <UserPermissionDrawer @register="registerPermissionModal" @success="reload" />
+  </div>
+</template>
+
+<script lang="ts" name="system-user" setup>
+//ts语法
+import { ref, computed, unref } from 'vue';
+import { BasicTable, TableAction, ActionItem } from '/@/components/Table';
+import UserDrawer from './UserDrawer.vue';
+// import UserRecycleBinModal from './UserRecycleBinModal.vue';
+import PasswordModal from './PasswordModal.vue';
+import UserAgentModal from './UserAgentModal.vue';
+import JThirdAppButton from '/@/components/jeecg/thirdApp/JThirdAppButton.vue';
+import { useDrawer } from '/@/components/Drawer';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { useModal } from '/@/components/Modal';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { columns, searchFormSchema } from './user.data';
+import { list, deleteUser, batchDeleteUser, frozenBatch, syncUser } from './user.api';
+import UserPermissionDrawer from './UserPermissionDrawer.vue';
+// import { usePermission } from '/@/hooks/web/usePermission'
+// const { hasPermission } = usePermission();
+
+const { createMessage, createConfirm } = useMessage();
+
+//注册drawer
+const [registerDrawer, { openDrawer }] = useDrawer();
+//回收站model
+// const [registerModal, { openModal }] = useModal();
+//密码model
+const [registerPasswordModal, { openModal: openPasswordModal }] = useModal();
+//代理人model
+const [registerAgentModal, { openModal: openAgentModal }] = useModal();
+// 授权model
+const [registerPermissionModal, { openDrawer: openPermissionModal }] = useDrawer();
+
+// 列表页面公共参数、方法
+const { prefixCls, tableContext } = useListPage({
+  designScope: 'user-list',
+  tableProps: {
+    title: '用户列表',
+    api: list,
+    columns: columns,
+    size: 'small',
+    formConfig: {
+      // labelWidth: 200,
+      schemas: searchFormSchema,
+    },
+    actionColumn: {
+      width: 120,
+    },
+    beforeFetch(params) {
+      return Object.assign(
+        {
+          column: 'createTime',
+          order: 'desc',
+          thirdType: '_thirdPartyUser',
+        },
+        params
+      );
+    },
+  },
+  // exportConfig: {
+  //   name: '用户列表',
+  //   url: getExportUrl,
+  // },
+  // importConfig: {
+  //   url: getImportUrl,
+  // },
+});
+
+//注册table数据
+const [registerTable, { reload }, { selectedRows, selectedRowKeys }] = tableContext;
+
+/**
+ * 新增事件
+ */
+function handleCreate() {
+  openDrawer(true, {
+    isUpdate: false,
+    showFooter: true,
+  });
+}
+/**
+ * 编辑事件
+ */
+async function handleEdit(record: Recordable) {
+  openDrawer(true, {
+    record,
+    isUpdate: true,
+    showFooter: true,
+  });
+}
+/**
+ * 详情
+ */
+async function handleDetail(record: Recordable) {
+  openDrawer(true, {
+    record,
+    isUpdate: true,
+    showFooter: false,
+  });
+}
+/**
+ * 删除事件
+ */
+async function handleDelete(record) {
+  if ('admin' == record.username) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await deleteUser({ id: record.id }, reload);
+}
+/**
+ * 批量删除事件
+ */
+async function batchHandleDelete() {
+  let hasAdmin = unref(selectedRows).filter((item) => item.username == 'admin');
+  if (unref(hasAdmin).length > 0) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await batchDeleteUser({ ids: selectedRowKeys.value }, () => {
+    selectedRowKeys.value = [];
+    reload();
+  });
+}
+/**
+ * 成功回调
+ */
+function handleSuccess() {
+  reload();
+}
+
+/**
+ * 打开修改密码弹窗
+ */
+function handleChangePassword(username) {
+  openPasswordModal(true, { username });
+}
+/**
+ * 打开代理人弹窗
+ */
+function handleAgentSettings(userName) {
+  openAgentModal(true, { userName });
+}
+/**
+ * 打开授权弹窗
+ */
+function handlePermissionModel() {
+  openPermissionModal(true);
+}
+
+/**
+ * 冻结解冻
+ */
+async function handleFrozen(record, status) {
+  if ('admin' == record.username) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await frozenBatch({ ids: record.id, status: status }, reload);
+}
+/**
+ *同步钉钉和微信回调
+ */
+function onSyncFinally({ isToLocal }) {
+  // 同步到本地时刷新下数据
+  if (isToLocal) {
+    reload();
+  }
+}
+/**
+ * 操作栏
+ */
+function getTableAction(record): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: handleEdit.bind(null, record),
+      // ifShow: () => hasPermission('system:user:edit'),
+    },
+    {
+      label: '授权',
+      onClick: handlePermissionModel.bind(null),
+      // ifShow: () => hasPermission('system:user:edit'),
+    },
+  ];
+}
+/**
+ * 下拉操作栏
+ */
+function getDropDownAction(record): ActionItem[] {
+  return [
+    {
+      label: '详情',
+      onClick: handleDetail.bind(null, record),
+    },
+    {
+      label: '密码',
+      onClick: handleChangePassword.bind(null, record.username),
+    },
+    {
+      label: '删除',
+      popConfirm: {
+        title: '是否确认删除',
+        confirm: handleDelete.bind(null, record),
+      },
+    },
+  ];
+}
+</script>
+
+<style scoped></style>

+ 177 - 0
src/views/vent/dataCenter/user/user.api.ts

@@ -0,0 +1,177 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/sys/user/list',
+  save = '/sys/user/add',
+  edit = '/sys/user/edit',
+  agentSave = '/sys/sysUserAgent/add',
+  agentEdit = '/sys/sysUserAgent/edit',
+  getUserRole = '/sys/user/queryUserRole',
+  duplicateCheck = '/sys/duplicate/check',
+  deleteUser = '/sys/user/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  // importExcel = '/sys/user/importExcel',
+  // exportXls = '/sys/user/exportXls',
+  recycleBinList = '/sys/user/recycleBin',
+  putRecycleBin = '/sys/user/putRecycleBin',
+  deleteRecycleBin = '/sys/user/deleteRecycleBin',
+  allRolesList = '/sys/role/queryall',
+  allTenantList = '/sys/tenant/queryList',
+  allPostList = '/sys/position/list',
+  userDepartList = '/sys/user/userDepartList',
+  changePassword = '/sys/user/changePassword',
+  frozenBatch = '/sys/user/frozenBatch',
+  getUserAgent = '/sys/sysUserAgent/queryByUserName',
+  syncUser = '/act/process/extActProcess/doSyncUser',
+  allAPIList = '/compute/apiManage/list',
+}
+
+/**
+ * 查询api列表
+ * @param params
+ */
+export const getAPILIst = (params) => {
+  defHttp.get({
+    url: Api.allAPIList,
+    params,
+  });
+};
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) =>
+  defHttp.get({
+    url: Api.list,
+    params,
+  });
+
+/**
+ * 用户角色接口
+ * @param params
+ */
+export const getUserRoles = (params) => defHttp.get({ url: Api.getUserRole, params }, { errorMessageMode: 'none' });
+
+/**
+ * 删除用户
+ */
+export const deleteUser = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteUser, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteUser = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdateUser = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.post({
+    url: url,
+    params,
+  });
+};
+/**
+ * 唯一校验
+ * @param params
+ */
+export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck, params }, { isTransformResponse: false });
+/**
+ * 获取全部角色
+ * @param params
+ */
+export const getAllRolesList = (params) => defHttp.get({ url: Api.allRolesList, params });
+/**
+ * 获取全部租户
+ */
+export const getAllTenantList = (params) => defHttp.get({ url: Api.allTenantList, params });
+/**
+ * 获取指定用户负责部门
+ */
+export const getUserDepartList = (params) => defHttp.get({ url: Api.userDepartList, params }, { successMessageMode: 'none' });
+/**
+ * 获取全部职务
+ */
+export const getAllPostList = (params) => {
+  return new Promise((resolve) => {
+    defHttp.get({ url: Api.allPostList, params }).then((res) => {
+      resolve(res.records);
+    });
+  });
+};
+/**
+ * 回收站列表
+ * @param params
+ */
+export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params });
+/**
+ * 回收站还原
+ * @param params
+ */
+export const putRecycleBin = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.putRecycleBin, params }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 回收站删除
+ * @param params
+ */
+export const deleteRecycleBin = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteRecycleBin, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 修改密码
+ * @param params
+ */
+export const changePassword = (params) => {
+  return defHttp.put({ url: Api.changePassword, params });
+};
+/**
+ * 冻结解冻
+ * @param params
+ */
+export const frozenBatch = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.frozenBatch, params }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 获取用户代理
+ * @param params
+ */
+export const getUserAgent = (params) => defHttp.get({ url: Api.getUserAgent, params }, { isTransformResponse: false });
+/**
+ * 保存或者更新用户代理
+ * @param params
+ */
+export const saveOrUpdateAgent = (params) => {
+  const url = params.id ? Api.agentEdit : Api.agentSave;
+  return defHttp.post({ url: url, params });
+};
+
+/**
+ * 用户同步流程
+ * @param params
+ */
+export const syncUser = () => defHttp.put({ url: Api.syncUser });

+ 246 - 0
src/views/vent/dataCenter/user/user.data.ts

@@ -0,0 +1,246 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { getAllRolesList, getAllTenantList } from './user.api';
+import { rules } from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '用户账号',
+    dataIndex: 'username',
+    width: 80,
+  },
+  {
+    title: '用户姓名',
+    dataIndex: 'realname',
+    width: 80,
+  },
+  {
+    title: '令牌',
+    dataIndex: 'thirdId',
+    width: 150,
+  },
+  {
+    title: '有效期',
+    dataIndex: 'birthday',
+    width: 80,
+  },
+  {
+    title: '状态',
+    dataIndex: 'status_dictText',
+    width: 80,
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '用户账号',
+    dataIndex: 'username',
+    width: 100,
+  },
+  {
+    title: '用户姓名',
+    dataIndex: 'realname',
+    width: 100,
+  },
+  {
+    title: '头像',
+    dataIndex: 'avatar',
+    width: 80,
+    customRender: render.renderAvatar,
+  },
+  {
+    title: '性别',
+    dataIndex: 'sex',
+    width: 80,
+    sorter: true,
+    customRender: ({ text }) => {
+      return render.renderDict(text, 'sex');
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '账号',
+    field: 'username',
+    component: 'JInput',
+    colProps: { span: 6 },
+  },
+  {
+    label: '名字',
+    field: 'realname',
+    component: 'JInput',
+    colProps: { span: 6 },
+  },
+  // {
+  //   label: '性别',
+  //   field: 'sex',
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'sex',
+  //     placeholder: '请选择性别',
+  //     stringToNumber: true,
+  //   },
+  //   colProps: { span: 6 },
+  // },
+  // {
+  //   label: '手机号码',
+  //   field: 'phone',
+  //   component: 'Input',
+  //   colProps: { span: 6 },
+  // },
+  // {
+  //   label: '用户状态',
+  //   field: 'status',
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'user_status',
+  //     placeholder: '请选择状态',
+  //     stringToNumber: true,
+  //   },
+  //   colProps: { span: 6 },
+  // },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    dynamicDisabled: ({ values }) => {
+      return !!values.id;
+    },
+    dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'username', model, schema, true),
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+  {
+    label: '用户姓名',
+    field: 'realname',
+    required: true,
+    component: 'Input',
+  },
+  {
+    label: '有效期',
+    field: 'birthday',
+    component: 'DatePicker',
+  },
+  {
+    label: '第三方用户',
+    field: 'thirdType',
+    component: 'Input',
+    defaultValue: '_thirdPartyUser',
+  },
+];
+
+export const formPasswordSchema: FormSchema[] = [
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    componentProps: { readOnly: true },
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    componentProps: {
+      placeholder: '请输入登录密码',
+    },
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+];
+
+export const formAgentSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'userName',
+    label: '用户名',
+    component: 'Input',
+    componentProps: {
+      readOnly: true,
+      allowClear: false,
+    },
+  },
+  {
+    field: 'agentUserName',
+    label: '代理人用户名',
+    required: true,
+    component: 'JSelectUser',
+    componentProps: {
+      rowKey: 'username',
+      labelKey: 'realname',
+      maxSelectCount: 10,
+    },
+  },
+  {
+    field: 'startTime',
+    label: '代理开始时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理开始时间',
+    },
+  },
+  {
+    field: 'endTime',
+    label: '代理结束时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理结束时间',
+    },
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'JDictSelectTag',
+    defaultValue: '1',
+    componentProps: {
+      dictCode: 'valid_status',
+      type: 'radioButton',
+    },
+  },
+];

+ 54 - 0
src/views/vent/dataCenter/user/userDetails.vue

@@ -0,0 +1,54 @@
+<template>
+  <Description @register="register" class="mt-4" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Description, DescItem, useDescription } from '/@/components/Description/index';
+  const mockData = {
+    username: 'test',
+    nickName: 'VB',
+    age: '123',
+    phone: '15695909xxx',
+    email: '190848757@qq.com',
+    addr: '厦门市思明区',
+    sex: '男',
+    certy: '3504256199xxxxxxxxx',
+    tag: 'orange',
+  };
+  const schema: DescItem[] = [
+    {
+      field: 'username',
+      label: '用户名',
+    },
+    {
+      field: 'nickName',
+      label: '昵称',
+      render: (curVal, data) => {
+        return `${data.username}-${curVal}`;
+      },
+    },
+    {
+      field: 'phone',
+      label: '联系电话',
+    },
+    {
+      field: 'email',
+      label: '邮箱',
+    },
+    {
+      field: 'addr',
+      label: '地址',
+    },
+  ];
+  export default defineComponent({
+    components: { Description },
+    setup() {
+      const [register] = useDescription({
+        title: 'useDescription',
+        data: mockData,
+        schema: schema,
+      });
+      return { mockData, schema, register };
+    },
+  });
+</script>

+ 5 - 2
src/views/vent/deviceManager/configurationTable/types.ts

@@ -1,4 +1,3 @@
-import { data } from 'emoji-mart-vue-fast/data/apple.json';
 export interface Config {
   /** 模块的名称 */
   moduleName: string;
@@ -98,7 +97,9 @@ export interface ModuleData {
         | 'partition'
         | 'selector_dual_chart'
         | 'radio_label'
-        | 'button_list';
+        | 'button_list'
+        | 'card_list'
+        | 'generalist';
       /** 分区大小 */
       basis: string;
       overflow?: boolean;
@@ -334,6 +335,8 @@ export interface ModuleDataChart extends ReadFrom {
 export interface ModuleDataTable extends ReadFrom {
   /** 表格的预设样式 */
   type: 'A' | 'B' | 'C' | 'D';
+  /** 是否自动滚动 */
+  autoScroll?: boolean;
   /** 核心配置,每个表格列对应一项 */
   columns: {
     /** 数据说明,注意该项不支持 formatter 格式 */

+ 7 - 0
src/views/vent/home/configurable/components/ModuleBD.vue

@@ -134,6 +134,13 @@
 <style scoped lang="less">
   @import '/@/design/theme.less';
 
+  @{theme-green} {
+    .dane-bd {
+      --image-module-title: url('@/assets/images/themify/green/home-container/configurable/firehome/module-title.png');
+      --image-module-title-long: url('@/assets/images/themify/green/home-container/configurable/firehome/module-title-long.png');
+    }
+  }
+
   @{theme-deepblue} {
     .dane-bd {
       --image-module-title: url('@/assets/images/themify/deepblue/home-container/configurable/firehome/module-title.png');

+ 1 - 1
src/views/vent/home/configurable/components/MonitorBar.vue

@@ -125,7 +125,7 @@
 <style lang="less" scoped>
   .middata {
     // margin-top: 7px;
-    padding: 5px 0px 5px 50px;
+    padding: 5px 0px 5px 60px;
     width: calc(100% - 10px);
     height: 65px;
     margin-top: 4px;

+ 402 - 368
src/views/vent/home/configurable/components/content.vue

@@ -3,37 +3,62 @@
   <!-- 主体内容部分 -->
   <div class="content">
     <!-- 背景 -->
-    <img v-if="background.show && background.type === 'image'" class="content__background image__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>
+    <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"
+      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' }">
+      <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 v-if="config.pageType == 'vent_New'" style="padding-top: 11%"
-            class="content__module content__module1 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
+            v-if="config.pageType == 'vent_New'"
+            style="padding-top: 11%"
+            class="content__module content__module1 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>
-          <div v-else-if="config.pageType == 'New_fire'"
-            class="content__module flex flex-justify-around flex-items-center flex-wrap">
-            <MiniBoardNew v-for="item in config.items" :key="item.prop" :label="item.label" :value="item.value"
-              :type="config.type" :layout="config.layout" />
+          <div v-else-if="config.pageType == 'New_fire'" class="content__module flex flex-justify-around flex-items-center flex-wrap">
+            <MiniBoardNew
+              v-for="item in config.items"
+              :key="item.prop"
+              :label="item.label"
+              :value="item.value"
+              :type="config.type"
+              :layout="config.layout"
+            />
           </div>
           <div v-else 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" />
+            <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 v-if="config.pageType == 'New_dust'" class="content__module_dust" :chart-config="config.config"
-            :chart-data="config.data" />
+          <CustomChart v-if="config.pageType == 'New_dust'" class="content__module_dust" :chart-config="config.config" :chart-data="config.data" />
           <CustomChart v-else class="content__module" :chart-config="config.config" :chart-data="config.data" />
         </template>
         <!-- 通常列表部分 -->
@@ -57,8 +82,7 @@
         </template>
         <!-- 复杂列表部分 -->
         <template v-if="config.name === 'gallery_list'">
-          <GalleryList class="content__module" :type="config.type" :list-config="config.items"
-            :gallery-config="config.galleryItems" />
+          <GalleryList class="content__module" :type="config.type" :list-config="config.items" :gallery-config="config.galleryItems" />
         </template>
         <!-- 复杂列表部分 -->
         <template v-if="config.name === 'complex_list'">
@@ -66,22 +90,28 @@
         </template>
         <!-- 表格部分,这部分通常是占一整个模块的 -->
         <template v-if="config.name === 'table'">
-          <CustomTable class="content__module text-center overflow-auto" :type="config.type" :columns="config.columns"
-            :data="config.data" />
+          <CustomTable
+            class="content__module text-center overflow-auto"
+            :type="config.type"
+            :columns="config.columns"
+            :auto-scroll="config.autoScroll"
+            :data="config.data"
+          />
         </template>
         <template v-if="config.name === 'tabs'">
-          <CustomTabs class="content__module" :type="config.type" :tab-config="config.items"
-            :overflow="config.overflow" />
+          <CustomTabs class="content__module" :type="config.type" :tab-config="config.items" :overflow="config.overflow" />
         </template>
         <template v-if="config.name === 'blast_delta'">
-          <BlastDelta v-if="config.pageType === 'New_fire'" class="content__moduleFire" :pos-monitor="config.data"
-            :canvasSize="{ width: 250, height: 200 }" />
-          <BlastDelta v-else class="content__module" :pos-monitor="config.data"
-            :canvasSize="{ width: 250, height: 200 }" />
+          <BlastDelta
+            v-if="config.pageType === 'New_fire'"
+            class="content__moduleFire"
+            :pos-monitor="config.data"
+            :canvasSize="{ width: 250, height: 200 }"
+          />
+          <BlastDelta v-else 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" />
+          <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" />
@@ -94,8 +124,13 @@
           <SelectCs :devicedata="config.data" :setLabelData="config.config.setLabelConfig" />
         </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" />
+          <MeasureDetail
+            class="content__module"
+            :show-title="false"
+            :composite-data="config.data"
+            :topconfig="config.config.topconfig"
+            :btnconfig="config.config.btnconfig"
+          />
         </template>
         <template v-if="config.name === 'partition'">
           <Partition class="content__module" :type="config.type" :label="config.label" :icon="config.icon" />
@@ -107,15 +142,14 @@
           <RadioLabel class="content__module" :type="config.config.type" :config="config.config" />
         </template>
         <template v-if="config.name === 'button_list'">
-          <ButtonList class="content__module" :type="config.config.type" :config="config.config"
-            :buttonList="config.config.buttonList" />
+          <ButtonList class="content__module" :type="config.config.type" :config="config.config" :buttonList="config.config.buttonList" />
         </template>
         <template v-if="config.name === 'card_list'">
           <cardList class="content__module" :cardData="config.data" />
         </template>
-          <template v-else-if="config.name === 'generalist'">
-            <generalList class="content__module" :generalData="config.data"></generalList>
-          </template>
+        <template v-else-if="config.name === 'generalist'">
+          <generalList class="content__module" :generalData="config.data" />
+        </template>
         <!-- <template v-if="config.key === 'fire_control'">
         <FIreControl class="content__module" />
       </template>
@@ -127,405 +161,405 @@
   </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.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 SelectCs from './preset/SelectCs.vue';
-import MiniBoardNew from './detail/MiniBoard-New.vue';
-import Partition from './preset/partition.vue';
-import SelectorDualChart from './preset/selectorDualChart.vue';
-import RadioLabel from './preset/radioLabel.vue';
-import ButtonList from './preset/buttonList.vue';
-import cardList from './preset/cardList.vue'
-import generalList from './preset/generalList.vue'
-
-// import FIreWarn from './preset/FIreWarn.vue';
-// import FIreControl from './preset/FIreControl.vue';
+  import { computed } from 'vue';
+  import {
+    CommonItem,
+    Config,
+    // ModuleDataBoard,
+    // ModuleDataChart,
+    // ModuleDataList,
+    // ModuleDataPreset,
+    // ModuleDataTable,
+  } from '../../../deviceManager/configurationTable/types';
+  import MiniBoard from './detail/MiniBoard.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 SelectCs from './preset/SelectCs.vue';
+  import MiniBoardNew from './detail/MiniBoard-New.vue';
+  import Partition from './preset/partition.vue';
+  import SelectorDualChart from './preset/selectorDualChart.vue';
+  import RadioLabel from './preset/radioLabel.vue';
+  import ButtonList from './preset/buttonList.vue';
+  import cardList from './preset/cardList.vue';
+  import generalList from './preset/generalList.vue';
 
-const props = defineProps<{
-  data: any;
-  moduleData: Config['moduleData'];
-  chartData: any;
-}>();
+  // import FIreWarn from './preset/FIreWarn.vue';
+  // import FIreControl from './preset/FIreControl.vue';
 
-const { background, layout } = props.moduleData;
+  const props = defineProps<{
+    data: any;
+    moduleData: Config['moduleData'];
+    chartData: any;
+  }>();
 
-// 获取当原始配置带 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),
-    };
-  });
-}
+  const { background, layout } = props.moduleData;
 
-// 获取当 List 组件配置带 items 项时的最终 items 配置
-function getListItems(raw: any, items: CommonItem[], mapFromData?: boolean) {
-  if (mapFromData && Array.isArray(raw)) {
-    return raw.map((data) => {
-      const item = items[0];
+  // 获取当原始配置带 items 项时的最终 items 配置
+  function getItems(raw, items: CommonItem[]) {
+    return items.map((i) => {
       return {
-        ...item,
-        label: getFormattedText(data, item.label, item.trans),
-        value: getFormattedText(data, item.value, item.trans),
+        ...i,
+        label: getFormattedText(raw, i.label, i.trans),
+        value: getFormattedText(raw, i.value, i.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) || [];
-  const partition = clone(props.moduleData.partition) || [];
-  const mockData = clone(props.chartData) || [];
-  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,
+  // 获取当 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,
-          ...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);
+          label: getFormattedText(data, item.label, item.trans),
+          value: getFormattedText(data, item.value, item.trans),
+        };
+      });
+    }
+    return getItems(raw, items);
+  }
 
-        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);
+  /** 根据配置里的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) || [];
+    const partition = clone(props.moduleData.partition) || [];
+    const mockData = clone(props.chartData) || [];
+    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);
 
-        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),
-                  };
-                }),
-              };
-            }),
+            items: getItems(data, cfg.items),
           });
-        } else {
+          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: 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),
-                  };
-                }),
-              };
-            }),
+            items: getListItems(data, cfg.items, cfg.mapFromData),
           });
+          break;
         }
-        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);
+        case 'gallery': {
+          const cfg = gallery.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),
-                  };
-                }),
-              };
-            }),
+            items: getItems(data, cfg.items),
           });
-        } else {
+          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: 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),
-                  };
-                }),
-              };
-            }),
+            items: getItems(data, cfg.items),
+            galleryItems: getItems(data, cfg.galleryItems),
           });
+          break;
         }
-        break;
-      }
-      case 'chart': {
-        const cfg = chart.shift();
-        if (cfg?.type == 'scatter') {
+        case 'tabs': {
+          const cfg = tabs.shift();
           if (!cfg) break;
-          const data = getData(mockData, cfg.readFrom, cfg.parser);
+          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?.type == 'scatter') {
+            if (!cfg) break;
+            const data = getData(mockData, cfg.readFrom, cfg.parser);
+
+            arr.push({
+              ...item,
+              config: cfg,
+              data,
+            });
+            break;
+          } else {
+            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,
-            config: cfg,
+            columns: cfg.columns,
             data,
           });
           break;
-        } else {
+        }
+        case 'partition': {
+          const cfg = partition.shift();
           if (!cfg) break;
           const data = getData(refData, cfg.readFrom, cfg.parser);
           arr.push({
+            overflow: true,
             ...item,
-            config: cfg,
             data,
+            ...cfg,
           });
           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;
-      }
-      case 'partition': {
-        const cfg = partition.shift();
-        if (!cfg) break;
-        const data = getData(refData, cfg.readFrom, cfg.parser);
-        arr.push({
-          overflow: true,
-          ...item,
-          data,
-          ...cfg,
-        });
-        break;
-      }
-      default: {
-        const cfg = preset.shift();
-        if (!cfg) break;
-        const data = getData(refData, cfg.readFrom, cfg.parser);
+        default: {
+          const cfg = preset.shift();
+          if (!cfg) break;
+          const data = getData(refData, cfg.readFrom, cfg.parser);
 
-        arr.push({
-          ...item,
-          data,
-          config: cfg,
-        });
-        break;
+          arr.push({
+            ...item,
+            data,
+            config: cfg,
+          });
+          break;
+        }
       }
-    }
-    // console.log(arr,'arr---')
-    return arr;
-  }, []);
-});
+      // console.log(arr,'arr---')
+      return arr;
+    }, []);
+  });
 </script>
 <style lang="less" scoped>
-@import '@/design/theme.less';
+  @import '@/design/theme.less';
 
-.content {
-  height: calc(100% - 30px);
-  position: relative;
-  // z-index: -2;
-  display: flex;
-  flex-direction: column;
-  overflow-y: auto; // 这里会导致样式无故添加滚动条
-  overflow-x: hidden;
-}
+  .content {
+    height: calc(100% - 30px);
+    position: relative;
+    // z-index: -2;
+    display: flex;
+    flex-direction: column;
+    overflow-y: auto; // 这里会导致样式无故添加滚动条
+    overflow-x: hidden;
+  }
 
-.content__background {
-  width: 100%;
-  height: 100%;
-  position: absolute;
-  top: 0;
-  left: 0;
-  z-index: 0;
-  object-fit: fill;
-}
+  .content__background {
+    width: 100%;
+    height: 100%;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 0;
+    object-fit: fill;
+  }
 
-.image__background {
-  width: 35%;
-  height: 61%;
-  left: 30%;
-}
+  .image__background {
+    width: 35%;
+    height: 61%;
+    left: 30%;
+  }
 
-.content__module {
-  // margin-top: 5px;
-  // margin-bottom: 5px;
-  width: 100%;
-  height: 100%;
-}
+  .content__module {
+    // margin-top: 5px;
+    // margin-bottom: 5px;
+    width: 100%;
+    height: 100%;
+  }
 
-.content__module1 {
-  background: url('@/assets/images/vent/homeNew/databg/4.png');
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-  height: 129px;
-  margin-top: 20%;
-}
+  .content__module1 {
+    background: url('@/assets/images/vent/homeNew/databg/4.png');
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    height: 129px;
+    margin-top: 20%;
+  }
 
-.content__moduleFire {
-  width: 100%;
-  height: 100%;
-  margin-left: -24% !important;
-}
+  .content__moduleFire {
+    width: 100%;
+    height: 100%;
+    margin-left: -24% !important;
+  }
 
-.content__module_dust {
-  background: url('@/assets/images/vent/homeNew/bottomBg.png');
-  background-repeat: no-repeat;
-  background-size: 100% 100%;
-  width: 100%;
-  height: 100%;
-}
+  .content__module_dust {
+    background: url('@/assets/images/vent/homeNew/bottomBg.png');
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
+    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;
-}
+  // .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;
-}
+  ::-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:not(.zxm-select-customize-input) .zxm-select-selector) {
+    /* background-color: transparent; */
+    color: #fff;
+  }
 
-:deep(.zxm-select-arrow) {
-  color: #fff;
-}
+  :deep(.zxm-select-arrow) {
+    color: #fff;
+  }
 
-:deep(.zxm-select-selection-item) {
-  color: #fff !important;
-}
+  :deep(.zxm-select-selection-item) {
+    color: #fff !important;
+  }
 
-:deep(.zxm-select-selection-placeholder) {
-  color: #fff !important;
-}
+  :deep(.zxm-select-selection-placeholder) {
+    color: #fff !important;
+  }
 
-:deep(.dialog-overlay) {
-  width: 100%;
-  height: 100%;
-  position: unset;
-  box-shadow: unset;
-}
+  :deep(.dialog-overlay) {
+    width: 100%;
+    height: 100%;
+    position: unset;
+    box-shadow: unset;
+  }
 
-::-webkit-scrollbar {
-  width: 5px !important;
-}
+  ::-webkit-scrollbar {
+    width: 5px !important;
+  }
 
-::-webkit-scrollbar-thumb {
-  width: 5px !important;
-}
+  ::-webkit-scrollbar-thumb {
+    width: 5px !important;
+  }
 </style>

+ 9 - 1
src/views/vent/home/configurable/components/detail/ComplexList.vue

@@ -46,7 +46,15 @@
     @import '@/design/theme.less';
     @import '@/design/theme.less';
     /* Timeline 相关的样式 */
-
+    @{theme-green} {
+      .list {
+      // --image-img-3: url(/@/assets/images/themify/deepblue/home-container/configurable/firehome/img-3.png);
+      // --image-img-7: url(/@/assets/images/themify/deepblue/home-container/configurable/firehome/img-7.png);
+      // --image-img-8: url(/@/assets/images/themify/deepblue/home-container/configurable/firehome/img-8.png);
+      --image-img-9: url(/@/assets/images/themify/green/home-container/configurable/firehome/img-9.png);
+      // --image-list_bg_1: url(/@/assets/images/themify/deepblue/home-container/configurable/dusthome/list_bg_1.png);
+      }
+    }
     @{theme-deepblue} {
       .list {
       --image-img-3: url(/@/assets/images/themify/deepblue/home-container/configurable/firehome/img-3.png);

+ 17 - 1
src/views/vent/home/configurable/components/detail/CustomList.vue

@@ -43,7 +43,23 @@
   @import '/@/design/theme.less';
   @import '/@/design/theme.less';
   /* Timeline 相关的样式 */
-
+  @{theme-green} {
+    .list {
+      // --image-list_bg_default: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_default.png);
+      // --image-triangle_icon: url(/@/assets/images/themify/deepblue/home-container/configurable/triangle_icon.png);
+      // --image-list_bg_b: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_b.png);
+      // --image-deco_1: url(/@/assets/images/themify/deepblue/home-container/configurable/deco_1.png);
+      // --image-list_bg_c: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_c.png);
+      // --image-list_bg_defflip: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_defflip.png);
+      // --image-list_bg_d: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_d.png);
+      // --image-list_bg_s: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_s.png);
+      // --image-list_bg_e: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_e.png);
+      --image-list: url(/@/assets/images/themify/green/home-container/configurable/list_bg_f.png);
+      --image-list_bg_h: url(/@/assets/images/themify/green/home-container/configurable/list_bg_h.png);
+      --image-list_bg_i: url(/@/assets/images/themify/green/home-container/configurable/list_bg_i.png);
+      // --image-list_bg_r: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_r.png);
+    }
+  }
   @{theme-deepblue} {
     .list {
       --image-list_bg_default: url(/@/assets/images/themify/deepblue/home-container/configurable/list_bg_default.png);

+ 17 - 2
src/views/vent/home/configurable/components/detail/CustomTable.vue

@@ -3,7 +3,7 @@
     <div class="table__content_label" :class="`table__content_label_${type}`">
       <div class="label-t" v-for="(item, index) in columns" :key="`svvhbcth-${index}`" :style="{ flexBasis }">{{ item.name }}</div>
     </div>
-    <div class="table__content_list" :class="`table__content_list_${type}`">
+    <div ref="scrollRef" class="table__content_list" :class="`table__content_list_${type}`">
       <div class="table__content_list_row" v-for="(item, index) in data" :key="`svvhbct-${index}`">
         <div v-for="(t, i) in columns" :key="`svvhbctr-${i}`" :style="{ flexBasis }" :class="`table__content__list_item_${type}`">
           <slot :name="t.prop" :scope="item">
@@ -15,13 +15,15 @@
   </div>
 </template>
 <script lang="ts" setup>
-  import { computed, defineProps } from 'vue';
+  import { computed, defineProps, ref } from 'vue';
   import _ from 'lodash';
+  import { useAutoScroll } from '/@/hooks/core/useAutoScroll';
 
   let props = withDefaults(
     defineProps<{
       /** B | C */
       type: string;
+      autoScroll: boolean;
       /** 列表表头配置,每个prop都有其对应的slot来提供定制化功能 */
       columns: { prop: string; name: string }[];
       data: any[];
@@ -29,12 +31,18 @@
     }>(),
     {
       type: 'B',
+      autoScroll: false,
       columns: () => [],
       data: () => [],
       defaultValue: '-',
     }
   );
 
+  const scrollRef = ref(null);
+  if (props.autoScroll) {
+    useAutoScroll(scrollRef);
+  }
+
   const flexBasis = computed(() => {
     return Math.fround(100 / props.columns.length) + '%';
   });
@@ -53,6 +61,13 @@
     src: url('/@/assets/font/douyuFont.otf');
   }
 
+  @{theme-green} {
+    .table__content {
+      --image-content-label: url(/@/assets/images/themify/green/company/content-label.png);
+      --image-content-text: url('/@/assets/images/themify/green/company/content-text.png');
+      --image-list-head: url('/@/assets/images/themify/green/home-container/configurable/firehome/list-head.png');
+    }
+  }
   @{theme-deepblue} {
     .table__content {
       --image-content-label: url(/@/assets/images/themify/deepblue/company/content-label.png);

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

@@ -106,6 +106,29 @@
   @import '/@/design/theme.less';
   @import '/@/design/theme.less';
 
+  @{theme-green} {
+    .mini-board {
+      // --image-areaNew: url('/@/assets/images/vent/homeNew/databg/1.png');
+      // --image-areaNew1: url('/@/assets/images/vent/homeNew/databg/2.png');
+      // --image-areaNew2: url('/@/assets/images/vent/homeNew/databg/3.png');
+      // --image-areaNew3: url('/@/assets/images/vent/homeNew/databg/8.png');
+      // --image-areaNew4: url('/@/assets/images/vent/homeNew/databg/7.png');
+      // --image-area3: url('/@/assets/images/themify/deepblue/company/area3.png');
+      // --image-areaR: url('/@/assets/images/themify/deepblue/company/areaR.png');
+      // --image-areaT: url('/@/assets/images/themify/deepblue/company/areaT.png');
+      // --image-value-bg: url('/@/assets/images/themify/deepblue/vent/value-bg.png');
+      // --image-value-S: url('/@/assets/images/themify/deepblue/vent/value-S.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-board_bg_1: url('/@/assets/images/themify/deepblue/home-container/configurable/board_bg_1.png');
+      --image-miehuo: url('/@/assets/images/themify/green/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');
+    }
+  }
   @{theme-deepblue} {
     .mini-board {
       --image-areaNew: url('/@/assets/images/vent/homeNew/databg/1.png');

+ 10 - 1
src/views/vent/home/configurable/components/preset/partition.vue

@@ -42,7 +42,16 @@
 <style lang="less" scoped>
   @import '/@/design/theme.less';
   @import '/@/design/theme.less';
-
+  @{theme-green} {
+    .partition-block {
+      --image-partition-bg: url('/@/assets/images/themify/green/home-container/configurable/partition-bg-a.png');
+    }
+  }
+  @{theme-deepblue} {
+    .partition-block {
+      --image-partition-bg: url('/@/assets/images/home-container/configurable/tashanhome/partition-bg-a.png');
+    }
+  }
   .partition-block {
     --image-partition-bg: url('/@/assets/images/home-container/configurable/tashanhome/partition-bg-a.png');
     display: flex;

+ 2 - 2
src/views/vent/home/configurable/configurable.data.tashan.ts

@@ -586,7 +586,7 @@ export const testConfigTSFire: Config[] = [
             { name: '氮气', prop: 'readData.n2val' },
             { name: '二氧化碳', prop: 'readData.co2val' },
             { name: '一氧化碳', prop: 'readData.coval' },
-            { name: '瓦斯', prop: 'readData.gasval' },
+            { name: '甲烷', prop: 'readData.gasval' },
             { name: '氧气', prop: 'readData.o2val' },
             { name: '报警等级', prop: 'syswarnLevel_str' },
           ],
@@ -648,7 +648,7 @@ export const testConfigTSFire: Config[] = [
             { name: '氮气', prop: 'readData.n2val' },
             { name: '二氧化碳', prop: 'readData.co2val' },
             { name: '一氧化碳', prop: 'readData.coval' },
-            { name: '瓦斯', prop: 'readData.gasval' },
+            { name: '甲烷', prop: 'readData.gasval' },
             { name: '氧气', prop: 'readData.o2val' },
             { name: '报警等级', prop: 'syswarnLevel_str' },
           ],

+ 508 - 12
src/views/vent/home/configurable/configurable.data.ts

@@ -4,6 +4,452 @@ import { fanControlState1 } from '../../monitorManager/fanLocalMonitor1/fanLocal
 // import { BDdustMock, BDfireMock } from './configurable.data.bd';
 import { getThemifyImagesURL } from '/@/utils/ui';
 
+export const testConfigVentSsl: Config[] = [
+  {
+    deviceType: 'fanmain',
+    moduleName: '主通风机系统',
+    pageType: 'vent',
+    moduleData: {
+      header: {
+        show: true,
+        readFrom: '',
+        selector: {
+          show: true,
+          value: '${strinstallpos}',
+        },
+        slot: {
+          show: true,
+          value: '运行风机:${readData.Fan1StartStatus}',
+          trans: {
+            '1': '一号',
+            '0': '二号',
+          },
+        },
+      },
+      background: {
+        show: true,
+        type: 'video',
+        link: '/video/video.mp4',
+      },
+      layout: {
+        direction: 'column',
+        items: [
+          {
+            name: 'board',
+            basis: '33%',
+          },
+        ],
+      },
+      board: [
+        {
+          type: 'A',
+          readFrom: '',
+          layout: 'val-top',
+          items: [
+            {
+              label: '风量(m³/min)',
+              value: '${flow_merge}',
+            },
+            {
+              label: '负压(Pa)',
+              value: '${fy_merge}',
+            },
+            {
+              label: '漏风率(%)',
+              value: '${leakage}',
+            },
+          ],
+        },
+      ],
+      list: [],
+      chart: [],
+      table: [],
+      gallery: [],
+      complex_list: [],
+      gallery_list: [],
+      preset: [],
+      to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=fanmain',
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:60px;left:0;',
+    },
+  },
+  {
+    deviceType: 'fanlocal',
+    moduleName: '局部通风机系统',
+    pageType: 'vent',
+    moduleData: {
+      header: {
+        show: true,
+        readFrom: '',
+        selector: {
+          show: true,
+          value: '${strinstallpos}',
+        },
+        slot: {
+          show: true,
+          value: '运行风机:${readData.Fan1StartStatus}',
+          trans: {
+            '1': '一号',
+            '0': '二号',
+          },
+        },
+      },
+      background: {
+        show: true,
+        type: 'video',
+        link: '/video/video.mp4',
+      },
+      layout: {
+        direction: 'column',
+        items: [
+          {
+            name: 'board',
+            basis: '33%',
+          },
+        ],
+      },
+      board: [
+        {
+          type: 'A',
+          readFrom: '',
+          layout: 'val-top',
+          items: [
+            {
+              label: '风量(m³/min)',
+              value: '${flow_merge}',
+            },
+            {
+              label: '负压(Pa)',
+              value: '${fy_merge}',
+            },
+            {
+              label: '漏风率(%)',
+              value: '${leakage}',
+            },
+          ],
+        },
+      ],
+      list: [],
+      chart: [],
+      table: [],
+      gallery: [],
+      complex_list: [],
+      gallery_list: [],
+      preset: [],
+      to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=fanlocal',
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:350px;left:0;',
+    },
+  },
+  {
+    deviceType: '',
+    moduleName: '通风设施远程控制',
+    pageType: 'vent',
+    moduleData: {
+      header: {
+        show: false,
+        readFrom: '',
+        selector: {
+          show: false,
+          value: '',
+        },
+        slot: {
+          show: false,
+          value: '',
+        },
+      },
+      background: {
+        show: true,
+        type: 'video',
+        link: '/video/video.mp4',
+      },
+      layout: {
+        direction: 'row',
+        items: [],
+      },
+      board: [],
+      list: [],
+      chart: [],
+      table: [],
+      gallery: [],
+      complex_list: [],
+      gallery_list: [],
+      preset: [],
+      to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=gate_xinJianFuXieJin',
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:640px;left:0;',
+    },
+  },
+  {
+    deviceType: 'sys_wind',
+    moduleName: '风量监测',
+    pageType: 'vent_new',
+    moduleData: {
+      header: {
+        show: false,
+        readFrom: '',
+        selector: {
+          show: false,
+          value: '',
+        },
+        slot: {
+          show: false,
+          value: '',
+        },
+      },
+      background: {
+        show: false,
+        type: 'video',
+        link: '',
+      },
+      layout: {
+        direction: 'row',
+        items: [
+          {
+            name: 'chart',
+            basis: '100%',
+          },
+        ],
+      },
+      board: [],
+      list: [],
+      table: [],
+      preset: [],
+      gallery: [],
+      complex_list: [],
+      gallery_list: [],
+      chart: [
+        {
+          type: 'bar',
+          readFrom: '',
+          legend: { show: true },
+          xAxis: [{ show: true }],
+          yAxis: [
+            { show: true, name: '风量(m³/min)', position: 'left' },
+            { show: true, name: '风速(m/s)', position: 'right' },
+          ],
+          dataZoom: [
+            {
+              show: false,
+              end: 20,
+            },
+          ],
+          series: [
+            { readFrom: 'sys_wind', xprop: 'strinstallpos', yprop: 'readData.m3', label: '风量' },
+            { readFrom: 'sys_wind', xprop: 'strinstallpos', yprop: 'readData.va', label: '风速' },
+          ],
+        },
+      ],
+      to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=windrect',
+    },
+    showStyle: {
+      size: 'width:1100px;height:280px;',
+      version: '原版',
+      position: 'top:640px;left:410px;',
+    },
+  },
+  {
+    deviceType: '',
+    moduleName: '风窗监测',
+    pageType: 'vent',
+    moduleData: {
+      header: {
+        show: false,
+        readFrom: '',
+        selector: {
+          show: false,
+          value: '',
+        },
+        slot: {
+          show: false,
+          value: '',
+        },
+      },
+      background: {
+        show: true,
+        type: 'video',
+        link: '/video/video.mp4',
+      },
+      layout: {
+        direction: 'row',
+        items: [],
+      },
+      board: [],
+      list: [],
+      chart: [],
+      table: [],
+      gallery: [],
+      complex_list: [],
+      gallery_list: [],
+      preset: [],
+      to: '/micro-vent-3dModal/dashboard/analysis?type=tunMonitor&deviceType=gate_xinJianFuXieJin',
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:60px;right:0;',
+    },
+  },
+  {
+    deviceType: 'safetymonitor',
+    moduleName: '安全监控系统',
+    pageType: 'fire',
+    moduleData: {
+      header: {
+        show: false,
+        readFrom: '',
+        selector: {
+          show: false,
+          value: '',
+        },
+        slot: {
+          show: false,
+          value: '',
+        },
+      },
+      background: {
+        show: false,
+        type: 'video',
+        link: '',
+      },
+      layout: {
+        direction: 'row',
+        items: [
+          {
+            name: 'table',
+            basis: '100%',
+          },
+        ],
+      },
+      board: [],
+      chart: [],
+      gallery: [],
+      gallery_list: [],
+      table: [
+        {
+          type: 'C',
+          readFrom: 'safetymonitor',
+          columns: [
+            {
+              name: '测点位置',
+              prop: 'strinstallpos',
+            },
+            {
+              name: '数据',
+              prop: 'readData.V',
+            },
+            {
+              name: '单位',
+              prop: 'readData.unit',
+            },
+          ],
+        },
+      ],
+      list: [],
+      complex_list: [],
+      preset: [],
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:350px;right:0;',
+    },
+  },
+  {
+    deviceType: 'warn',
+    moduleName: '预警监测',
+    pageType: 'vent',
+    moduleData: {
+      header: {
+        show: true,
+        readFrom: '',
+        selector: {
+          show: false,
+          value: '',
+        },
+        slot: {
+          show: true,
+          value: '网络异常:${warn[0].netstatus.val}',
+        },
+      },
+      background: {
+        show: false,
+        type: 'video',
+        link: '',
+      },
+      layout: {
+        direction: 'row',
+        items: [
+          {
+            name: 'list',
+            basis: '100%',
+          },
+        ],
+      },
+      board: [],
+      list: [
+        {
+          type: 'timeline',
+          readFrom: 'warn[0]',
+          items: [
+            {
+              label: '报警',
+              value: '${alarm.val}',
+              info: '',
+              color: 'red',
+            },
+            {
+              label: '重大风险预警',
+              value: '${red.val}',
+              info: '',
+              color: 'red',
+            },
+            {
+              label: '较大风险预警',
+              value: '${orange.val}',
+              info: '',
+              color: 'orange',
+            },
+            {
+              label: '一般风险预警',
+              value: '${yellow.val}',
+              info: '',
+              color: 'yellow',
+            },
+            {
+              label: '低风险预警',
+              value: '${blue.val}',
+              info: '',
+              color: 'blue',
+            },
+          ],
+        },
+      ],
+      preset: [],
+      table: [],
+      gallery: [],
+      chart: [],
+      gallery_list: [],
+      complex_list: [],
+      to: '/monitorChannel/monitor-alarm-home',
+    },
+    showStyle: {
+      size: 'width:420px;height:280px;',
+      version: '原版',
+      position: 'top:640px;right:0;',
+    },
+  },
+];
+
 export const testConfigVent: Config[] = [
   {
     deviceType: 'fanmain',
@@ -4408,7 +4854,7 @@ export let menuList: any[] = [
   { name: '通风预警', MenuItemList: [] },
   { name: '火灾预警', MenuItemList: [] },
   { name: '粉尘预警', MenuItemList: [] },
-]
+];
 export const testConfigBDFire: Config[] = [
   {
     deviceType: 'fireManageInfo',
@@ -5527,9 +5973,8 @@ export const testConfigElectro: Config[] = [
           show: true,
           value: '${strinstallpos}',
           toggleIcon: true,
-
         },
-        
+
         slot: {
           show: false,
           value: '',
@@ -5558,8 +6003,7 @@ export const testConfigElectro: Config[] = [
       complex_list: [],
       preset: [
         {
-          readFrom: 'cardData',
-         
+          readFrom: 'datalist',
         },
       ],
       to: '/ventilate/warn/home',
@@ -5604,10 +6048,64 @@ export const testConfigElectro: Config[] = [
             basis: '70%',
           },
         ],
-
       },
       board: [],
-      list: [],
+      list: [
+        {
+          type: 'N',
+          readFrom: 'arrayCount',
+          items: [
+            {
+              label: '分区1',
+              value: '${coVal}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区2',
+              value: '${co2Val}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区3',
+              value: '${o2Val}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区4',
+              value: '${tempVal}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区5',
+              value: '${coVal}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区6',
+              value: '${co2Val}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区7',
+              value: '${o2Val}',
+              color: 'blue',
+              info: '',
+            },
+            {
+              label: '分区8',
+              value: '${tempVal}',
+              color: 'blue',
+              info: '',
+            },
+          ],
+        },
+      ],
       chart: [
         {
           type: 'line',
@@ -5619,7 +6117,7 @@ export const testConfigElectro: Config[] = [
             { readFrom: 'chartData', xprop: 'time', yprop: 'coRealTime', label: 'CO实时值' },
             { readFrom: 'chartData', xprop: 'time', yprop: 'coWarn', label: 'CO报警阈值' },
           ],
-        }
+        },
       ],
       table: [],
       gallery: [],
@@ -5628,9 +6126,8 @@ export const testConfigElectro: Config[] = [
       preset: [
         {
           readFrom: 'tempData',
-         
-        }
-        ],
+        },
+      ],
       to: '',
     },
     showStyle: {
@@ -5639,5 +6136,4 @@ export const testConfigElectro: Config[] = [
       position: 'top:0px;right:0px;',
     },
   },
-
 ];

+ 11 - 0
src/views/vent/home/configurable/fireBD.vue

@@ -141,6 +141,17 @@
     src: url('../../../../assets/font/douyuFont.otf');
   }
 
+  @{theme-green} {
+    .company-home {
+      // --image-bg: url('@/assets/images/themify/deepblue/home-container/configurable/firehome/bg.png');
+      --image-fire-title: url(/@/assets/images/themify/green/vent/vent-header7.png);
+      --image-qkjaq: url('/@/assets/images/themify/green/home-container/configurable/firehome/qkjaq.png');
+      // --image-common-border2: url('/@/assets/images/themify/deepblue/home-container/configurable/firehome/common-border2.png');
+      --image-znzjxt: url(/@/assets/images/themify/green/home-container/configurable/firehome/znzjxt.png);
+      --image-znzdxt: url(/@/assets/images/themify/green/home-container/configurable/firehome/znzdxt.png);
+    }
+  }
+
   @{theme-deepblue} {
     .company-home {
       --image-bg: url('@/assets/images/themify/deepblue/home-container/configurable/firehome/bg.png');

+ 23 - 14
src/views/vent/home/configurable/fireTS.vue

@@ -175,9 +175,9 @@
           label: device.strinstallpos, // 选项标签:设备
         })),
       };
-      const targetIndicators: Array<keyof Pick<BundletubeHistoryItem, 'ch2val' | 'chval' | 'co2val' | 'coval' | 'gasval' | 'o2val'>> = [
-        'ch2val',
-        'chval',
+      const targetIndicators: Array<keyof Pick<BundletubeHistoryItem, 'temperature' | 'n2val' | 'co2val' | 'coval' | 'gasval' | 'o2val'>> = [
+        'temperature',
+        'n2val',
         'co2val',
         'coval',
         'gasval',
@@ -220,11 +220,11 @@
   };
   interface BundletubeHistoryItem {
     time: string; // 时间戳(如"2025-08-23 07:56:35")
-    ch2val: string | number; // 甲烷2
-    chval: string | number; // 甲烷
+    temperature: string | number; // 温度
+    n2val: string | number; // 氮气
     co2val: string | number; // 二氧化碳值
     coval: string | number; // 一氧化碳值
-    gasval: string | number; // 瓦斯
+    gasval: string | number; // 甲烷
     o2val: string | number; // 氧气值
   }
   // 定义chartData中seriesData的单个元素类型
@@ -234,7 +234,7 @@
   }
   interface ChartDataItem {
     deviceID: string; // 设备id
-    sensorId: 'ch2val' | 'chval' | 'co2val' | 'coval' | 'gasval' | 'o2val'; // 指标ID(固定6个)
+    sensorId: 'temperature' | 'n2val' | 'co2val' | 'coval' | 'gasval' | 'o2val'; // 指标ID(固定6个)
     seriesData: SeriesDataItem[]; // 时间+数值数组
   }
   // 处理selectorConfig1:遍历datalist,提取deviceID和strinstallpos
@@ -249,12 +249,12 @@
   // 处理selectorConfig2:固定6个指标ID及对应中文标签
   const selectorConfig2 = {
     options: [
-      { value: 'o2val', label: '氧气' },
-      { value: 'coval', label: '一氧化碳' },
+      { value: 'temperature', label: '温度' },
+      { value: 'n2val', label: '氮气' },
       { value: 'co2val', label: '二氧化碳' },
-      { value: 'chval', label: '甲烷' },
-      { value: 'ch2val', label: '乙烷' },
-      { value: 'gasval', label: '瓦斯' },
+      { value: 'coval', label: '一氧化碳' },
+      { value: 'gasval', label: '甲烷' },
+      { value: 'o2val', label: '氧气' },
     ],
   };
   // 最终生成的图表数据
@@ -269,9 +269,18 @@
 
   @font-face {
     font-family: 'douyuFont';
-    src: url('../../../../assets/font/douyuFont.otf');
+    src: url('/@/assets/font/douyuFont.otf');
+  }
+  @{theme-green} {
+    .company-home {
+      --image-fire-title: url(/@/assets/images/themify/green/vent/vent-header2.png);
+    }
+  }
+  @{theme-deepblue} {
+    .company-home {
+      --image-fire-title: url(/@/assets/images/vent/vent-header1.png);
+    }
   }
-
   .company-home {
     --image-fire-title: url(/@/assets/images/vent/vent-header1.png);
     --image-common-border1: url('/@/assets/images/home-container/configurable/minehome/common-border1.png');

+ 10 - 1
src/views/vent/home/configurable/fireWZ.vue

@@ -102,7 +102,16 @@
     font-family: 'douyuFont';
     src: url('../../../../assets/font/douyuFont.otf');
   }
-
+  @{theme-green} {
+    .company-home {
+      // --image-bg: url('@/assets/images/themify/deepblue/home-container/configurable/firehome/bg.png');
+      --image-fire-title: url(/@/assets/images/themify/green/vent/vent-header7.png);
+      --image-qkjaq: url('/@/assets/images/themify/green/home-container/configurable/firehome/qkjaq.png');
+      // --image-common-border2: url('/@/assets/images/themify/deepblue/home-container/configurable/firehome/common-border2.png');
+      --image-znzjxt: url(/@/assets/images/themify/green/home-container/configurable/firehome/znzjxt.png);
+      --image-znzdxt: url(/@/assets/images/themify/green/home-container/configurable/firehome/znzdxt.png);
+    }
+  }
   @{theme-deepblue} {
     .company-home {
       --image-bg: url('@/assets/images/themify/deepblue/home-container/configurable/firehome/bg.png');

+ 303 - 0
src/views/vent/home/configurable/ventV6.vue

@@ -0,0 +1,303 @@
+<!-- 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">
+        <div class="main-title">{{ mainTitle }}</div>
+      </div>
+      <a class="ant-dropdown-link module-dropdown" @click.prevent="showBar = !showBar">
+        全矿井通风检测
+        <CaretDownOutlined />
+      </a>
+      <MonitorBar
+        v-if="showBar"
+        class="module-monitor-bar"
+        :style="{ width: `${barWidth}px`, left: `calc(50% - ${Math.floor(barWidth / 2)}px)` }"
+        :is-data-real-time="isDataRealTime"
+        :data="data"
+      />
+      <!-- <a-dropdown class="module-dropdown" :class="{ 'module-dropdown-original': isOriginal }" :trigger="['click']" placement="bottomRight">
+        <template #overlay>
+        </template>
+      </a-dropdown> -->
+
+      <!-- 采用定位方式以避免出现各个模块隐藏时其他模块下移的问题 -->
+      <template v-if="isOriginal">
+        <ModuleOriginal
+          v-for="cfg in configs"
+          :key="cfg.deviceType"
+          :show-style="cfg.showStyle"
+          :module-data="cfg.moduleData"
+          :module-name="cfg.moduleName"
+          :device-type="cfg.deviceType"
+          :data="data"
+          :visible="true"
+        />
+      </template>
+      <template v-else-if="isCommon">
+        <ModuleCommon
+          v-for="cfg in configs"
+          :key="cfg.deviceType"
+          :show-style="cfg.showStyle"
+          :module-data="cfg.moduleData"
+          :module-name="cfg.moduleName"
+          :device-type="cfg.deviceType"
+          :data="data"
+          :visible="true"
+        />
+      </template>
+      <template v-else>
+        <!-- 下面是正常展示的各新版模块 -->
+        <ModuleEnhanced
+          v-for="cfg in enhancedConfigs"
+          :key="cfg.deviceType"
+          :visible="cfg.visible"
+          :show-style="cfg.showStyle"
+          :module-data="cfg.moduleData"
+          :module-name="cfg.moduleName"
+          :device-type="cfg.deviceType"
+          :data="data"
+          @close="cfg.visible = false"
+        />
+        <!-- 下面是用于呼出已隐藏的模块的按钮 -->
+        <div class="flex pos-absolute bottom-10px left-60px z-3">
+          <div v-for="(item, i) in hiddenList" :key="`vvhchg${i}`">
+            <AButton class="module-trigger-button" @click="item.visible = true">{{ item.moduleName }}</AButton>
+          </div>
+        </div>
+      </template>
+      <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>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, onUnmounted, ref, watch } from 'vue';
+  // import { CaretDownOutlined } from '@ant-design/icons-vue';
+  import MonitorBar from './components/MonitorBar.vue';
+  import { useInitConfigs, useInitPage } from './hooks/useInit';
+  import ModuleEnhanced from './components/ModuleEnhanced.vue';
+  import ModuleOriginal from './components/ModuleOriginal.vue';
+  import ModuleCommon from './components/ModuleCommon.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 { testConfigVentSsl } from './configurable.data';
+  import { computed } from 'vue';
+  // import { testConfigVent, testConfigVentRealtime } from './configurable.data';
+
+  const { sysDataType = 'monitor', title = '智能通风管控系统' } = useGlobSetting();
+  const { configs, 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);
+  /** 最长的模块的长度(一版指正下方模块) */
+  const barWidth = computed<number>(() => {
+    for (let index = 0; index < configs.value.length; index++) {
+      const element = configs.value[index];
+      const { size, position } = element.showStyle;
+      const [_, width] = size.match(/width:([0-9]+)px/) || [];
+      if (position.includes('bottom') || parseInt(width) > 800) {
+        return parseInt(width);
+      }
+    }
+    return 1000;
+  });
+  let interval: number | undefined;
+
+  function switchDataMode() {
+    isDataRealTime.value = !isDataRealTime.value;
+    refresh();
+  }
+
+  function refresh() {
+    fetchConfigs(isDataRealTime.value ? 'vent_realtime' : 'vent').then(() => {
+      // configs.value = isDataRealTime.value ? testConfigVentRealtime : testConfigVent;
+      // configs.value = testConfigVentSsl;
+      updateEnhancedConfigs(configs.value);
+
+      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: url('@/assets/images/home-container/configurable/firehome/bg.png') no-repeat center;
+
+    .top-bg {
+      width: 100%;
+      height: 56px;
+      background: var(--image-modal-top) no-repeat center;
+      position: absolute;
+      z-index: 1;
+      .main-title {
+        height: 56px;
+        font-family: 'douyuFont';
+        font-size: 20px;
+        letter-spacing: 2px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+    }
+
+    // .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>