Explorar o código

红沙泉露天煤矿火灾监测-接口对接-提交

lxh hai 1 día
pai
achega
2c36ae5854
Modificáronse 58 ficheiros con 2540 adicións e 1266 borrados
  1. 2 1
      .env.development
  2. BIN=BIN
      public/texture/earth.jpg
  3. BIN=BIN
      public/texture/earth3.png
  4. BIN=BIN
      public/video/fanLocal.webm
  5. 3 0
      src/assets/icons/back-bg.svg
  6. BIN=BIN
      src/assets/images/company/home-pageBg.png
  7. BIN=BIN
      src/assets/images/fireNew/FireBj.png
  8. BIN=BIN
      src/assets/images/gasInjection/login-bg.png
  9. BIN=BIN
      src/assets/images/gif/fanlocal.gif
  10. BIN=BIN
      src/assets/images/home-container/configurable/minehome/bg.png
  11. BIN=BIN
      src/assets/images/home-warn/bg.png
  12. BIN=BIN
      src/assets/images/themify/deepblue/home-container/configurable/gasInjection/login-bg.png
  13. BIN=BIN
      src/assets/images/vent/homeNew/bg.png
  14. BIN=BIN
      src/assets/images/vent/loginDataCenter/dataCenterBg.png
  15. BIN=BIN
      src/assets/images/vent/loginNeiye/bg.png
  16. BIN=BIN
      src/assets/images/zd-model.png
  17. BIN=BIN
      src/assets/video/fanLocal.mp4
  18. BIN=BIN
      src/assets/video/gate.mp4
  19. BIN=BIN
      src/assets/video/mainFan.mp4
  20. BIN=BIN
      src/assets/video/output_video.mp4
  21. 5 5
      src/views/sys/login/Login.vue
  22. 2 2
      src/views/sys/login/LoginDataCenter.vue
  23. 2 2
      src/views/sys/login/LoginForm.vue
  24. 2 2
      src/views/sys/login/LoginNeiye.vue
  25. 182 150
      src/views/system/role/index.vue
  26. 246 214
      src/views/system/user/index.vue
  27. 24 19
      src/views/vent/cad/dwgViewer.vue
  28. 0 88
      src/views/vent/home/configurable/components/ModuleGasInject.vue
  29. 0 1
      src/views/vent/home/configurable/hooks/helper.ts
  30. 1 1
      src/views/vent/home/configurable/linkConfiguration.vue
  31. 39 27
      src/views/vent/monitorManager/hsqHome/components/AlarmMonitor.vue
  32. 55 11
      src/views/vent/monitorManager/hsqHome/components/CommonTable.vue
  33. 50 26
      src/views/vent/monitorManager/hsqHome/components/CustomChart.vue
  34. 6 27
      src/views/vent/monitorManager/hsqHome/components/CustomGalleryNew.vue
  35. 145 86
      src/views/vent/monitorManager/hsqHome/components/DeviceCenter.vue
  36. 58 12
      src/views/vent/monitorManager/hsqHome/components/DeviceLeft.vue
  37. 44 43
      src/views/vent/monitorManager/hsqHome/components/DeviceRight.vue
  38. 1 13
      src/views/vent/monitorManager/hsqHome/components/EnvironmentMonitor.vue
  39. 263 0
      src/views/vent/monitorManager/hsqHome/components/LinkLogPanel.vue
  40. 203 137
      src/views/vent/monitorManager/hsqHome/components/LinkRuleEditor.vue
  41. 140 0
      src/views/vent/monitorManager/hsqHome/components/LinkRuleStats.vue
  42. 218 0
      src/views/vent/monitorManager/hsqHome/components/LinkRuleTable.vue
  43. 3 1
      src/views/vent/monitorManager/hsqHome/components/SystemCenBottom.vue
  44. 5 1
      src/views/vent/monitorManager/hsqHome/components/SystemLeftBottom.vue
  45. 21 13
      src/views/vent/monitorManager/hsqHome/components/SystemRightTop.vue
  46. 37 28
      src/views/vent/monitorManager/hsqHome/components/WarnCenTop.vue
  47. 104 48
      src/views/vent/monitorManager/hsqHome/components/WarnLeftTop.vue
  48. 119 69
      src/views/vent/monitorManager/hsqHome/components/WarnRightTop.vue
  49. 21 8
      src/views/vent/monitorManager/hsqHome/components/basicDevice.vue
  50. 8 4
      src/views/vent/monitorManager/hsqHome/components/navMenu.vue
  51. 29 21
      src/views/vent/monitorManager/hsqHome/deviceManger.vue
  52. 123 75
      src/views/vent/monitorManager/hsqHome/fireMonitor.vue
  53. 57 3
      src/views/vent/monitorManager/hsqHome/hsqHome.api.ts
  54. 57 32
      src/views/vent/monitorManager/hsqHome/hsqHome.data.ts
  55. 40 28
      src/views/vent/monitorManager/hsqHome/index.vue
  56. 84 0
      src/views/vent/monitorManager/hsqHome/linkConfiguration.vue
  57. 71 48
      src/views/vent/monitorManager/hsqHome/systemManger.vue
  58. 70 20
      src/views/vent/monitorManager/hsqHome/warnMonitor.vue

+ 2 - 1
.env.development

@@ -6,7 +6,8 @@ VITE_PUBLIC_PATH = /
 
 # 跨域代理,您可以配置多个 ,请注意,没有换行符
 #VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
-VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://39.97.59.228:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://39.97.59.228:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"]]
+# VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://39.97.59.228:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://39.97.59.228:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"]]
+VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://39.97.59.228:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://182.92.126.35:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"]]
 # VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://39.97.59.228:9999"],["/upload","http://39.97.59.228:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://39.97.59.228:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/tun2D", "http://127.0.0.1:8088/micro-vent-2dModal/tun2D"],["/dataCenter", "http://39.97.59.228:10999"]]
 # VITE_PROXY = [["/jeecgsystem","http://192.168.183.88:9999"],["/upload","http://192.168.183.88:9999/upload"],["/documents", "http://192.168.183.88:9050"],["/modelreq", "http://192.168.183.88:9999"],["/webRtc", "http://192.168.183.88:8051"]]
 # VITE_PROXY = [["/jeecgsystem","http://10.10.150.72:9999"],["/upload","http://localhost:3300/upload"],["/documents", "http://10.10.150.72:9050"],["/modelreq", "http://10.10.150.72:9999"],["/webRtc", "http://192.168.183.216:8051"]]

BIN=BIN
public/texture/earth.jpg


BIN=BIN
public/texture/earth3.png


BIN=BIN
public/video/fanLocal.webm


+ 3 - 0
src/assets/icons/back-bg.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="26.273" height="26.099" viewBox="0 0 26.273 26.099">
+  <path id="路径_57984" data-name="路径 57984" d="M107.917,71.526a2.112,2.112,0,0,0-2.831,0l-11.4,10.817a1.1,1.1,0,0,0-.145,1.452c.363.581,1.089.363,1.525,0l.218-.218v11.18a2.343,2.343,0,0,0,.581,1.525,2.71,2.71,0,0,0,1.67.726h6.171V90.692a3.211,3.211,0,0,1,.363-1.089,1.38,1.38,0,0,1,.944-.363h2.831a3.211,3.211,0,0,1,1.089.363,1.783,1.783,0,0,1,.363,1.089v6.389h6.316a2.343,2.343,0,0,0,1.525-.581,2.2,2.2,0,0,0,.581-1.525V83.8a1.2,1.2,0,0,0,1.525,0,.778.778,0,0,0,.363-.726,1.383,1.383,0,0,0-.363-.726Z" transform="translate(-93.332 -70.982)" fill="#fff"/>
+</svg>

BIN=BIN
src/assets/images/company/home-pageBg.png


BIN=BIN
src/assets/images/fireNew/FireBj.png


BIN=BIN
src/assets/images/gasInjection/login-bg.png


BIN=BIN
src/assets/images/gif/fanlocal.gif


BIN=BIN
src/assets/images/home-container/configurable/minehome/bg.png


BIN=BIN
src/assets/images/home-warn/bg.png


BIN=BIN
src/assets/images/themify/deepblue/home-container/configurable/gasInjection/login-bg.png


BIN=BIN
src/assets/images/vent/homeNew/bg.png


BIN=BIN
src/assets/images/vent/loginDataCenter/dataCenterBg.png


BIN=BIN
src/assets/images/vent/loginNeiye/bg.png


BIN=BIN
src/assets/images/zd-model.png


BIN=BIN
src/assets/video/fanLocal.mp4


BIN=BIN
src/assets/video/gate.mp4


BIN=BIN
src/assets/video/mainFan.mp4


BIN=BIN
src/assets/video/output_video.mp4


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

@@ -41,8 +41,8 @@
 
   const formRef = ref();
   let loginForm = reactive({
-    account: 'zhuqi',
-    password: 'zhuqi123zhuqi',
+    account: 'hsq',
+    password: 'hsq123hsq',
     inputCode: '',
   });
   const { validForm } = useFormValid(formRef);
@@ -103,18 +103,18 @@
 
   @{theme-deepblue} {
     .Login {
-      --image-login-bg: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/login-bg.png');
+      // --image-login-bg: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/login-bg.png');
       --image-login-bd: url('@/assets/images/themify/deepblue/home-container/configurable/gasInjection/login-border.png');
     }
   }
 
   .Login {
-    --image-login-bg: url('@/assets/images/gasInjection/login-bg.png');
+    // --image-login-bg: url('@/assets/images/gasInjection/login-bg.png');
     --image-login-bd: url('@/assets/images/gasInjection/login-border.png');
     position: relative;
     width: 100%;
     height: 100%;
-    background: var(--image-login-bg) no-repeat;
+    // background: var(--image-login-bg) no-repeat;
     background-size: 100% 100%;
 
     .login-container {

+ 2 - 2
src/views/sys/login/LoginDataCenter.vue

@@ -43,7 +43,7 @@ const handleForgotPwd = () => {
 .login-container {
   width: 100%;
   height: 100%;
-  background: url('@/assets/images/vent/loginDataCenter/dataCenterBg.png') no-repeat center;
+  /* background: url('@/assets/images/vent/loginDataCenter/dataCenterBg.png') no-repeat center; */
   background-size: 100% 100%;
   overflow: hidden;
   position: relative;
@@ -128,4 +128,4 @@ const handleForgotPwd = () => {
   background: #2563eb;
   box-shadow: 0 0 15px rgba(59, 130, 246, 0.6);
 }
-</style>
+</style>

+ 2 - 2
src/views/sys/login/LoginForm.vue

@@ -63,8 +63,8 @@
   const rememberMe = ref(false);
 
   const formData = reactive({
-    account: 'zhuqi',
-    password: 'zhuqi123zhuqi',
+    account: 'hsq',
+    password: 'hsq123hsq',
     inputCode: '',
   });
   const randCodeData = reactive({

+ 2 - 2
src/views/sys/login/LoginNeiye.vue

@@ -43,7 +43,7 @@ const handleForgotPwd = () => {
 .login-container {
   width: 100%;
   height: 100%;
-  background: url('@/assets/images/vent/loginNeiye/bg.png') no-repeat center;
+  /* background: url('@/assets/images/vent/loginNeiye/bg.png') no-repeat center; */
   background-size: 100% 100%;
   overflow: hidden;
   position: relative;
@@ -129,4 +129,4 @@ const handleForgotPwd = () => {
   background: #2563eb;
   box-shadow: 0 0 15px rgba(59, 130, 246, 0.6);
 }
-</style>
+</style>

+ 182 - 150
src/views/system/role/index.vue

@@ -1,5 +1,8 @@
 <template>
   <div class="device-manager-box">
+    <div class="back-btn" @click="getBack">
+      <SvgIcon class="box-icon-style" size="20" name="back-bg" />
+    </div>
     <BasicTable @register="registerTable">
       <template #tableTitle>
         <a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
@@ -14,8 +17,7 @@
               </a-menu-item>
             </a-menu>
           </template>
-          <a-button
-            >批量操作
+          <a-button>批量操作
             <Icon icon="mdi:chevron-down" />
           </a-button>
         </a-dropdown>
@@ -37,161 +39,191 @@
   <RoleIndexModal @register="registerIndexModal" />
 </template>
 <script lang="ts" name="system-role" setup>
-  import { ref } from 'vue';
-  import { BasicTable, TableAction } from '/@/components/Table';
-  import { useDrawer } from '/@/components/Drawer';
-  import { useModal } from '/@/components/Modal';
-  import RoleDrawer from './components/RoleDrawer.vue';
-  import RoleDesc from './components/RoleDesc.vue';
-  import RolePermissionDrawer from './components/RolePermissionDrawer.vue';
-  import RoleIndexModal from './components/RoleIndexModal.vue';
-  import RoleUserTable from './components/RoleUserTable.vue';
-  import { columns, searchFormSchema } from './role.data';
-  import { list, deleteRole, batchDeleteRole, getExportUrl, getImportUrl } from './role.api';
-  import { useListPage } from '/@/hooks/system/useListPage';
-  const showFooter = ref(true);
-  const [roleUserDrawer, { openDrawer: openRoleUserDrawer }] = useDrawer();
-  const [registerDrawer, { openDrawer }] = useDrawer();
-  const [registerModal, { openModal }] = useModal();
-  const [registerIndexModal, { openModal: openIndexModal }] = useModal();
-  const [rolePermissionDrawer, { openDrawer: openRolePermissionDrawer }] = useDrawer();
-  const [registerDesc, { openDrawer: openRoleDesc }] = useDrawer();
+import { ref } from 'vue';
+import { BasicTable, TableAction } from '/@/components/Table';
+import { useDrawer } from '/@/components/Drawer';
+import { useModal } from '/@/components/Modal';
+import RoleDrawer from './components/RoleDrawer.vue';
+import RoleDesc from './components/RoleDesc.vue';
+import RolePermissionDrawer from './components/RolePermissionDrawer.vue';
+import RoleIndexModal from './components/RoleIndexModal.vue';
+import RoleUserTable from './components/RoleUserTable.vue';
+import { columns, searchFormSchema } from './role.data';
+import { list, deleteRole, batchDeleteRole, getExportUrl, getImportUrl } from './role.api';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { SvgIcon } from '/@/components/Icon';
 
-  // 列表页面公共参数、方法
-  const { prefixCls, tableContext, onImportXls, onExportXls } = useListPage({
-    designScope: 'role-template',
-    tableProps: {
-      title: '系统角色列表',
-      api: list,
-      columns: columns,
-      formConfig: {
-        // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
-        labelWidth:65,
-        rowProps: { gutter: 24 },
-        // update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
-        schemas: searchFormSchema,
-      },
-      actionColumn: {
-        width: 120,
-      },
-      rowSelection: null,
-      //自定义默认排序
-      defSort: {
-        column: 'id',
-        order: 'desc',
-      },
+const showFooter = ref(true);
+const [roleUserDrawer, { openDrawer: openRoleUserDrawer }] = useDrawer();
+const [registerDrawer, { openDrawer }] = useDrawer();
+const [registerModal, { openModal }] = useModal();
+const [registerIndexModal, { openModal: openIndexModal }] = useModal();
+const [rolePermissionDrawer, { openDrawer: openRolePermissionDrawer }] = useDrawer();
+const [registerDesc, { openDrawer: openRoleDesc }] = useDrawer();
+
+// 列表页面公共参数、方法
+const { prefixCls, tableContext, onImportXls, onExportXls } = useListPage({
+  designScope: 'role-template',
+  tableProps: {
+    title: '系统角色列表',
+    api: list,
+    columns: columns,
+    formConfig: {
+      // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
+      labelWidth: 65,
+      rowProps: { gutter: 24 },
+      // update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5873】查询区域lablel默认居左
+      schemas: searchFormSchema,
     },
-    exportConfig: {
-      name: '角色列表',
-      url: getExportUrl,
+    actionColumn: {
+      width: 120,
     },
-    importConfig: {
-      url: getImportUrl,
+    rowSelection: null,
+    //自定义默认排序
+    defSort: {
+      column: 'id',
+      order: 'desc',
     },
+  },
+  exportConfig: {
+    name: '角色列表',
+    url: getExportUrl,
+  },
+  importConfig: {
+    url: getImportUrl,
+  },
+});
+const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
+
+/**
+ * 新增
+ */
+function handleCreate() {
+  showFooter.value = true;
+  openDrawer(true, {
+    isUpdate: false,
+  });
+}
+/**
+ * 编辑
+ */
+function handleEdit(record: Recordable) {
+  showFooter.value = true;
+  openDrawer(true, {
+    record,
+    isUpdate: true,
   });
-  const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
+}
+/**
+ * 详情
+ */
+function handleDetail(record) {
+  showFooter.value = false;
+  openRoleDesc(true, {
+    record,
+    isUpdate: true,
+  });
+}
+/**
+ * 删除事件
+ */
+async function handleDelete(record) {
+  await deleteRole({ id: record.id }, reload);
+}
+/**
+ * 批量删除事件
+ */
+async function batchHandleDelete() {
+  await batchDeleteRole({ ids: selectedRowKeys.value }, reload);
+}
+/**
+ * 角色授权弹窗
+ */
+function handlePerssion(record) {
+  openRolePermissionDrawer(true, { roleId: record.id });
+}
 
-  /**
-   * 新增
-   */
-  function handleCreate() {
-    showFooter.value = true;
-    openDrawer(true, {
-      isUpdate: false,
-    });
-  }
-  /**
-   * 编辑
-   */
-  function handleEdit(record: Recordable) {
-    showFooter.value = true;
-    openDrawer(true, {
-      record,
-      isUpdate: true,
-    });
-  }
-  /**
-   * 详情
-   */
-  function handleDetail(record) {
-    showFooter.value = false;
-    openRoleDesc(true, {
-      record,
-      isUpdate: true,
-    });
-  }
-  /**
-   * 删除事件
-   */
-  async function handleDelete(record) {
-    await deleteRole({ id: record.id }, reload);
-  }
-  /**
-   * 批量删除事件
-   */
-  async function batchHandleDelete() {
-    await batchDeleteRole({ ids: selectedRowKeys.value }, reload);
-  }
-  /**
-   * 角色授权弹窗
-   */
-  function handlePerssion(record) {
-    openRolePermissionDrawer(true, { roleId: record.id });
-  }
+/**
+ * 首页配置弹窗
+ */
+function handleIndexConfig(roleCode) {
+  openIndexModal(true, { roleCode });
+}
+/**
+ * 角色用户
+ */
+function handleUser(record) {
+  //onSelectChange(selectedRowKeys)
+  openRoleUserDrawer(true, record);
+}
+/**
+ * 操作栏
+ */
+function getTableAction(record) {
+  return [
+    {
+      label: '用户',
+      onClick: handleUser.bind(null, record),
+    },
+    {
+      label: '授权',
+      onClick: handlePerssion.bind(null, record),
+    },
+  ];
+}
 
-  /**
-   * 首页配置弹窗
-   */
-  function handleIndexConfig(roleCode) {
-    openIndexModal(true, { roleCode });
-  }
-  /**
-   * 角色用户
-   */
-  function handleUser(record) {
-    //onSelectChange(selectedRowKeys)
-    openRoleUserDrawer(true, record);
-  }
-  /**
-   * 操作栏
-   */
-  function getTableAction(record) {
-    return [
-      {
-        label: '用户',
-        onClick: handleUser.bind(null, record),
-      },
-      {
-        label: '授权',
-        onClick: handlePerssion.bind(null, record),
+/**
+ * 下拉操作栏
+ */
+function getDropDownAction(record) {
+  return [
+    {
+      label: '编辑',
+      onClick: handleEdit.bind(null, record),
+    },
+    {
+      label: '详情',
+      onClick: handleDetail.bind(null, record),
+    },
+    {
+      label: '删除',
+      popConfirm: {
+        title: '是否确认删除',
+        confirm: handleDelete.bind(null, record),
       },
-    ];
-  }
+    },
+    {
+      label: '首页配置',
+      onClick: handleIndexConfig.bind(null, record.roleCode),
+    },
+  ];
+}
+/**
+ * 返回上一页
+ *
+ * 使用浏览器的历史记录API返回到上一个页面
+ */
+function getBack() {
+  window.history.back();
+}
+</script>
+<style scoped>
+.device-manager-box {
+  position: relative;
+  width: 100%;
+  height: 100%;
 
-  /**
-   * 下拉操作栏
-   */
-  function getDropDownAction(record) {
-    return [
-      {
-        label: '编辑',
-        onClick: handleEdit.bind(null, record),
-      },
-      {
-        label: '详情',
-        onClick: handleDetail.bind(null, record),
-      },
-      {
-        label: '删除',
-        popConfirm: {
-          title: '是否确认删除',
-          confirm: handleDelete.bind(null, record),
-        },
-      },
-      {
-        label: '首页配置',
-        onClick: handleIndexConfig.bind(null, record.roleCode),
-      },
-    ];
+  .back-btn {
+    position: absolute;
+    left: 5px;
+    top: 5px;
+    color: var(--vent-font-color);
+    background-color: #1580cc;
+    border: 1px solid #1580cc;
+    cursor: pointer;
+    z-index: 9999;
+    padding: 5px 8px;
+    border-radius: 5px;
   }
-</script>
+}
+</style>

+ 246 - 214
src/views/system/user/index.vue

@@ -1,5 +1,8 @@
 <template>
   <div class="device-manager-box">
+    <div class="back-btn" @click="getBack">
+      <SvgIcon class="box-icon-style" size="20" name="back-bg" />
+    </div>
     <!--引用表格-->
     <BasicTable @register="registerTable" :rowSelection="rowSelection">
       <!--插槽:table标题-->
@@ -9,7 +12,8 @@
         <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" />
+        <JThirdAppButton biz-type="user" :selected-row-keys="selectedRowKeys" syncToApp syncToLocal
+          @sync-finally="onSyncFinally" />
         <a-dropdown v-if="selectedRowKeys.length > 0">
           <template #overlay>
             <a-menu>
@@ -27,8 +31,7 @@
               </a-menu-item>
             </a-menu>
           </template>
-          <a-button
-            >批量操作
+          <a-button>批量操作
             <Icon icon="mdi:chevron-down" />
           </a-button>
         </a-dropdown>
@@ -50,236 +53,265 @@
 </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, getImportUrl, getExportUrl, frozenBatch, syncUser } from './user.api';
-  // import { usePermission } from '/@/hooks/web/usePermission'
-  // const { hasPermission } = usePermission();
+//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, getImportUrl, getExportUrl, frozenBatch, syncUser } from './user.api';
+import { SvgIcon } from '/@/components/Icon';
+// import { usePermission } from '/@/hooks/web/usePermission'
+// const { hasPermission } = usePermission();
 
-  const { createMessage, createConfirm } = useMessage();
+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();
+//注册drawer
+const [registerDrawer, { openDrawer }] = useDrawer();
+//回收站model
+const [registerModal, { openModal }] = useModal();
+//密码model
+const [registerPasswordModal, { openModal: openPasswordModal }] = useModal();
+//代理人model
+const [registerAgentModal, { openModal: openAgentModal }] = useModal();
 
-  // 列表页面公共参数、方法
-  const { prefixCls, tableContext, onExportXls, onImportXls } = 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' }, params);
-      },
+// 列表页面公共参数、方法
+const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
+  designScope: 'user-list',
+  tableProps: {
+    title: '用户列表',
+    api: list,
+    columns: columns,
+    size: 'small',
+    formConfig: {
+      // labelWidth: 200,
+      schemas: searchFormSchema,
     },
-    exportConfig: {
-      name: '用户列表',
-      url: getExportUrl,
+    actionColumn: {
+      width: 120,
     },
-    importConfig: {
-      url: getImportUrl,
+    beforeFetch: (params) => {
+      return Object.assign({ column: 'createTime', order: 'desc' }, params);
     },
-  });
+  },
+  exportConfig: {
+    name: '用户列表',
+    url: getExportUrl,
+  },
+  importConfig: {
+    url: getImportUrl,
+  },
+});
 
-  //注册table数据
-  const [registerTable, { reload, updateTableDataRecord }, { rowSelection, selectedRows, selectedRowKeys }] = tableContext;
+//注册table数据
+const [registerTable, { reload, updateTableDataRecord }, { rowSelection, 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);
+/**
+ * 新增事件
+ */
+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;
   }
-  /**
-   * 批量删除事件
-   */
-  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();
-    });
+  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;
   }
-  /**
-   * 成功回调
-   */
-  function handleSuccess() {
+  await batchDeleteUser({ ids: selectedRowKeys.value }, () => {
+    selectedRowKeys.value = [];
     reload();
-  }
+  });
+}
+/**
+ * 成功回调
+ */
+function handleSuccess() {
+  reload();
+}
 
-  /**
-   * 打开修改密码弹窗
-   */
-  function handleChangePassword(username) {
-    openPasswordModal(true, { username });
+/**
+ * 打开修改密码弹窗
+ */
+function handleChangePassword(username) {
+  openPasswordModal(true, { username });
+}
+/**
+ * 打开代理人弹窗
+ */
+function handleAgentSettings(userName) {
+  openAgentModal(true, { userName });
+}
+/**
+ * 冻结解冻
+ */
+async function handleFrozen(record, status) {
+  if ('admin' == record.username) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
   }
-  /**
-   * 打开代理人弹窗
-   */
-  function handleAgentSettings(userName) {
-    openAgentModal(true, { userName });
-  }
-  /**
-   * 冻结解冻
-   */
-  async function handleFrozen(record, status) {
-    if ('admin' == record.username) {
-      createMessage.warning('管理员账号不允许此操作!');
-      return;
-    }
-    await frozenBatch({ ids: record.id, status: status }, reload);
-  }
-  /**
-   * 批量冻结解冻
-   */
-  function batchFrozen(status) {
-    let hasAdmin = selectedRows.value.filter((item) => item.username == 'admin');
-    if (unref(hasAdmin).length > 0) {
-      createMessage.warning('管理员账号不允许此操作!');
-      return;
-    }
-    createConfirm({
-      iconType: 'warning',
-      title: '确认操作',
-      content: '是否' + (status == 1 ? '解冻' : '冻结') + '选中账号?',
-      onOk: async () => {
-        await frozenBatch({ ids: unref(selectedRowKeys).join(','), status: status }, reload);
-      },
-    });
+  await frozenBatch({ ids: record.id, status: status }, reload);
+}
+/**
+ * 批量冻结解冻
+ */
+function batchFrozen(status) {
+  let hasAdmin = selectedRows.value.filter((item) => item.username == 'admin');
+  if (unref(hasAdmin).length > 0) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
   }
+  createConfirm({
+    iconType: 'warning',
+    title: '确认操作',
+    content: '是否' + (status == 1 ? '解冻' : '冻结') + '选中账号?',
+    onOk: async () => {
+      await frozenBatch({ ids: unref(selectedRowKeys).join(','), status: status }, reload);
+    },
+  });
+}
 
-  /**
-   *同步流程
-   */
-  function handleSyncUser() {
-    syncUser();
-  }
-  /**
-   *同步钉钉和微信回调
-   */
-  function onSyncFinally({ isToLocal }) {
-    // 同步到本地时刷新下数据
-    if (isToLocal) {
-      reload();
-    }
+/**
+ *同步流程
+ */
+function handleSyncUser() {
+  syncUser();
+}
+/**
+ *同步钉钉和微信回调
+ */
+function onSyncFinally({ isToLocal }) {
+  // 同步到本地时刷新下数据
+  if (isToLocal) {
+    reload();
   }
+}
 
-  /**
-   * 操作栏
-   */
-  function getTableAction(record): ActionItem[] {
-    return [
-      {
-        label: '编辑',
-        onClick: handleEdit.bind(null, record),
-        // 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),
-        },
-      },
-      {
-        label: '冻结',
-        ifShow: record.status == 1,
-        popConfirm: {
-          title: '确定冻结吗?',
-          confirm: handleFrozen.bind(null, record, 2),
-        },
+/**
+ * 操作栏
+ */
+function getTableAction(record): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: handleEdit.bind(null, record),
+      // 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),
       },
-      {
-        label: '解冻',
-        ifShow: record.status == 2,
-        popConfirm: {
-          title: '确定解冻吗?',
-          confirm: handleFrozen.bind(null, record, 1),
-        },
+    },
+    {
+      label: '冻结',
+      ifShow: record.status == 1,
+      popConfirm: {
+        title: '确定冻结吗?',
+        confirm: handleFrozen.bind(null, record, 2),
       },
-      {
-        label: '代理人',
-        onClick: handleAgentSettings.bind(null, record.username),
+    },
+    {
+      label: '解冻',
+      ifShow: record.status == 2,
+      popConfirm: {
+        title: '确定解冻吗?',
+        confirm: handleFrozen.bind(null, record, 1),
       },
-    ];
-  }
+    },
+    {
+      label: '代理人',
+      onClick: handleAgentSettings.bind(null, record.username),
+    },
+  ];
+}
+
+/**
+ * 返回上一页
+ *
+ * 使用浏览器的历史记录API返回到上一个页面
+ */
+function getBack() {
+  window.history.back();
+}
 </script>
 
-<style scoped></style>
+<style scoped>
+.device-manager-box {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  .back-btn {
+    position: absolute;
+    left: 5px;
+    top: 5px;
+    color: var(--vent-font-color);
+    background-color: #1580cc;
+    border: 1px solid #1580cc;
+    cursor: pointer;
+    z-index: 9999;
+    padding: 5px 8px;
+    border-radius: 5px;
+  }
+}
+</style>

+ 24 - 19
src/views/vent/cad/dwgViewer.vue

@@ -96,6 +96,7 @@
 
   // ==================== 表格 ====================
 
+  // 设备列表表格列
   const columns = [
     { title: '#', key: 'index', width: 36 },
     { title: '设备名称', dataIndex: 'name', ellipsis: true },
@@ -104,25 +105,26 @@
     { title: '操作', key: 'action', width: 56 },
     { title: '布点', key: 'markerStatus', width: 48 },
   ];
-
+  // 设备列表数据
   const devices = ref<DeviceInfo[]>([
-    { id: 'd001', name: '1#瓦斯传感器', type: '传感器', status: 'online', markerId: null },
-    { id: 'd002', name: '2#瓦斯传感器', type: '传感器', status: 'online', markerId: null },
-    { id: 'd003', name: '主通风机', type: '风机', status: 'online', markerId: null },
-    { id: 'd004', name: '局部通风机', type: '风机', status: 'offline', markerId: null },
-    { id: 'd005', name: '风门A', type: '风门', status: 'online', markerId: null },
-    { id: 'd006', name: '风速传感器-1', type: '传感器', status: 'alarm', markerId: null },
-    { id: 'd007', name: '温度传感器-1', type: '传感器', status: 'online', markerId: null },
-    { id: 'd008', name: '风窗B', type: '风窗', status: 'online', markerId: null },
+    // { id: 'd001', name: '1#瓦斯传感器', type: '传感器', status: 'online', markerId: null },
+    // { id: 'd002', name: '2#瓦斯传感器', type: '传感器', status: 'online', markerId: null },
+    // { id: 'd003', name: '主通风机', type: '风机', status: 'online', markerId: null },
+    // { id: 'd004', name: '局部通风机', type: '风机', status: 'offline', markerId: null },
+    // { id: 'd005', name: '风门A', type: '风门', status: 'online', markerId: null },
+    // { id: 'd006', name: '风速传感器-1', type: '传感器', status: 'alarm', markerId: null },
+    // { id: 'd007', name: '温度传感器-1', type: '传感器', status: 'online', markerId: null },
+    // { id: 'd008', name: '风窗B', type: '风窗', status: 'online', markerId: null },
   ]);
-
+  // 状态颜色映射
   function statusColor(s: string) {
     return { online: 'green', offline: 'gray', alarm: 'red' }[s] || 'default';
   }
+  // 状态文本映射
   function statusText(s: string) {
     return { online: '在线', offline: '离线', alarm: '报警' }[s] || s;
   }
-
+  // 模拟监测数据
   function getMockMonitoring(deviceId: string) {
     const d = devices.value.find((x) => x.id === deviceId);
     return {
@@ -217,13 +219,13 @@
 
     console.log(`[布点] 进入放置模式: ${device.name}`);
   }
-
+  // 退出放置模式
   function exitPlacingMode() {
     placingMode.value = false;
     placingDevice.value = null;
     if (floatingDot) floatingDot.style.display = 'none';
   }
-
+  // 放置 marker
   function placeMarker(device: DeviceInfo, location: { x: number; y: number }) {
     if (!viewer) return;
     markerIdCounter++;
@@ -287,7 +289,7 @@
     console.log(`[布点] 设备=${device.name} X=${location.x.toFixed(2)} Y=${location.y.toFixed(2)} r=${radius.toFixed(2)}`);
     exitPlacingMode();
   }
-
+  // 移除 marker
   function removeMarker(markerId: string) {
     if (!viewer) return;
     const m = markersMap.get(markerId);
@@ -311,8 +313,8 @@
   }
 
   let infoPanel: HTMLElement | null = null;
-
-  function showMarkerInfo(markerId: string) {
+  // 显示 marker 信息面板
+   function showMarkerInfo(markerId: string) {
     const m = markersMap.get(markerId);
     if (!m || !viewer) return;
     const data = getMockMonitoring(m.deviceId);
@@ -356,7 +358,7 @@
     };
     setTimeout(() => document.addEventListener('click', closeHandler), 100);
   }
-
+  // 点击 marker 事件
   function onMarkerClick(markerId: string) {
     console.log(`[标记点击] marker=${markerId}`);
     showMarkerInfo(markerId);
@@ -365,6 +367,7 @@
   // ==================== 初始化 ====================
 
   onMounted(async () => {
+    // 初始化 viewer
     try {
       viewer = new Viewer2d({
         containerId,
@@ -450,12 +453,14 @@
       console.error('[DWG] 初始化失败:', err);
     }
   });
-
+  /** 键盘事件处理:Esc 键退出放置模式 */
   const onKeydown = (e: KeyboardEvent) => {
     if (e.key === 'Escape' && placingMode.value) exitPlacingMode();
   };
-  onMounted(() => window.addEventListener('keydown', onKeydown));
+  // 注册键盘事件监听
+   onMounted(() => window.addEventListener('keydown', onKeydown));
 
+  /** 组件卸载时清理:移除事件监听、销毁所有 marker、释放 viewer 资源 */
   onUnmounted(() => {
     window.removeEventListener('keydown', onKeydown);
     floatingDot?.remove();

+ 0 - 88
src/views/vent/home/configurable/components/ModuleGasInject.vue

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

+ 0 - 1
src/views/vent/home/configurable/hooks/helper.ts

@@ -40,7 +40,6 @@ export function getRawProp(formatter: string): string {
 // 获取模块所依赖的数据的方法
 export function getData(raw, readFrom, parser?) {
   const result = readFrom ? get(raw, readFrom) : raw;
-  console.log()
   if (!result) return result;
   switch (parser) {
     case 'json':

+ 1 - 1
src/views/vent/home/configurable/linkConfiguration.vue

@@ -20,7 +20,7 @@ import { getSystemApi } from './configurable.api';
 import { testConfigLink } from './configurable.data';
 
 const { configs, fetchConfigs } = useInitConfigs();
-const { data, updateData } = useInitPage('红沙泉露天煤矿系统');
+const { data, updateData } = useInitPage('红沙泉露天煤矿火灾监测预警系统');
 let interval: number | undefined;
 
 // https获取监测数据

+ 39 - 27
src/views/vent/monitorManager/hsqHome/components/AlarmMonitor.vue

@@ -7,19 +7,29 @@
       </div>
     </div>
     <div class="alarm-bot">
-      <CommonTable :columns="columns" :table-data="tableData"></CommonTable>
+      <CommonTable :columns="alarmColumns" :table-data="tableData"></CommonTable>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
 import CommonTable from './CommonTable.vue';
+import { autoLogList } from '../hsqHome.api';
+import {alarmColumns} from '../hsqHome.data'
 
 let props = defineProps({
+  /**
+   * 顶部卡片配置项
+   * 每项包含 label(显示名称)和 value(对应 cardData 中的 key)
+   */
   option: {
     type: Array as any
   },
+  /**
+   * 卡片数据对象
+   * key 对应 option 中每项的 value,value 为实际展示数值
+   */
   cardData: {
     type: Object as any,
     default: () => {
@@ -28,31 +38,33 @@ let props = defineProps({
   }
 })
 
-let columns = ref<any[]>([
-  {
-    name: '时间',
-    prop: 'time',
-  },
-  {
-    name: '位置',
-    prop: 'address',
-  },
-  {
-    name: '级别',
-    prop: 'grade',
-  },
-])
-let tableData = ref<any[]>([
-  { time: '14:25:10', address: '采掘场A区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场B区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场C区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场D区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场E区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场F区', grade: '一级' },
-   { time: '14:25:10', address: '采掘场D区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场E区', grade: '一级' },
-  { time: '14:25:10', address: '采掘场F区', grade: '一级' },
-])
+// 表格数据源:从后端获取报警日志列表数据
+let tableData = ref<any[]>([])
+
+/**
+ * 获取报警日志列表数据
+ *
+ * 调用 autoLogList API,默认查询第1页、每页10条记录,
+ * 将返回的报警日志数据填充到表格中用于展示
+ *
+ * @returns {Promise<void>}
+ */
+async function getAlarmData() {
+  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: '2074416476136718337',  });
+  tableData.value = res.records.map(el=>{
+    return {
+      ...el,
+      // 将数字类型的报警等级映射为中文描述
+      // 101:低风险  102:一般风险  103:较大风险  104:重大风险  201:报警
+      alarmLevelC: el.alarmLevel == 101 ? '低风险' : el.alarmLevel == 102 ? '一般风险' : el.alarmLevel == 103 ? '较大风险' : el.alarmLevel == 104 ? '重大风险' : el.alarmLevel == 201 ? '报警' : '低风险'  
+    }
+  });
+}
+
+// 组件挂载后立即获取报警日志数据
+onMounted(() => {
+  getAlarmData();
+})
 </script>
 
 <style lang="less" scoped>

+ 55 - 11
src/views/vent/monitorManager/hsqHome/components/CommonTable.vue

@@ -8,10 +8,22 @@
       <div class="table__content_list_row" v-for="(item, index) in tableData" :key="`svvhbct-${index}`">
         <div v-for="(t, i) in columns" :key="`svvhbctr-${i}`" :style="{ flexBasis }" class="table__content__list_item">
           <slot :name="t.prop" :scope="item">
-            <span v-if="t.prop == 'status' || t.prop == 'remark' || t.prop == 'grade'" class="icon-pie">
-              <span class="icon-item"></span>
+            <span v-if="t.prop == 'syswarnLevel'" style="display: flex; align-items: center;justify-content: center;">
+              <span :class="get(item, t.prop) ? 'icon-pie1' : 'icon-pie'">
+                <span :class="get(item, t.prop) ? 'icon-item1' : 'icon-item'"></span>
+              </span>
+              <span>{{ get(item, t.prop) ? '报警' : '正常' }}</span>
             </span>
-            <span :style="{ color: t.prop == 'grade' ? 'rgba(79, 255, 173)' : '' }">{{ get(item, t.prop) }}</span>
+            <span v-else-if="t.prop == 'netStatus'" style="display: flex; align-items: center;justify-content: center;">
+              <span :class="get(item, t.prop) ? 'icon-pie' : 'icon-pie1'">
+                <span :class="get(item, t.prop) ? 'icon-item' : 'icon-item1'"></span>
+              </span>
+              <span>{{ get(item, t.prop) ? '在线' : '离线' }}</span>
+            </span>
+            <span v-else-if="t.prop == 'alarmLevelC'"
+              :style="{ color: item.alarmLevelC == '低风险' ? '#00d8ff' : item.alarmLevelC == '一般风险' ? '#fcfc22' : item.alarmLevelC == '较大风险' ? '#ff7010' : item.alarmLevelC == '重大风险' ? '#df4e43' : item.alarmLevelC == '报警' ? '#ff0000' : '' }">{{
+                get(item, t.prop) }}</span>
+            <span v-else>{{ get(item, t.prop) }}</span>
           </slot>
         </div>
       </div>
@@ -38,7 +50,7 @@ let props = defineProps({
   },
   defaultValue: {
     type: String,
-    default: '-'
+    default: ''
   }
 })
 
@@ -51,6 +63,13 @@ const flexBasis = computed(() => {
   return Math.fround(100 / props.columns.length) + '%';
 });
 
+/**
+ * 安全获取对象属性值,当值为 null/undefined/空字符串时返回默认值
+ *
+ * @param o 要访问的对象
+ * @param p 对象属性路径
+ * @returns 属性值或默认值
+ */
 function get(o, p) {
   const d = _.get(o, p);
   return _.isNil(d) ? props.defaultValue : d === '' ? props.defaultValue : d;
@@ -72,7 +91,7 @@ function get(o, p) {
   position: relative;
   width: 100%;
   height: 100%;
-  padding: 10px;
+  padding: 10px 0px 0px 0px;
   box-sizing: border-box;
 
   .gallery-content {
@@ -135,7 +154,7 @@ function get(o, p) {
     width: 400px;
     height: 32px;
     display: flex;
-    justify-content: space-around;
+    justify-content: space-between;
     align-items: center;
     background-image: var(--image-content-label);
     background-size: 100% 100%;
@@ -160,7 +179,7 @@ function get(o, p) {
     .table__content_list_row {
       width: 100%;
       display: flex;
-      justify-content: space-around;
+      justify-content: space-between;
       align-items: center;
       color: #fff;
       margin-bottom: 5px;
@@ -175,10 +194,11 @@ function get(o, p) {
       }
     }
   }
-.table__content__list_item{
-  text-align: center;
-  padding-left: 8px;
-}
+
+  .table__content__list_item {
+    text-align: center;
+  }
+
   .icon-pie {
     position: relative;
     width: 12px;
@@ -188,6 +208,15 @@ function get(o, p) {
     margin-right: 5px;
   }
 
+  .icon-pie1 {
+    position: relative;
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    background-color: rgba(255, 79, 79, .3);
+    margin-right: 5px;
+  }
+
   .icon-item {
     position: absolute;
     left: 50%;
@@ -198,5 +227,20 @@ function get(o, p) {
     border-radius: 50%;
     background-color: rgba(79, 255, 173);
   }
+
+  .icon-item1 {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    background-color: rgba(255, 79, 79);
+  }
+
+  ::-webkit-scrollbar {
+    display: none;
+  }
 }
 </style>

+ 50 - 26
src/views/vent/monitorManager/hsqHome/components/CustomChart.vue

@@ -56,24 +56,35 @@ const genChartOption = () => {
   }
 
   // 该项作为下面所有图表依赖的基准系列数据
-  const baseSeries: { name: string; data: [string, string][]; color: string }[] = sorttedData.reduce((res: any[], baseData) => {
-    series.forEach((serie) => {
-      // 将读取出的数据转为数组
-      console.log(serie.readFrom,'serie.readFrom')
-      const temp = getData(baseData, serie.readFrom) || [];
-
-      res.push({
-        name: getFormattedText(baseData, serie.label),
-        data: (Array.isArray(temp) ? temp : [temp]).map((data) => {
-          return [getData(data, serie.xprop), getData(data, serie.yprop)]; /** x y */
-          // return { name: getData(data, serie.xprop), value: getData(data, serie.yprop) }; /** x y */
-        }),
-        color: serie.color,
-      });
-    });
+  // const baseSeries: { name: string; data: [string, string][]; color: string }[] = sorttedData.reduce((res: any[], baseData) => {
+  //   series.forEach((serie) => {
+  //     // 将读取出的数据转为数组
+  //     // const temp = getData(baseData, serie.readFrom) || [];
+  //     const temp = sorttedData
+  //     console.log(temp,'temp===')
+  //     console.log(baseData, 'baseData===')
+  //     res.push({
+  //       name: getFormattedText(baseData, serie.label),
+  //       data: (Array.isArray(temp) ? temp : [temp]).map((data) => {
+  //         return [getData(data, serie.xprop), getData(data, serie.yprop)]; /** x y */
+  //         // return { name: getData(data, serie.xprop), value: getData(data, serie.yprop) }; /** x y */
+  //       }),
+  //       color: serie.color,
+  //     });
+  //   });
+  //   console.log(res,'res===')
+  //   return res
+  // }, []);
+  const baseSeries = series.map((serie) => {
+    return {
+      name: serie.label,
+      color: serie.color,
+      data: sorttedData.map((data) => {
+        return [getData(data, serie.xprop), getData(data, serie.yprop)]; /** x y */
+      })
+    }
+  });
 
-    return res;
-  }, []);
 
   const baseDataZoom = dataZoom.map((e, i) => {
     return {
@@ -747,6 +758,14 @@ const genChartOption = () => {
   // 折线区域图,即折线下面的区域填满
   if (type === 'line_area') {
     return {
+      tooltip: {
+        trigger: 'axis',
+        backgroundColor: 'rgba(0, 0, 0, .6)',
+        textStyle: {
+          color: '#fff',
+          fontSize: 12,
+        },
+      },
       textStyle,
       legend: {
         itemGap: 25,
@@ -769,6 +788,7 @@ const genChartOption = () => {
           ...e,
           type: 'category',
           boundaryGap: false,
+          interval: 0,
           axisLabel: {
             width: 100,
             overflow: 'break',
@@ -795,7 +815,7 @@ const genChartOption = () => {
         };
       }),
       series: baseSeries.map((serie, index) => {
-        const colors = ['#66ffff', '#6666ff'];
+        const colors = ['rgba(52, 193, 248,.6)', 'rgba(52, 193, 248,.2)'];
         let color;
         if (serie.color) {
           color = serie.color;
@@ -807,13 +827,18 @@ const genChartOption = () => {
           type: 'line',
           symbol: 'none',
           endLabel: { distance: 0 },
-          lineStyle: { color: color },
+          lineStyle: {
+            normal: {
+              width: 1,
+              color: 'rgba(52, 193, 248)',
+            },
+          },
           areaStyle: {
             origin: 'auto',
             color: new graphic.LinearGradient(0, 0, 0, 1, [
-              { offset: 0, color: color },
-              { offset: 0.2, color: color },
-              { offset: 1, color: `${color}22` },
+              { offset: 0, color: color[0] },
+              { offset: 1, color: color[1] },
+              // { offset: 1, color: `${color}22` },
             ]),
           },
         };
@@ -1048,14 +1073,14 @@ const genChartOption = () => {
             interval: 0,
             width: (domWidth - 100) / get(baseSeries, '[0].data.length', 1),
             overflow: 'break',
-            color:'#fff'
+            color: '#fff'
           },
         };
       }),
       yAxis: yAxis.map((e, i) => {
         return {
           ...e,
-           axisLabel: {
+          axisLabel: {
             textStyle: {
               color: '#fff',
             },
@@ -1280,8 +1305,7 @@ const genChartOption = () => {
 
 watch(
   () => props.chartData,
-  (newV,oldV) => {
-    console.log(newV,'new===---');
+  (newV, oldV) => {
     initCharts();
   },
   {

+ 6 - 27
src/views/vent/monitorManager/hsqHome/components/CustomGalleryNew.vue

@@ -13,15 +13,16 @@
       </div>
     </div>
     <div class="custom-bottom">
-      <CommonTable :columns="columns" :table-data="tableData"></CommonTable>
+      <CommonTable :columns="deviceColumns" :table-data="tableData"></CommonTable>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { SvgIcon } from '/@/components/Icon';
 import CommonTable from './CommonTable.vue';
+import { deviceColumns } from '../hsqHome.data.js';
 
 let props = defineProps({
   option: {
@@ -30,31 +31,10 @@ let props = defineProps({
   galleryData: {
     type: Object
   },
-})
-
-let columns = ref<any[]>([
-  {
-    name: '设备名称',
-    prop: 'deviceName',
-  },
-  {
-    name: '状态',
-    prop: 'status',
-  },
-  {
-    name: '备注',
-    prop: 'remark',
+  tableData: {
+    type: Array as any
   },
-])
-let tableData = ref<any[]>([
-  { deviceName: 'CAM-001', status: '在线', remark: '正常' },
-  { deviceName: 'CAM-002', status: '在线', remark: '正常' },
-  { deviceName: 'CAM-003', status: '报警', remark: '高温' },
-  { deviceName: 'CAM-001', status: '在线', remark: '正常' },
-  { deviceName: 'CAM-002', status: '在线', remark: '正常' },
-  { deviceName: 'CAM-003', status: '报警', remark: '高温' },
-])
-
+})
 </script>
 
 <style lang="less" scoped>
@@ -86,7 +66,6 @@ let tableData = ref<any[]>([
     display: flex;
     flex-direction: column;
     align-items: center;
-    padding: 5px 0;
   }
 
   .gallery-new-box {

+ 145 - 86
src/views/vent/monitorManager/hsqHome/components/DeviceCenter.vue

@@ -3,11 +3,13 @@
     <div class="list-title">
       <div class="title-l">
         <span>设备详情-</span>
-        <span style="margin-left: 5px;">{{ deviceModel }}</span>
+        <span style="margin-left: 5px;">{{ detailDatas.strserno }}</span>
       </div>
       <div class="title-r">
-        <span class="icon-pie"></span>
-        <span>报警中</span>
+        <span :class="detailDatas.syswarnLevel ? 'icon-pie1' : 'icon-pie'">
+          <span :class="detailDatas.syswarnLevel ? 'pie-item1' : 'pie-item'"></span>
+        </span>
+        <span>{{ detailDatas.syswarnLevel ? '报警' : '正常' }}</span>
       </div>
     </div>
     <div class="basic-box">
@@ -16,37 +18,37 @@
           <div class="card-t">
             <div class="card-item">
               <div class="label-text">设备编号</div>
-              <div class="value-text">{{ cardData.deviceCode }}</div>
+              <div class="value-text">{{ detailDatas.strserno || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">设备名称</div>
-              <div class="value-text">{{ cardData.deviceName }}</div>
+              <div class="value-text">{{ detailDatas.strname || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">设备类型</div>
-              <div class="value-text">{{ cardData.deviceType }}</div>
+              <div class="value-text">{{ detailDatas.typeName || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">安装位置</div>
-              <div class="value-text">{{ cardData.address }}</div>
+              <div class="value-text">{{ detailDatas.strinstallpos || '-' }}</div>
             </div>
           </div>
           <div class="card-b">
             <div class="card-item">
               <div class="label-text">安装时间</div>
-              <div class="value-text">{{ cardData.time }}</div>
+              <div class="value-text">{{ detailDatas.readTime || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">生产厂家</div>
-              <div class="value-text">{{ cardData.company }}</div>
+              <div class="value-text">{{ detailDatas.manufacturer || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">Ip地址</div>
-              <div class="value-text">{{ cardData.ip }}</div>
+              <div class="value-text">{{ detailDatas.ip || '-' }}</div>
             </div>
             <div class="card-item">
               <div class="label-text">固件版本</div>
-              <div class="value-text">{{ cardData.version }}</div>
+              <div class="value-text">{{ detailDatas.version || '-' }}</div>
             </div>
           </div>
         </div>
@@ -59,14 +61,14 @@
             <div class="card-item">
               <SvgIcon class="icon-style" size="20" name="yxzt" />
               <div class="label-status-text">运行状态</div>
-              <div :class="statusData.runstatus == '报警' ? 'value-status-warn' : 'value-status-text'">{{
-                statusData.runstatus }}</div>
+              <div :class="detailDatas.netStatus == 0 ? 'value-status-warn' : 'value-status-text'">{{
+                detailDatas.netStatus==1 ? '在线' : detailDatas.status==0 ? '离线' : '-' }}</div>
             </div>
             <div class="card-item">
               <SvgIcon class="icon-style" size="20" name="wlyc" />
               <div class="label-status-text">网络延迟</div>
               <div class="value-status-text">
-                <span>{{ statusData.internet }}</span>
+                <span>{{ detailDatas.readData.wlyc || '-' }}</span>
                 <span>ms</span>
               </div>
             </div>
@@ -74,7 +76,7 @@
               <SvgIcon class="icon-style" size="20" name="spml" />
               <div class="label-status-text">视频码率</div>
               <div class="value-status-text">
-                <sapn>{{ statusData.videocode }}</sapn>
+                <sapn>{{ detailDatas.readData.spml || '-' }}</sapn>
                 <sapn>Mbps</sapn>
               </div>
             </div>
@@ -85,7 +87,7 @@
               <SvgIcon class="icon-style" size="20" name="sswd" />
               <div class="label-status-text">实时温度</div>
               <div class="value-status-text">
-                <span> {{ statusData.temp }}</span>
+                <span> {{ detailDatas.readData.temperature || '-' }}</span>
                 <span> ℃</span>
 
               </div>
@@ -93,12 +95,12 @@
             <div class="card-item">
               <SvgIcon class="icon-style" size="20" name="zl" />
               <div class="label-status-text">帧率</div>
-              <div class="value-status-text">{{ statusData.framerate }}</div>
+              <div class="value-status-text">{{ detailDatas.readData.zl || '-' }}</div>
             </div>
             <div class="card-item">
               <SvgIcon class="icon-style" size="20" name="csb" />
               <div class="label-status-text">传输比</div>
-              <div class="value-status-text">{{ statusData.ratio }}</div>
+              <div class="value-status-text">{{ detailDatas.readData.csb || '-' }}</div>
             </div>
           </div>
         </div>
@@ -108,24 +110,13 @@
       <basicBorder title="历史报警记录">
         <div class="basic-history">
           <div class="history-title">
-            <div class="title-item" v-for="(item, index) in titleList" :key="index">{{ item.label }}</div>
+            <div class="title-item" v-for="(item, index) in alarmColumns" :key="index">{{ item.name }}</div>
           </div>
           <div class="history-content">
             <div class="content-item" v-for="(item, index) in tableData" :key="index">
-              <div class="item-text">{{ item.time }}</div>
-              <div class="item-text item-text1">
-                <span>{{ item.temp }}</span>
-                <span>℃</span>
-              </div>
-              <div class="item-text">
-                <span> {{ item.durTime }}</span>
-                <span>min</span>
-              </div>
-              <div class="item-text item-text1">{{ item.grade }}</div>
-              <div class="item-text item-text2">{{ item.status }}</div>
-              <div class="item-text">{{ item.person }}</div>
-              <div class="item-text">
-                <span class="text-look">查看</span>
+              <div class="item-text" v-for="(ite,ind) in alarmColumns" :key="ind">
+                <div v-if="ite.prop=='alarmLevelC'"  :style="{ color: item.alarmLevelC == '低风险' ? '#00d8ff' : item.alarmLevelC == '一般风险' ? '#fcfc22' : item.alarmLevelC == '较大风险' ? '#ff7010' : item.alarmLevelC == '重大风险' ? '#df4e43' : item.alarmLevelC == '报警' ? '#ff0000' : '' }">{{item[ite.prop]  }}</div>
+                <div v-else>{{ item[ite.prop] }}</div>
               </div>
             </div>
           </div>
@@ -136,53 +127,64 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
+import { onUnmounted, reactive, ref, watch, onMounted } from 'vue'
 import basicBorder from './basicBorder.vue'
 import { SvgIcon } from '/@/components/Icon';
-
-let deviceModel = ref('CAM-001')
-let cardData = reactive({
-  deviceCode: 'CAM-001',
-  deviceName: '双光谱摄像机',
-  deviceType: '双光谱热成像',
-  address: '采掘场A区',
-  time: '2024-06-15',
-  company: 'xxx',
-  ip: '192.168.10.13',
-  version: 'v2.4.1',
+import { autoLogList } from '../hsqHome.api.js'
+import {alarmColumns} from '../hsqHome.data.js'
+
+let props = defineProps({
+  detailData: {
+    type: Object,
+    default: () => { },
+  },
 })
-let statusData = reactive({
-  runstatus: '报警',
-  internet: '12',
-  videocode: '3',
-  temp: '65.5',
-  framerate: 'xxx',
-  ratio: 'xxxx',
-})
-let titleList = ref<any[]>([
-  { label: '时间', value: '1' },
-  { label: '最高温度', value: '2' },
-  { label: '持续时间', value: '3' },
-  { label: '报警级别', value: '4' },
-  { label: '处理状态', value: '5' },
-  { label: '处理人', value: '6' },
-  { label: '操作', value: '7' },
-])
-let tableData = ref<any[]>([
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-  { time: '14:31:22', temp: 65.3, durTime: 15, grade: '一级', status: '未处理', person: '张三', },
-])
 
-</script>
+let detailDatas = ref<any>({})
+let tableData = ref<any[]>([])
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+
+/**
+ * 定时获取监测数据
+ * 通过定时器循环调用数据获取方法,更新表格数据
+ *
+ * @param flag - 是否立即执行(首次调用时传true跳过延时)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getAutoLogList();
+
+    if (timer) {
+      timer = null;
+    }
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
+
+async function getAutoLogList() {
+  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: '2074416476136718337', });
+  tableData.value = res.records.map(el => {
+    return {
+      ...el,
+      alarmLevelC: el.alarmLevel == 101 ? '低风险' : el.alarmLevel == 102 ? '一般风险' : el.alarmLevel == 103 ? '较大风险' : el.alarmLevel == 104 ? '重大风险' : el.alarmLevel == 201 ? '报警' : '低风险'
+    }
+  });
+}
+
+watch(() => props.detailData, (newVal, oldVal) => {
+  detailDatas.value =newVal
+}, { immediate: true })
 
+onMounted(() => {
+  getMonitor(true);
+});
+onUnmounted(() => {
+  if (timer) {
+    clearTimeout(timer);
+  }
+});
+</script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
@@ -225,17 +227,52 @@ let tableData = ref<any[]>([
 
   .title-r {
     padding-right: 16px;
+    display: flex;
+    align-items: center;
   }
 
   .icon-pie {
     display: inline-block;
-    width: 10px;
-    height: 10px;
+    position: relative;
+    width: 14px;
+    height: 14px;
+    border-radius: 50%;
+    background-color: rgba(79, 255, 173, .3);
+    margin-right: 5px;
+  }
+
+  .pie-item {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 7px;
+    height: 7px;
+    border-radius: 50%;
+    background-color: rgba(79, 255, 173);
+  }
+
+  .icon-pie1 {
+    display: inline-block;
+    position: relative;
+    width: 14px;
+    height: 14px;
     border-radius: 50%;
-    background-color: #fc002a;
+    background-color: rgba(255, 0, 42, .3);
     margin-right: 5px;
   }
 
+  .pie-item1 {
+    position: absolute;
+    left: 50%;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 7px;
+    height: 7px;
+    border-radius: 50%;
+    background-color: rgba(255, 0, 42);
+  }
+
   .basic-box {
     height: 180px;
     margin-bottom: 10px;
@@ -258,16 +295,25 @@ let tableData = ref<any[]>([
       display: flex;
       justify-content: space-between;
       align-items: center;
-      width: 25%;
+      width: calc(25% - 10px);
       height: 100%;
-      padding: 0px 40px;
+      // padding: 0px 40px;
+      margin: 0 5px;
+      padding: 0px 24px;
 
       &:nth-child(odd) {
         background: var(--image-box-bg1) no-repeat center;
+        background-size: 100% auto;
       }
 
       &:nth-child(even) {
         background: var(--image-box-bg2) no-repeat center;
+        background-size: 100% auto;
+      }
+
+      .value-text {
+        width: 100px;
+        text-align: right;
       }
     }
   }
@@ -306,16 +352,25 @@ let tableData = ref<any[]>([
       display: flex;
       justify-content: space-between;
       align-items: center;
-      width: 25%;
+      width: calc(25% - 10px);
       height: 100%;
-      padding: 0px 40px;
+      // padding: 0px 40px;
+      margin: 0px 5px;
+      padding: 0px 24px;
 
       &:nth-child(even) {
         background: var(--image-box-bg1) no-repeat center;
+        background-size: 100% auto;
       }
 
       &:nth-child(odd) {
         background: var(--image-box-bg2) no-repeat center;
+        background-size: 100% auto;
+      }
+
+      .value-text {
+        width: 100px;
+        text-align: right;
       }
     }
   }
@@ -424,19 +479,23 @@ let tableData = ref<any[]>([
     justify-content: center;
     align-items: center;
   }
-  .item-text1{
+
+  .item-text1 {
     color: #fc002a;
   }
-   .item-text2{
+
+  .item-text2 {
     color: orange;
   }
-  .text-look{
+
+  .text-look {
     padding: 0px 5px;
     border-radius: 2px;
     background-color: #2484bc;
     cursor: pointer;
   }
-   ::-webkit-scrollbar{
+
+  ::-webkit-scrollbar {
     display: none;
   }
 }

+ 58 - 12
src/views/vent/monitorManager/hsqHome/components/DeviceLeft.vue

@@ -1,4 +1,3 @@
-
 <template>
   <div class="long-list">
     <div class="list-title">
@@ -15,7 +14,8 @@
             :key="index" @click="changeNav(item, index)">{{ item.label }}</div>
         </div>
         <div class="content-box">
-          <basicDevice v-for="(item,index) in 10" :key="index"></basicDevice>
+          <basicDevice v-for="(item, index) in listData" :key="index" :deviceData="item" @detail="handlerDetail">
+          </basicDevice>
         </div>
       </div>
     </div>
@@ -24,9 +24,12 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { onMounted, onUnmounted, ref } from 'vue'
 import basicDevice from './basicDevice.vue'
+import { managesysList, monitorSystem } from '../hsqHome.api.js'
 
+//场景ID
+let systemId = ref('')
 //设备列表数量
 let count = ref(0)
 //搜索条件
@@ -34,20 +37,61 @@ let searchData = ref('')
 //当前激活nav选项
 let activeIndex = ref(0)
 let navList = ref<any[]>([
-  { label: '全部', value: '0' },
-  { label: 'xxx', value: '1' },
-  { label: 'xxx', value: '2' },
-  { label: 'xxx', value: '3' },
-  { label: 'xxx', value: '4' },
-  { label: 'xxx', value: '5' },
+  { label: '光谱摄像机', value: '0' },
 ])
+let listData = ref<any[]>([])
+let $emit = defineEmits(['details'])
+// https获取监测数据
+let timer: null | NodeJS.Timeout = null;
+
+/**
+ * 定时获取监测数据
+ * 通过定时器循环调用数据获取方法,更新表格数据
+ *
+ * @param flag - 是否立即执行(首次调用时传true跳过延时)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getData()
+    if (timer) {
+      timer = null;
+    }
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
 
 //nav选项切换
 function changeNav(item, index) {
   activeIndex.value = index
 }
-</script>
 
+async function getData() {
+  const res = await monitorSystem({ type: 'all', systemID: systemId.value, devicetype: '' });
+  console.log(res, '设备列表数据')
+  listData.value = res.deviceInfo.duaSpeCamera.datalist || [];
+  count.value = res.deviceInfo.duaSpeCamera.datalist.length;
+}
+
+function handlerDetail(item) {
+  $emit('details', item)
+}
+
+async function getManagesysList() {
+  const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
+  if (res && res.records) {
+    systemId.value = res.records[0].id
+  }
+}
+onMounted(async () => {
+  await getManagesysList()
+  getMonitor(true)
+})
+onUnmounted(() => {
+  if (timer) {
+    clearTimeout(timer)
+  }
+})
+</script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
@@ -119,7 +163,8 @@ function changeNav(item, index) {
     background-color: #1d73a8;
     border-bottom: 2px solid #36c7ff;
   }
-  .content-box{
+
+  .content-box {
     height: calc(100% - 42px);
     overflow-y: auto;
   }
@@ -136,7 +181,8 @@ function changeNav(item, index) {
   .zxm-input-sm {
     padding: 0px 20px;
   }
-  ::-webkit-scrollbar{
+
+  ::-webkit-scrollbar {
     display: none;
   }
 }

+ 44 - 43
src/views/vent/monitorManager/hsqHome/components/DeviceRight.vue

@@ -9,27 +9,27 @@
           :key="index" @click="changeNav(item, index)">{{ item.label }}</div>
       </div>
       <div class="basic-box">
-        <basicBorder title="【温度报警阈值】">
+        <basicBorder title="【设备基本信息】">
           <div class="temp-warn">
             <div class="warn-item">
-              <div class="item-label">一级报警温度 : </div>
-              <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+              <div class="item-label">安装位置 : </div>
+              <a-input class="item-input" v-model:value="formData.strinstallpos" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
-              <div class="item-label">二级报警温度 : </div>
-              <a-input class="item-input" v-model:value="formData.tempTwo" placeholder="请输入" size="small" />
+              <div class="item-label">设备编码 : </div>
+              <a-input class="item-input" v-model:value="formData.strserno" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
-              <div class="item-label">三级报警温度 : </div>
-              <a-input class="item-input" v-model:value="formData.tempThree" placeholder="请输入" size="small" />
+              <div class="item-label">生产厂家 : </div>
+              <a-input class="item-input" v-model:value="formData.manufacturer" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
-              <div class="item-label">温升速率阈值 : </div>
-              <a-input class="item-input" v-model:value="formData.tempMax" placeholder="请输入" size="small" />
+              <div class="item-label">坐标x : </div>
+              <a-input class="item-input" v-model:value="formData.alarm_east" placeholder="请输入" size="small" />
             </div>
             <div class="warn-item">
-              <div class="item-label">持续时间 : </div>
-              <a-input class="item-input" v-model:value="formData.time" placeholder="请输入" size="small" />
+              <div class="item-label">坐标y : </div>
+              <a-input class="item-input" v-model:value="formData.alarm_north" placeholder="请输入" size="small" />
             </div>
           </div>
         </basicBorder>
@@ -40,41 +40,41 @@
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label">亮度 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
               <div class="item-basic">
                 <div class="item-label">对比度 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label">饱和度 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
               <div class="item-basic">
                 <div class="item-label">锐度 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label">色温 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
               <div class="item-basic">
                 <div class="item-label">增益 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label">曝光 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
               <div class="item-basic">
                 <div class="item-label">白平衡 : </div>
-                <a-input class="item-input" v-model:value="formData.tempOne" placeholder="请输入" size="small" />
+                <a-input class="item-input" v-model:value="formData.x" placeholder="请输入" size="small" />
               </div>
             </div>
           </div>
@@ -86,41 +86,41 @@
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label1">声光报警器联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
               <div class="item-basic">
                 <div class="item-label1">短信通知联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label1">视频录制联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
               <div class="item-basic">
                 <div class="item-label1">消防系统联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label1">应急广播联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
               <div class="item-basic">
                 <div class="item-label1">大屏显示联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
             </div>
             <div class="warn-item">
               <div class="item-basic">
                 <div class="item-label1">GIS定位联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
               <div class="item-basic">
                 <div class="item-label1">数据记录联动 : </div>
-                <a-switch v-model:checked="formData.checked" />
+                <a-switch v-model:checked="formData.x" />
               </div>
             </div>
           </div>
@@ -137,32 +137,32 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue'
+import { reactive, ref, watch } from 'vue'
 import basicBorder from './basicBorder.vue'
 
+let props = defineProps({
+  detailData: {
+    type: Object,
+    default: () => { },
+  },
+})
+
 //当前激活nav选项
 let activeIndex = ref(0)
 let navList = ref<any[]>([
-  { label: '报警阈值', value: '0' },
-  { label: 'xxx', value: '1' },
-  { label: 'xxx', value: '2' },
-  { label: 'xxx', value: '3' },
-  { label: 'xxx', value: '4' },
-  { label: 'xxx', value: '5' },
+  { label: '光谱摄像机', value: '0' },
 ])
-let formData = reactive({
-  tempOne: '',
-  tempTwo: '',
-  tempThree: '',
-  tempMax: '',
-  time: '',
-  checked: true
-})
+
+let formData = ref<any>({})
 
 //nav选项切换
 function changeNav(item, index) {
   activeIndex.value = index
 }
+
+watch(() => props.detailData, (newVal, oldVal) => {
+  formData.value = Object.assign({}, newVal,newVal.readData)
+}, { immediate: true })
 </script>
 
 <style lang="less" scoped>
@@ -308,7 +308,8 @@ function changeNav(item, index) {
     height: 100%;
     background-color: rgba(32, 166, 169);
   }
-   ::-webkit-scrollbar{
+
+  ::-webkit-scrollbar {
     display: none;
   }
 }

+ 1 - 13
src/views/vent/monitorManager/hsqHome/components/EnvironmentMonitor.vue

@@ -20,19 +20,7 @@ let props = defineProps({
  environData: {
     type: Object,
     default: () => {
-      return {
-        tempHj: '',
-        fsfx: '',
-        temp: '',
-        coval: '',
-        co2val: '',
-        o2val: '',
-        ch4val: '',
-        fcnd: '',
-        ywzs: '',
-        tempDb: '',
-
-      }
+      return {}
     }
   }
 })

+ 263 - 0
src/views/vent/monitorManager/hsqHome/components/LinkLogPanel.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="search-table">
+    <div class="list-title">
+      <span>联动日志</span>
+    </div>
+    <div class="search-area">
+      <div class="title-top">
+        <a-input v-model:value="searchWarn" placeholder="搜索" size="small" class="zxm-input zxm-input-sm" />
+        <div class="search-btn">
+          <div class="btn-item">筛选</div>
+        </div>
+      </div>
+    </div>
+
+    <div class="time-select">
+      <span class="span">时间范围:</span>
+      <div
+        class="btn"
+        :class="{ active: timeType === item.value }"
+        v-for="item in timeOptions"
+        :key="item.value"
+        @click="timeType = item.value"
+      >
+        {{ item.label }}
+      </div>
+    </div>
+    <div class="content-area">
+      <div class="content-title">
+        <div class="title-item col-time">时间</div>
+        <div class="title-item col-rule">规则</div>
+        <div class="title-item col-grade">动作</div>
+        <div class="title-item col-status">说明</div>
+      </div>
+      <div class="history-content">
+        <div class="content-item" v-for="(item, index) in tableData" :key="index">
+          <div class="item-text col-time">{{ item.time }}</div>
+          <div class="item-text col-rule">{{ item.rule }}</div>
+          <div class="item-text col-grade item-text2">{{ item.grade }}</div>
+          <div class="item-text col-status item-text1">{{ item.status }}</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+interface ILogItem {
+  time: string;
+  rule: string;
+  grade: string;
+  status: string;
+}
+
+interface ITimeOption {
+  label: string;
+  value: number;
+}
+
+const searchWarn = ref('');
+const timeType = ref(1);
+
+const timeOptions: ITimeOption[] = [
+  { label: '今日', value: 1 },
+  { label: '近三天', value: 2 },
+  { label: '近七天', value: 3 },
+  { label: '自定义', value: 4 },
+];
+
+const tableData = ref<ILogItem[]>([
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+  { time: '14:31:22', rule: '001', grade: '触发', status: '温度65.3℃超过阈值' },
+]);
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+.search-table {
+  --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
+  --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-6.png');
+  --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-24.png');
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 15px;
+  box-sizing: border-box;
+}
+
+.list-title {
+  width: 177px;
+  height: 30px;
+  line-height: 22px;
+  color: #01fefc;
+  font-weight: bolder;
+  background: var(--image-box-bg) no-repeat;
+  background-size: 80% 100%;
+  margin-bottom: 5px;
+}
+
+.search-area {
+  margin-bottom: 5px;
+}
+
+.title-top {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.search-btn {
+  width: 85px;
+  height: 30px;
+  border: 1px solid #01fefc;
+  border-radius: 4px;
+  padding: 3px;
+  cursor: pointer;
+  flex-shrink: 0;
+}
+
+.time-select {
+  display: flex;
+  align-items: center;
+  padding-left: 10px;
+  margin-bottom: 8px;
+  gap: 6px;
+  white-space: nowrap;
+
+  .span {
+    color: #fff;
+    font-size: 13px;
+  }
+
+  .btn {
+    padding: 3px 8px;
+    background: #114268;
+    border: 1px solid transparent;
+    border-radius: 4px;
+    color: #fff;
+    cursor: pointer;
+    margin-right: 4px;
+    font-size: 13px;
+    white-space: nowrap;
+    transition: all 0.2s;
+
+    &.active {
+      background-color: #185f8e;
+      border-color: #2084c0;
+    }
+  }
+}
+
+.btn-item {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(32, 166, 169);
+}
+
+.content-area {
+  height: calc(100% - 120px);
+}
+
+.content-title {
+  display: flex;
+  align-items: center;
+  height: 34px;
+  background: var(--image-box-bg2) no-repeat;
+  background-size: 100% 100%;
+  margin-bottom: 6px;
+}
+
+.title-item {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: #01fefc;
+}
+
+.history-content {
+  height: calc(100% - 40px);
+  overflow-y: auto;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+
+.content-item {
+  display: flex;
+  align-items: center;
+  height: 36px;
+
+  &:nth-child(odd) {
+    background-color: #0e3455;
+  }
+
+  &:nth-child(even) {
+    background-color: #114268;
+  }
+}
+
+.item-text {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.col-time {
+  width: 90px;
+  flex-shrink: 0;
+}
+
+.col-rule {
+  width: 70px;
+  flex-shrink: 0;
+}
+
+.col-grade {
+  width: 70px;
+  flex-shrink: 0;
+}
+
+.col-status {
+  flex: 1;
+  padding: 0 10px;
+  justify-content: center;
+}
+
+.item-text1 {
+  color: #fc002a;
+}
+
+.item-text2 {
+  color: orange;
+}
+
+.zxm-input {
+  height: 42px;
+  color: #fff;
+  background-color: transparent;
+  border: none;
+  background: var(--image-box-bg1) no-repeat;
+  background-size: 100% 100%;
+}
+
+.zxm-input-sm {
+  padding: 0 20px;
+}
+</style>

+ 203 - 137
src/views/vent/home/configurable/components/preset/linkRuleEdit.vue → src/views/vent/monitorManager/hsqHome/components/LinkRuleEditor.vue

@@ -7,29 +7,9 @@
       <basicBorder title="触发条件设置">
         <div class="edit-wrap">
           <div class="edit-left">
-            <div class="edit-item">
-              <div class="item-label">规则名称 : </div>
-              <a-input class="item-input" v-model:value="formData.gzmc" placeholder="请输入" size="small" />
-            </div>
-            <div class="edit-item">
-              <div class="item-label">报警级别 : </div>
-              <a-input class="item-input" v-model:value="formData.bjjb" placeholder="请输入" size="small" />
-            </div>
-            <div class="edit-item">
-              <div class="item-label">温度阈值 : </div>
-              <a-input class="item-input" v-model:value="formData.wdyz" placeholder="请输入" size="small" />
-            </div>
-            <div class="edit-item">
-              <div class="item-label">触发设备 : </div>
-              <a-input class="item-input" v-model:value="formData.cfsb" placeholder="请输入" size="small" />
-            </div>
-            <div class="edit-item">
-              <div class="item-label">持续时间 : </div>
-              <a-input class="item-input" v-model:value="formData.time" placeholder="请输入" size="small" />
-            </div>
-            <div class="edit-item">
-              <div class="item-label">时间范围 : </div>
-              <a-input class="item-input" v-model:value="formData.sjfw" placeholder="请输入" size="small" />
+            <div class="edit-item" v-for="(field, index) in formFields" :key="index">
+              <div class="item-label">{{ field.label }} :</div>
+              <a-input class="item-input" v-model:value="formData[field.key]" placeholder="请输入" size="small" />
             </div>
           </div>
           <div class="edit-right">
@@ -37,7 +17,7 @@
             <div class="right-content">
               <div class="right-item">IF 温度≥{{ formData.wdyz || 60 }}℃</div>
               <div class="right-item">AND 持续时间≥{{ formData.time || 30 }}s</div>
-              <div class="right-item">AND 设备状态-在线</div>
+              <div class="right-item">AND 设备状态=在线</div>
               <div class="right-item">AND 时间在生效范围内</div>
               <div class="right-item">THEN 执行联动动作</div>
               <div class="right-item">【优先级:高】</div>
@@ -55,22 +35,22 @@
           </div>
           <div class="table-content">
             <div class="content-item" v-for="(item, index) in tableData" :key="index">
-              <div class="item-text">{{ item.indnex }}</div>
+              <div class="item-text">{{ item.index }}</div>
               <div class="item-text item-text1">
                 <span>{{ item.actionType }}</span>
               </div>
               <div class="item-text">
-                <span> {{ item.action }}</span>
+                <span>{{ item.action }}</span>
               </div>
               <div class="item-text">
-                <div class="switch" :class="{ active: item.qy === 1 }">
+                <div class="switch" :class="{ active: item.qy === 1 }" @click="toggleSwitch(item)">
                   <div class="switch-dot"></div>
                 </div>
               </div>
               <div class="item-text">{{ item.delayTime }}</div>
               <div class="item-text">{{ item.duration }}</div>
               <div class="item-text">
-                <span class="text-look">查看</span>
+                <span class="text-look">配置</span>
               </div>
             </div>
           </div>
@@ -82,11 +62,12 @@
         <div class="workflow-container">
           <div class="steps-wrapper">
             <div v-for="(step, index) in steps" :key="index" class="step-item">
-              <div class="step-icon" :class="`icon-${step.id}`"></div>
-              <div class="step-frame" :class="`frame-${step.id}`">
+              <div class="step-icon" :class="`icon-${step.id}`">
+                <span class="icon-inner"></span>
+              </div>
+              <div class="step-frame">
                 <span class="step-text">{{ step.name }}</span>
               </div>
-              <!-- 底部小圆点 -->
               <div class="step-dot">
                 <div class="dot" :class="{ active: step.status === 1 }"></div>
               </div>
@@ -101,69 +82,80 @@
 
 <script setup lang="ts">
 import { reactive, ref, onMounted } from 'vue';
-import basicBorder from '../BasicBorder.vue';
-
-let titleList = ref<any[]>([
-  { label: '序号', value: '1' },
-  { label: '动作类型', value: '2' },
-  { label: '动作说明', value: '3' },
-  { label: '启用', value: '4' },
-  { label: '延迟', value: '5' },
-  { label: '持续', value: '6' },
-  { label: '操作', value: '7' },
+import basicBorder from './basicBorder.vue';
+
+interface ITitleItem {
+  label: string;
+}
+
+interface IActionItem {
+  index: string;
+  actionType: string;
+  action: string;
+  qy: number;
+  delayTime: string;
+  duration: string;
+}
+
+interface IStepItem {
+  id: string;
+  name: string;
+  status: number;
+}
+
+interface IFormField {
+  label: string;
+  key: keyof typeof formData;
+}
+
+const titleList = ref<ITitleItem[]>([
+  { label: '序号' },
+  { label: '动作类型' },
+  { label: '动作说明' },
+  { label: '启用' },
+  { label: '延迟' },
+  { label: '持续' },
+  { label: '操作' },
 ]);
 
-let tableData = ref<any[]>([
-  { indnex: '1', actionType: '声光报警器', action: '触发报警声+红色闪烁', qy: 1, delayTime: '0s', duration: '300s' },
-  { indnex: '2', actionType: '声光报警器', action: '触发报警声', qy: 1, delayTime: '1s', duration: '200s' },
-  { indnex: '3', actionType: '声光报警器', action: '红色闪烁', qy: 0, delayTime: '2s', duration: '180s' },
+const tableData = ref<IActionItem[]>([
+  { index: '1', actionType: '声光报警器', action: '触发报警声+红色闪烁', qy: 1, delayTime: '0s', duration: '300s' },
+  { index: '2', actionType: '声光报警器', action: '触发报警声', qy: 1, delayTime: '1s', duration: '200s' },
+  { index: '3', actionType: '声光报警器', action: '红色闪烁', qy: 0, delayTime: '2s', duration: '180s' },
 ]);
 
-// 流程图,添加 status 字段
-const steps = ref([
-  { id: 'alarm', name: '报警触发', status: 0 },
+const steps = ref<IStepItem[]>([
+  { id: 'alarm', name: '报警触发', status: 1 },
   { id: 'condition', name: '条件判断', status: 0 },
   { id: 'search', name: '查找规则', status: 0 },
   { id: 'action', name: '执行动作', status: 0 },
   { id: 'log', name: '记录日志', status: 0 },
 ]);
 
-// 模拟接口请求获取步骤状态
-const fetchStepStatus = async () => {
-  // 模拟 API 返回的数据,实际使用时替换为真实接口
-  const apiResponse = {
-    alarmTrigger: 1, // 报警触发为 1
-    conditionJudge: 0, // 条件判断为 1
-    searchRule: 0, // 查找规则为 0
-    executeAction: 0, // 执行动作为 0
-    recordLog: 0, // 记录日志为 0
-  };
-
-  // 根据接口返回数据更新状态
-  steps.value[0].status = apiResponse.alarmTrigger;
-  steps.value[1].status = apiResponse.conditionJudge;
-  steps.value[2].status = apiResponse.searchRule;
-  steps.value[3].status = apiResponse.executeAction;
-  steps.value[4].status = apiResponse.recordLog;
-};
-let formData = reactive({
+const formFields: IFormField[] = [
+  { label: '规则名称', key: 'gzmc' },
+  { label: '报警级别', key: 'bjjb' },
+  { label: '温度阈值', key: 'wdyz' },
+  { label: '触发设备', key: 'cfsb' },
+  { label: '持续时间', key: 'time' },
+  { label: '时间范围', key: 'sjfw' },
+];
+
+const formData = reactive({
   gzmc: '一级温度报警',
   bjjb: '一级(严重)',
-  wdyz: '50',
+  wdyz: '60',
   cfsb: '声光报警',
-  time: '≥35',
+  time: '≥30',
   sjfw: '值班人员',
 });
-// const formData = ref({
-//   gzmc: '',
-//   bjjb: '',
-//   wdyz: '',
-//   cfsb: '',
-//   time: '',
-//   sjfw: '',
-// });
+
+function toggleSwitch(item: IActionItem) {
+  item.qy = item.qy === 1 ? 0 : 1;
+}
+
 onMounted(() => {
-  fetchStepStatus();
+  // 模拟流程状态
 });
 </script>
 
@@ -172,11 +164,6 @@ onMounted(() => {
 
 .long-list1 {
   --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
-  --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-13.png');
-  --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-14.png');
-  --image-box-bg3: url('@/assets/images/home-container/configurable/hsq/2-15.png');
-  --image-box-bg4: url('@/assets/images/home-container/configurable/hsq/2-16.png');
-  --image-box-bg5: url('@/assets/images/home-container/configurable/hsq/2-17.png');
   --image-box-bg6: url('@/assets/images/home-container/configurable/hsq/2-24.png');
 
   position: relative;
@@ -186,22 +173,50 @@ onMounted(() => {
   box-sizing: border-box;
   display: flex;
   flex-direction: column;
+
   .item-input {
     background: transparent !important;
     background: linear-gradient(to right, #114469, #0b2542) !important;
     color: #fff;
     border: 1px solid #2690c1 !important;
     border-radius: 5px;
+    box-shadow: none !important;
+
     &:hover,
     &:focus {
       background: transparent !important;
     }
-    box-shadow: none !important;
   }
-  .basic-box1,
+
+  .basic-box {
+    flex: 1.2;
+    min-height: 0;
+
+    :deep(.basic-border) {
+      height: 100%;
+    }
+  }
+
+  .basic-box1 {
+    flex: 0.85;
+    min-height: 0;
+    margin-top: 10px;
+
+    :deep(.basic-border) {
+      height: 100%;
+    }
+  }
+
   .basic-box2 {
+    flex: 0.85;
+    min-height: 0;
     margin-top: 10px;
+
+    :deep(.basic-border) {
+      height: 100%;
+    }
   }
+
   .edit-wrap {
     display: flex;
     width: 100%;
@@ -209,6 +224,7 @@ onMounted(() => {
     gap: 10px;
     box-sizing: border-box;
   }
+
   .edit-left {
     flex: 1;
     display: flex;
@@ -225,16 +241,15 @@ onMounted(() => {
     margin-bottom: 10px;
     padding: 0 10px;
     box-sizing: border-box;
-    &:last-child {
-      margin-bottom: 10px;
-    }
   }
 
   .item-label {
     width: 80px;
     text-align: right;
     margin-right: 5px;
+    flex-shrink: 0;
   }
+
   .edit-right {
     background: #0e395d;
     width: 50%;
@@ -272,21 +287,26 @@ onMounted(() => {
     line-height: 25px;
     font-weight: bold;
     padding: 0 5px;
+
+    &:nth-child(1) {
+      color: #fc002a;
+    }
+
+    &:nth-child(2),
+    &:nth-child(3),
+    &:nth-child(4) {
+      color: #29a1d4;
+    }
+
+    &:nth-child(5) {
+      color: #46e8a3;
+    }
+
+    &:nth-child(6) {
+      margin-top: 10px;
+    }
   }
-  .right-item:nth-child(1) {
-    color: #fc002a;
-  }
-  .right-item:nth-child(2),
-  .right-item:nth-child(3),
-  .right-item:nth-child(4) {
-    color: #29a1d4;
-  }
-  .right-item:nth-child(5) {
-    color: #46e8a3;
-  }
-  .right-item:nth-child(6) {
-    margin-top: 10px;
-  }
+
   .list-title {
     width: 177px;
     height: 30px;
@@ -298,17 +318,13 @@ onMounted(() => {
     background: var(--image-box-bg) no-repeat;
     background-size: 80% 100%;
     margin-bottom: 5px;
+    flex-shrink: 0;
   }
 
   .basic-table {
     height: 100%;
   }
 
-  .item-label1 {
-    width: 105px;
-    text-align: right;
-    margin-right: 5px;
-  }
   .table-title {
     display: flex;
     align-items: center;
@@ -332,15 +348,21 @@ onMounted(() => {
   .table-content {
     height: calc(100% - 40px);
     overflow-y: auto;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
   }
 
   .content-item {
     display: flex;
     align-items: center;
     height: 36px;
+
     &:nth-child(odd) {
       background-color: #0e3455;
     }
+
     &:nth-child(even) {
       background-color: #114268;
     }
@@ -360,9 +382,6 @@ onMounted(() => {
   .item-text1 {
     color: #fc002a;
   }
-  .item-text2 {
-    color: orange;
-  }
 
   .switch {
     width: 36px;
@@ -373,6 +392,7 @@ onMounted(() => {
     transition: all 0.2s ease;
     cursor: pointer;
     border: 1px solid #35d9e5;
+
     .switch-dot {
       width: 14px;
       height: 14px;
@@ -383,9 +403,10 @@ onMounted(() => {
       left: 1px;
       transition: all 0.2s ease;
     }
-  }
-  .switch.active .switch-dot {
-    left: 20px;
+
+    &.active .switch-dot {
+      left: 20px;
+    }
   }
 
   .text-look {
@@ -394,20 +415,24 @@ onMounted(() => {
   }
 }
 
-/*流程图*/
 .workflow-container {
   width: 100%;
-  padding: 20px 0;
+  height: 100%;
+  padding: 10px 0;
   position: relative;
+  display: flex;
+  align-items: center;
 }
 
 .steps-wrapper {
   display: flex;
   justify-content: space-between;
   align-items: flex-end;
+  width: 100%;
   padding: 0 20px;
   position: relative;
   z-index: 2;
+
   &::before {
     content: '';
     position: absolute;
@@ -419,6 +444,7 @@ onMounted(() => {
     background: #4a9eff;
     z-index: 1;
   }
+
   &::after {
     content: '';
     position: absolute;
@@ -430,6 +456,7 @@ onMounted(() => {
     background: #4a9eff;
     z-index: 1;
   }
+
   .steps-arrow {
     position: absolute;
     left: 5%;
@@ -440,10 +467,12 @@ onMounted(() => {
     background: url('@/assets/images/home-container/configurable/hsq/arrow.svg') center / contain no-repeat;
     z-index: 2;
   }
+
   .step-item:first-child .steps-arrow {
     display: none;
   }
 }
+
 .step-item {
   display: flex;
   flex-direction: column;
@@ -455,26 +484,62 @@ onMounted(() => {
 .step-icon {
   width: 56px;
   height: 56px;
-  background-size: contain;
-  background-repeat: no-repeat;
-  background-position: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
   margin-bottom: -6px;
-}
+  position: relative;
 
-.icon-alarm {
-  background-image: url('@/assets/images/home-container/configurable/hsq/bjcf.png');
-}
-.icon-condition {
-  background-image: url('@/assets/images/home-container/configurable/hsq/tjpd.png');
-}
-.icon-search {
-  background-image: url('@/assets/images/home-container/configurable/hsq/czgz.png');
-}
-.icon-action {
-  background-image: url('@/assets/images/home-container/configurable/hsq/zxdz.png');
+  .icon-inner {
+    width: 36px;
+    height: 36px;
+    border: 2px solid #4facfe;
+    transform: rotate(45deg);
+    background: linear-gradient(135deg, rgba(20, 80, 140, 0.9), rgba(10, 40, 80, 0.9));
+    box-shadow: 0 0 12px rgba(79, 172, 254, 0.5);
+    position: relative;
+
+    &::after {
+      content: '';
+      position: absolute;
+      inset: 6px;
+      border: 1px solid rgba(79, 172, 254, 0.6);
+    }
+  }
+
+  &.icon-alarm .icon-inner {
+    background: linear-gradient(135deg, rgba(180, 40, 40, 0.8), rgba(80, 20, 20, 0.9));
+    border-color: #ff6b6b;
+    box-shadow: 0 0 12px rgba(255, 77, 77, 0.5);
+  }
+
+  &.icon-condition .icon-inner {
+    background: linear-gradient(135deg, rgba(30, 100, 160, 0.9), rgba(15, 50, 90, 0.9));
+  }
+
+  &.icon-search .icon-inner {
+    background: linear-gradient(135deg, rgba(40, 120, 100, 0.9), rgba(20, 60, 50, 0.9));
+    border-color: #46e8a3;
+    box-shadow: 0 0 12px rgba(70, 232, 163, 0.4);
+  }
+
+  &.icon-action .icon-inner {
+    background: linear-gradient(135deg, rgba(160, 100, 30, 0.9), rgba(80, 50, 15, 0.9));
+    border-color: #ffa500;
+    box-shadow: 0 0 12px rgba(255, 165, 0, 0.4);
+  }
+
+  &.icon-log .icon-inner {
+    background: linear-gradient(135deg, rgba(60, 80, 160, 0.9), rgba(30, 40, 80, 0.9));
+  }
 }
+
+.icon-alarm,
+.icon-condition,
+.icon-search,
+.icon-action,
 .icon-log {
-  background-image: url('@/assets/images/home-container/configurable/hsq/jlrz.png');
+  background-image: none;
 }
 
 .step-frame {
@@ -519,10 +584,11 @@ onMounted(() => {
   background: #2c5a8c;
   border: 1px solid #4a9eff;
   transition: all 0.3s ease;
+
+  &.active {
+    background: #4facfe;
+    border-color: #4facfe;
+    box-shadow: 0 0 6px rgba(79, 172, 254, 0.6);
+  }
 }
-.dot.active {
-  background: #4facfe;
-  border-color: #4facfe;
-  box-shadow: 0 0 6px rgba(79, 172, 254, 0.6);
-}
-</style>
+</style>

+ 140 - 0
src/views/vent/monitorManager/hsqHome/components/LinkRuleStats.vue

@@ -0,0 +1,140 @@
+<template>
+  <div class="long-list">
+    <div class="list-title">
+      <span>规则统计</span>
+    </div>
+    <div class="list-content">
+      <div class="stat-item" :class="item.type" v-for="(item, index) in statList" :key="index">
+        <div class="icon-wrap" :class="`icon-${index + 1}`"></div>
+        <div class="label-wrap">
+          <span>{{ item.label }}</span>
+        </div>
+        <div class="num-wrap">
+          <span>{{ item.value }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+interface IStatItem {
+  label: string;
+  value: number;
+  type: 'blue' | 'green';
+}
+
+const statList = ref<IStatItem[]>([
+  { label: '总规则', value: 10, type: 'blue' },
+  { label: '已启用', value: 7, type: 'green' },
+  { label: '已禁用', value: 2, type: 'green' },
+  { label: '触发中', value: 2, type: 'blue' },
+]);
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+.long-list {
+  --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
+  --image-box-bg-green: url('@/assets/images/home-container/configurable/hsq/4-2.png');
+  --image-box-bg-blue: url('@/assets/images/home-container/configurable/hsq/4-3.png');
+  --image-box-svg1: url('@/assets/images/home-container/configurable/hsq/4-total.svg');
+  --image-box-svg2: url('@/assets/images/home-container/configurable/hsq/4-cf.svg');
+  --image-box-svg3: url('@/assets/images/home-container/configurable/hsq/4-qy.svg');
+  --image-box-svg4: url('@/assets/images/home-container/configurable/hsq/4-jy.svg');
+
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 10px 15px;
+  box-sizing: border-box;
+
+  .list-title {
+    width: 177px;
+    height: 30px;
+    line-height: 22px;
+    color: #01fefc;
+    font-weight: bolder;
+    background: var(--image-box-bg) no-repeat;
+    background-size: 80% 100%;
+    margin-bottom: 10px;
+  }
+
+  .list-content {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 16px 20px;
+  }
+
+  .stat-item {
+    width: calc(50% - 10px);
+    display: flex;
+    align-items: center;
+    height: 40px;
+    padding-left: 6px;
+    box-sizing: border-box;
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: 100% 100%;
+
+    &.blue {
+      background-image: var(--image-box-bg-blue);
+    }
+
+    &.green {
+      background-image: var(--image-box-bg-green);
+    }
+  }
+
+  .icon-wrap {
+    width: 30px;
+    height: 30px;
+    flex-shrink: 0;
+    background-repeat: no-repeat;
+    background-position: center;
+    background-size: 60% 70%;
+
+    &.icon-1 {
+      background-image: var(--image-box-svg1);
+    }
+
+    &.icon-2 {
+      background-image: var(--image-box-svg3);
+    }
+
+    &.icon-3 {
+      background-image: var(--image-box-svg4);
+    }
+
+    &.icon-4 {
+      background-image: var(--image-box-svg2);
+    }
+  }
+
+  .label-wrap {
+    margin-left: 10px;
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #fff;
+    font-size: 14px;
+    white-space: nowrap;
+  }
+
+  .num-wrap {
+    width: 100px;
+    height: 100%;
+    flex-shrink: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #fff;
+    font-size: 16px;
+    font-weight: bold;
+  }
+}
+</style>

+ 218 - 0
src/views/vent/monitorManager/hsqHome/components/LinkRuleTable.vue

@@ -0,0 +1,218 @@
+<template>
+  <div class="search-table">
+    <div class="search-area">
+      <div class="list-title">
+        <span>联动规则列表</span>
+      </div>
+      <div class="search-btn">
+        <div class="btn-item">新增</div>
+      </div>
+    </div>
+    <div class="content-area">
+      <div class="content-title">
+        <div class="title-item" v-for="(item, index) in titleList" :key="index">{{ item.label }}</div>
+      </div>
+      <div class="history-content">
+        <div
+          class="content-item"
+          :class="{ 'row-active': activeIndex === index }"
+          v-for="(item, index) in tableData"
+          :key="index"
+          @click="activeIndex = index"
+        >
+          <div class="item-text">{{ item.ruleId }}</div>
+          <div class="item-text">{{ item.ruleName }}</div>
+          <div class="item-text item-text1">{{ item.condition }}</div>
+          <div class="item-text">{{ item.action }}</div>
+          <div class="item-text item-text2">{{ item.status }}</div>
+          <div class="item-text">
+            <span class="text-look">编辑</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+interface ITitleItem {
+  label: string;
+}
+
+interface IRuleItem {
+  ruleId: string;
+  ruleName: string;
+  condition: string;
+  action: string;
+  status: string;
+}
+
+const activeIndex = ref(1);
+
+const titleList = ref<ITitleItem[]>([
+  { label: '规则ID' },
+  { label: '规则名称' },
+  { label: '触发条件' },
+  { label: '联动动作' },
+  { label: '状态' },
+  { label: '操作' },
+]);
+
+const tableData = ref<IRuleItem[]>([
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+  { ruleId: 'LINK001', ruleName: '温度报警联动', condition: '温度>60℃', action: '声光报警', status: '启用' },
+]);
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+.search-table {
+  --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
+  --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-24.png');
+  position: relative;
+  width: 100%;
+  height: 100%;
+  padding: 10px;
+  box-sizing: border-box;
+}
+
+.list-title {
+  width: 177px;
+  height: 30px;
+  line-height: 22px;
+  color: #01fefc;
+  font-weight: bolder;
+  background: var(--image-box-bg) no-repeat;
+  background-size: 80% 100%;
+  margin-bottom: 5px;
+}
+
+.search-area {
+  height: 42px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 5px;
+}
+
+.search-btn {
+  width: 85px;
+  height: 30px;
+  border: 1px solid #01fefc;
+  border-radius: 4px;
+  padding: 3px;
+  cursor: pointer;
+}
+
+.btn-item {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(32, 166, 169);
+}
+
+.content-area {
+  height: calc(100% - 47px);
+}
+
+.content-title {
+  display: flex;
+  align-items: center;
+  height: 34px;
+  background: var(--image-box-bg2) no-repeat;
+  background-size: 100% 100%;
+  margin-bottom: 6px;
+}
+
+.title-item {
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+  color: #01fefc;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.history-content {
+  height: calc(100% - 40px);
+  overflow-y: auto;
+
+  &::-webkit-scrollbar {
+    display: none;
+  }
+}
+
+.content-item {
+  display: flex;
+  align-items: center;
+  height: 36px;
+  cursor: pointer;
+
+  &:nth-child(odd) {
+    background-color: #0e3455;
+  }
+
+  &:nth-child(even) {
+    background-color: #114268;
+  }
+
+  &.row-active {
+    border: 1px solid #f4933d;
+    box-shadow: inset 0 0 6px rgba(244, 147, 61, 0.3);
+  }
+}
+
+.item-text {
+  display: flex;
+  flex: 1;
+  justify-content: center;
+  align-items: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  padding: 0 4px;
+}
+
+.title-item:nth-child(2),
+.item-text:nth-child(2) {
+  flex: 1.8;
+}
+
+.title-item:nth-child(3),
+.item-text:nth-child(3) {
+  flex: 1.5;
+}
+
+.title-item:nth-child(4),
+.item-text:nth-child(4) {
+  flex: 1.5;
+}
+
+.item-text1 {
+  color: #f4933d;
+}
+
+.item-text2 {
+  color: #4cf3a6;
+}
+
+.text-look {
+  padding: 0 3px;
+  margin: 0 2px;
+  border-radius: 2px;
+  background-color: #2484bc;
+  cursor: pointer;
+}
+</style>

+ 3 - 1
src/views/vent/monitorManager/hsqHome/components/SystemCenBottom.vue

@@ -37,7 +37,7 @@ watch(()=>props.data,(newV,oldV)=>{
     dayChartData.value=newV.map(el=>{
       return {
         x:el.userName,
-        y:el.loginCount,
+        y:parseFloat(el.loginCount),
       }
     })
   }
@@ -48,6 +48,8 @@ watch(()=>props.data,(newV,oldV)=>{
 .system-box {
    --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-5.png');
   position: relative;
+  display: flex;
+  justify-content: space-between;
   width: 100%;
   height: 100%;
 

+ 5 - 1
src/views/vent/monitorManager/hsqHome/components/SystemLeftBottom.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="log-audit">
     <div class="list-title">
-      <div class="title-l">日志审计</div>
+      <div class="title-l">{{ title }}</div>
       <div class="title-r">
         <div :class="activeIndex == index ? 'basic-btn-active' : 'basic-btn'" v-for="(item, index) in btnOption"
           :key="index" @click="handleClick(item, index)">{{
@@ -34,6 +34,10 @@ interface BtnOption {
 }
 
 let props = defineProps({
+  title: {
+    type: String,
+    default: '',
+  },
   data: {
     type: Array as PropType<any[]>,
     default: () => [],

+ 21 - 13
src/views/vent/monitorManager/hsqHome/components/SystemRightTop.vue

@@ -1,13 +1,13 @@
 <template>
   <div class="permission-manager">
-  <div class="list-title">
+    <div class="list-title">
       <div class="title-l">角色权限管理</div>
       <div class="title-r">
         <div class="basic-btn" @click="handleDetail">详情</div>
       </div>
     </div>
     <div class="list-content">
-      <div class="per-box" v-for="(item, index) in perList" :key="index">
+      <div class="per-box" v-for="(item, index) in userInfo" :key="index">
         <div class="box-label">{{ item.label }}</div>
         <div class="box-value">{{ item.value }}</div>
       </div>
@@ -17,19 +17,20 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
 import { useRouter } from 'vue-router'
 
-let router = useRouter()
-let perList = ref<any[]>([
-  { label: '超级管理员(全部权限)', value: '1' },
-  { label: '安全主管(报警处理+设备管理)', value: '2' },
-  { label: '值班员(监控+报警确认)', value: '2' },
-  { label: '设备维护(设备管理+配置)', value: '2' },
-  { label: '访客(仅查看)', value: '2' },
-])
+interface UserInfoItem {
+  label: string
+  value: string
+}
+
+const props = defineProps<{
+  userInfo?: UserInfoItem[]
+}>()
+
+const router = useRouter()
 
-//详情跳转
+// 详情跳转
 function handleDetail() {
   router.push({
     path: '/monitorChannel/system/role',
@@ -58,7 +59,7 @@ function handleDetail() {
   padding: 15px;
   box-sizing: border-box;
 
- .list-title {
+  .list-title {
     display: flex;
     justify-content: space-between;
     width: 100%;
@@ -99,6 +100,7 @@ function handleDetail() {
   .list-content {
     width: 100%;
     height: calc(100% - 35px);
+    overflow-y: auto;
   }
 
   .per-box {
@@ -108,6 +110,8 @@ function handleDetail() {
     height: 40px;
     padding: 0px 40px;
     margin-bottom: 10px;
+    background: var(--image-box-bg2) no-repeat;
+    background-size: 100% 100%;
 
     &:nth-child(1) {
       background: var(--image-box-bg) no-repeat;
@@ -160,5 +164,9 @@ function handleDetail() {
     font-family: 'douyuFont';
 
   }
+
+  ::-webkit-scrollbar {
+    display: none;
+  }
 }
 </style>

+ 37 - 28
src/views/vent/monitorManager/hsqHome/components/WarnCenTop.vue

@@ -27,27 +27,33 @@
           <div class="warn-detail">
             <div class="warn-item">
               <div class="item-label">报警级别 : </div>
-              <div class="item-val text-val"> {{ warnDetail.grade }}</div>
+              <div class="item-val"
+                :style="{ color: warnData.alarmLevel == 101 ? '#00d8ff' : warnData.alarmLevel == 102 ? '#fcfc22' : warnData.alarmLevel == 103 ? '#ff7010' : warnData.alarmLevel == 104 ? '#df4e43' : warnData.alarmLevel == 201 ? '#ff0000' : '' }">
+                {{ warnData.alarmLevel == 101 ? '低风险' : warnData.alarmLevel ==
+                  102 ? '一般风险' :
+                  warnData.alarmLevel == 103 ? '较大风险' : warnData.alarmLevel == 104 ? '重大风险' : warnData.alarmLevel == 201 ?
+                    '报警' :
+                '低风险' }}</div>
             </div>
             <div class="warn-item">
               <div class="item-label">报警时间 : </div>
-              <div class="item-val"> {{ warnDetail.time }}</div>
+              <div class="item-val"> {{ warnData.starttime || '-' }}</div>
             </div>
             <div class="warn-item">
               <div class="item-label">报警位置 : </div>
-              <div class="item-val"> {{ warnDetail.address }}</div>
+              <div class="item-val"> {{ warnData.alarmName || '-' }}</div>
             </div>
             <div class="warn-item">
               <div class="item-label">监测设备 : </div>
-              <div class="item-val"> {{ warnDetail.device }}</div>
+              <div class="item-val"> {{ warnData.device || '-' }}</div>
             </div>
             <div class="warn-item">
               <div class="item-label ">最高温度 : </div>
-              <div class="item-val text-val"> {{ warnDetail.maxtemp }}</div>
+              <div class="item-val text-val"> {{ warnData.maxtemp || '-' }}</div>
             </div>
             <div class="warn-item">
               <div class="item-label">平均速度 : </div>
-              <div class="item-val text-val1"> {{ warnDetail.speed }}</div>
+              <div class="item-val text-val1"> {{ warnData.speed || '-' }}</div>
             </div>
           </div>
         </basicBorder>
@@ -73,7 +79,7 @@
           </div>
           <div class="flow-step">
             <a-steps progress-dot :current="1">
-              <a-step title="" description="" v-for="(item,index) in 5" :key="index" />
+              <a-step title="" description="" v-for="(item, index) in 5" :key="index" />
             </a-steps>
           </div>
         </div>
@@ -87,15 +93,14 @@ import { reactive, ref } from 'vue'
 import basicBorder from './basicBorder.vue'
 import { getAssetURL } from '/@/utils/ui';
 
-let deviceModel = ref('CAM-001')
-let warnDetail = reactive({
-  grade: '一级报警(严重)',
-  time: '2024-12-20 14:31:22',
-  address: '采掘场A区,东经87.6,北纬43.8',
-  device: '双光谱相机',
-  maxtemp: '65.3',
-  speed: '48.7'
+let props = defineProps({
+  warnData: {
+    type: Object,
+    required: true
+  }
 })
+
+let deviceModel = ref('-')
 let picList = ref([
   { src: getAssetURL('home-container/configurable/hsq/4-1.png'), label: '1.报警触发' },
   { src: getAssetURL('home-container/configurable/hsq/6-1.png'), label: '2.系统确认' },
@@ -286,22 +291,26 @@ let picList = ref([
       border-radius: 5px;
     }
   }
-  .flow-step{
-      width: 100%;
-      position: absolute;
-      bottom: 0px;
-      left: 0;
-      padding: 0px 30px;
-    }
-  
+
+  .flow-step {
+    width: 100%;
+    position: absolute;
+    bottom: 0px;
+    left: 0;
+    padding: 0px 30px;
+  }
+
 }
-:deep(.zxm-steps-icon .zxm-steps-icon-dot){
+
+:deep(.zxm-steps-icon .zxm-steps-icon-dot) {
   border: 1px solid #2cb6ff !important;
 }
-:deep(.zxm-steps-item-process > .zxm-steps-item-container > .zxm-steps-item-tail::after){
-     background-color: rgba(28, 118, 172,.6) !important;
+
+:deep(.zxm-steps-item-process > .zxm-steps-item-container > .zxm-steps-item-tail::after) {
+  background-color: rgba(28, 118, 172, .6) !important;
 }
-:deep(.zxm-steps-item-wait > .zxm-steps-item-container > .zxm-steps-item-tail::after){
-   background-color: rgba(28, 118, 172,.6) !important;
+
+:deep(.zxm-steps-item-wait > .zxm-steps-item-container > .zxm-steps-item-tail::after) {
+  background-color: rgba(28, 118, 172, .6) !important;
 }
 </style>

+ 104 - 48
src/views/vent/monitorManager/hsqHome/components/WarnLeftTop.vue

@@ -1,26 +1,44 @@
+<!-- 报警信息左侧顶部组件:包含报警搜索、报警列表展示及详情查看功能 -->
 <template>
   <div class="search-table">
+    <!-- 搜索区域:包含搜索输入框和筛选按钮 -->
     <div class="search-area">
       <a-input v-model:value="searchWarn" placeholder="搜索报警" size="small" />
       <div class="search-btn">
         <div class="btn-item">筛选</div>
       </div>
     </div>
+    <!-- 内容区域:包含表头和表格数据 -->
     <div class="content-area">
+      <!-- 表头:根据 warnTitle 配置动态渲染列标题 -->
       <div class="content-title">
-        <div class="title-item" v-for="(item, index) in titleList" :key="index">{{ item.label }}</div>
+        <div class="title-item" v-for="(item, index) in warnTitle" :key="index">{{ item.label }}</div>
       </div>
+      <!-- 表格数据列表:遍历 tableData 渲染每行报警信息 -->
       <div class="table-content">
         <div class="content-item" v-for="(item, index) in tableData" :key="index">
-          <div class="item-text">{{ item.time }}</div>
-          <div class="item-text">{{ item.address }}
-          </div>
-          <div class="item-text item-text1">{{ item.grade }}</div>
-          <div class="item-text item-text2">{{ item.status }}</div>
-          <div class="item-text">
-            <span class="text-look">确认</span>
-            <span class="text-look">详情</span>
+          <div class="item-text" v-for="(ite, ind) in warnTitle" :key="ind">
+            <!-- 报警时间列 -->
+            <div v-if="ite.value == 'starttime'">{{ item[ite.value] }}</div>
+            <!-- 报警名称列 -->
+            <div v-if="ite.value == 'alarmName'">{{ item[ite.value] }}</div>
+            <!-- 报警等级列:根据不同等级码显示对应颜色和文字 -->
+            <!-- 101-低风险(蓝色) 102-一般风险(黄色) 103-较大风险(橙色) 104-重大风险(红色) 201-报警(深红色) -->
+            <div v-if="ite.value == 'alarmLevel'"
+              :style="{ color: item[ite.value] == 101 ? '#00d8ff' : item[ite.value] == 102 ? '#fcfc22' : item[ite.value] == 103 ? '#ff7010' : item[ite.value] == 104 ? '#df4e43' : item[ite.value] == 201 ? '#ff0000' : '' }">
+              {{ item[ite.value] == 101 ? '低风险' : item[ite.value] ==
+                102 ? '一般风险' :
+                item[ite.value] == 103 ? '较大风险' : item[ite.value] == 104 ? '重大风险' : item[ite.value] == 201 ? '报警' :
+                  '低风险' }}</div>
+            <!-- 处理状态列:0-未处理(红色) 其他-已处理(绿色) -->
+            <div v-if="ite.value == 'isok'" :style="{ color: item[ite.value] === 0 ? '#fd0404' : '#0eca74' }">{{
+              item[ite.value] === 0 ? '未处理' : '已处理' }}</div>
+            <!-- 操作列:点击"详情"触发详情查看 -->
+            <div v-if="ite.value == 'operation'">
+              <span class="text-look" @click="handlerDetail(item)">详情</span>
+            </div>
           </div>
+
         </div>
       </div>
     </div>
@@ -28,35 +46,26 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
-
+import { ref, type PropType } from 'vue'
+import { warnTitle } from '../hsqHome.data'
+
+// 接收父组件传入的报警数据列表
+let props = defineProps({
+  tableData: {
+    type: Array as PropType<any[]>,
+    default: () => [],
+  },
+})
+// 报警搜索关键词
 let searchWarn = ref('')
-let titleList = ref<any[]>([
-  { label: '时间', value: '1' },
-  { label: '位置', value: '2' },
-  { label: '级别', value: '3' },
-  { label: '状态', value: '4' },
-  { label: '操作', value: '5' },
-])
-let tableData = ref<any[]>([
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-  { time: '14:31:22', address: '采掘场A区', grade: '一级', status: '未处理', },
-])
-</script>
+// 定义事件:detail - 查看报警详情
+let $emit = defineEmits(['detail'])
 
+// 点击"详情"按钮,向父组件派发 detail 事件并传递当前行数据
+function handlerDetail(item: any) {
+  $emit('detail', item)
+}
+</script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
@@ -64,6 +73,7 @@ let tableData = ref<any[]>([
   .search-table {}
 }
 
+/* 整体容器:定义背景图片变量及基础布局 */
 .search-table {
   --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-6.png');
   --image-box-bg2: url('@/assets/images/home-container/configurable/hsq/2-24.png');
@@ -74,6 +84,7 @@ let tableData = ref<any[]>([
   box-sizing: border-box;
 }
 
+/* 搜索区域:输入框与筛选按钮水平排列 */
 .search-area {
   height: 42px;
   display: flex;
@@ -82,6 +93,7 @@ let tableData = ref<any[]>([
   margin-bottom: 5px;
 }
 
+/* 筛选按钮外框 */
 .search-btn {
   width: 85px;
   height: 30px;
@@ -91,6 +103,7 @@ let tableData = ref<any[]>([
   cursor: pointer;
 }
 
+/* 筛选按钮内部填充 */
 .btn-item {
   display: flex;
   justify-content: center;
@@ -100,11 +113,12 @@ let tableData = ref<any[]>([
   background-color: rgba(32, 166, 169);
 }
 
-
+/* 内容区域:占据搜索区域以下的所有空间 */
 .content-area {
   height: calc(100% - 47px);
 }
 
+/* 表头行:使用背景图片装饰 */
 .content-title {
   display: flex;
   justify-content: space-between;
@@ -115,19 +129,42 @@ let tableData = ref<any[]>([
   margin-bottom: 6px;
 }
 
+/* 表头列项:统一居中,青色文字,按列分配 flex 比例 */
 .title-item {
   display: flex;
-  flex: 1;
+
   justify-content: center;
   align-items: center;
   color: #01fefc;
+
+  &:nth-child(1) {
+    flex: 2;
+  }
+
+  &:nth-child(2) {
+    flex: 2;
+  }
+
+  &:nth-child(3) {
+    flex: 1;
+  }
+
+  &:nth-child(4) {
+    flex: 1;
+  }
+
+  &:nth-child(5) {
+    flex: 1;
+  }
 }
 
+/* 表格内容区:可纵向滚动 */
 .table-content {
   height: calc(100% - 40px);
   overflow-y: auto;
 }
 
+/* 数据行:奇偶行交替背景色 */
 .content-item {
   display: flex;
   justify-content: space-between;
@@ -143,21 +180,36 @@ let tableData = ref<any[]>([
   }
 }
 
+/* 数据单元格:居中对齐,按列分配 flex 比例(与表头一致) */
 .item-text {
   display: flex;
-  flex: 1;
   justify-content: center;
   align-items: center;
-}
+  text-align: center;
 
-.item-text1 {
-  color: #fc002a;
-}
+  &:nth-child(1) {
+    flex: 2;
+  }
+
+  &:nth-child(2) {
+    flex: 2;
+  }
+
+  &:nth-child(3) {
+    flex: 1;
+  }
+
+  &:nth-child(4) {
+    flex: 1;
+  }
+
+  &:nth-child(5) {
+    flex: 1;
+  }
 
-.item-text2 {
-  color: orange;
 }
 
+/* "详情"操作按钮样式 */
 .text-look {
   padding: 0px 3px;
   margin: 0px 2px;
@@ -166,6 +218,7 @@ let tableData = ref<any[]>([
   cursor: pointer;
 }
 
+/* 搜索输入框:自定义背景图片,去除默认边框 */
 .zxm-input {
   height: 42px;
   color: #fff;
@@ -175,10 +228,13 @@ let tableData = ref<any[]>([
   background-size: 100% 100%;
 }
 
+/* 小尺寸输入框内边距 */
 .zxm-input-sm {
   padding: 0px 20px;
 }
-::-webkit-scrollbar{
-    display: none;
-  }
+
+/* 隐藏滚动条 */
+::-webkit-scrollbar {
+  display: none;
+}
 </style>

+ 119 - 69
src/views/vent/monitorManager/hsqHome/components/WarnRightTop.vue

@@ -1,7 +1,11 @@
 <template>
   <div class="rule-list">
-    <div class="list-title"> 预警规则配置 </div>
+    <div class="list-title"> 设备操作记录 </div>
     <div class="list-content">
+      <div class="content-nav">
+        <div :class="activeIndex == index ? 'nav-item-active' : 'nav-item'" v-for="(item, index) in navList"
+          :key="index" @click="changeNav(item, index)">{{ item.label }}</div>
+      </div>
       <div class="basic-list-content">
         <div class="content-title">
           <div class="title-item" v-for="(item, index) in titleList" :key="index">{{ item.label }}</div>
@@ -14,91 +18,98 @@
             <div class="item-text">
               <a-switch v-model:checked="item.status" size="small" />
             </div>
-            <!-- <div class="item-text">
-              <span class="text-look">编辑</span>
-            </div> -->
           </div>
         </div>
       </div>
-      <!-- <div class="basic-list-content">
-        <basicBorder :widthV="298" colorV="orange" title="【编辑规则】一级温度报警(Rule-001)">
-          <div class="temp-warn">
-            <div class="warn-item">
-              <div class="item-label">规则名称 : </div>
-              <a-input class="item-input" v-model:value="rulesData.ruleName" placeholder="请输入" size="small" />
-            </div>
-            <div class="warn-item">
-              <div class="item-label">报警级别 : </div>
-              <a-input class="item-input" v-model:value="rulesData.grade" placeholder="请输入" size="small" />
-            </div>
-            <div class="warn-item">
-              <div class="item-label">温度阈值 : </div>
-              <a-input class="item-input" v-model:value="rulesData.temp" placeholder="请输入" size="small" />
-            </div>
-            <div class="warn-item">
-              <div class="item-label">持续时间 : </div>
-              <a-input class="item-input" v-model:value="rulesData.time" placeholder="请输入" size="small" />
-            </div>
-            <div class="warn-item">
-              <div class="item-label">联动动作 : </div>
-              <a-input class="item-input" v-model:value="rulesData.lddz" placeholder="请输入" size="small" />
-            </div>
-            <div class="warn-item">
-              <div class="item-label">通知对象 : </div>
-              <a-input class="item-input" v-model:value="rulesData.person" placeholder="请输入" size="small" />
-            </div>
-            <div class="btn-box">
-              <div class="baisc-btn">取消</div>
-              <div class="baisc-btn">
-                <div class="btn-item">保存</div>
-              </div>
-            </div>
-          </div>
-        </basicBorder>
-      </div> -->
+
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { reactive, ref } from 'vue';
-// import basicBorder from '../BasicBorder.vue';
-// import { rules } from '/@/utils/helper/validator.js';
+import { onMounted, onUnmounted, reactive, ref } from 'vue';
+import {deviceSetLog} from '../hsqHome.api'
 
+//当前激活nav选项
+let activeIndex = ref(0)
+let devicetype=ref('duaSpeCamera')
+let navList = ref<any[]>([
+  { label: '光谱摄像机', value: 'duaSpeCamera' },
+  { label: '火灾探测器', value: 'imgFireDet' }
+])
 let titleList = ref<any[]>([
-  { label: '规则ID', value: '1' },
-  { label: '规则名称', value: '2' },
-  { label: '触发条件', value: '3' },
-  { label: '启用', value: '4' },
-  // { label: '操作', value: '5' },
+  { label: '用户', value: '1' },
+  { label: '操作设备', value: '2' },
+  { label: '操作记录', value: '3' },
+  { label: '登录IP', value: '4' },
+  { label: '时间', value: '5' },
 ]);
 let tableData = ref<any[]>([
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: false },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
-  { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: false },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
+  // { ruleId: 'Rule-001', ruleName: '一级温度报警', fctj: '温度>=60℃', status: true },
 ]);
-let rulesData = reactive({
-  ruleName: '',
-  grade: '',
-  temp: '',
-  time: '',
-  lddz: '',
-  person: '',
-});
+let timer: null | NodeJS.Timeout = null;
+/**
+ * 定时获取监测数据
+ * 
+ * @param flag 是否立即执行(默认false,延迟30秒)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getDeviceSetLogList(devicetype.value);
+    if (timer) {
+      timer = null;
+    }
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
+//nav选项切换
+function changeNav(item, index) {
+  activeIndex.value = index
+  devicetype.value=item.value
+  switch (index) {
+    case 0:
+      // 处理光谱摄像机逻辑
+      getDeviceSetLogList(devicetype.value)
+      break;
+    case 1:
+      // 处理火灾探测器逻辑
+      getDeviceSetLogList(devicetype.value)
+      break;
+  }
+}
+
+/**
+ * 获取设备操作记录列表
+ * @param param - 设备类型参数
+ * @returns 异步请求设备操作记录,分页查询第1页共100条数据
+ */
+async function getDeviceSetLogList(param) {
+  const res = await deviceSetLog({pageNo:1,pageSize:100,devicetype:param})
+  console.log(res,'设备操作记录')
+}
+
+onMounted(()=>{
+  getMonitor(true)
+})
+onUnmounted(()=>{
+  timer = null
+  clearTimeout(timer);
+})
 </script>
 
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 
 @{theme-deepblue} {
-  .rule-list {
-  }
+  .rule-list {}
 }
 
 .rule-list {
@@ -130,10 +141,38 @@ let rulesData = reactive({
     height: calc(100% - 40px);
   }
 
+  .content-nav {
+    display: flex;
+    align-items: center;
+    height: 32px;
+    width: 100%;
+    margin: 0px 5px 10px 5px;
+    background-color: #15517b;
+  }
+
+  .nav-item {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 120px;
+    height: 100%;
+    cursor: pointer;
+  }
+
+  .nav-item-active {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 120px;
+    height: 100%;
+    cursor: pointer;
+    background-color: #1d73a8;
+    border-bottom: 2px solid #36c7ff;
+  }
+
   .basic-list-content {
     width: 100%;
-    height:100%;
-    // height: calc(50% - 5px);
+    height: calc(100% - 42px);
   }
 
   .content-title {
@@ -152,9 +191,11 @@ let rulesData = reactive({
     justify-content: center;
     align-items: center;
     color: #01fefc;
+
     &:nth-child(2) {
       flex: 1.5;
     }
+
     &:nth-child(3) {
       flex: 1.5;
     }
@@ -186,9 +227,11 @@ let rulesData = reactive({
     flex: 1;
     justify-content: center;
     align-items: center;
+
     &:nth-child(2) {
       flex: 1.5;
     }
+
     &:nth-child(3) {
       flex: 1.5;
     }
@@ -197,6 +240,7 @@ let rulesData = reactive({
   .item-text1 {
     color: orange;
   }
+
   .text-look {
     padding: 0px 3px;
     margin: 0px 2px;
@@ -204,10 +248,12 @@ let rulesData = reactive({
     background-color: #2484bc;
     cursor: pointer;
   }
+
   .temp-warn {
     position: relative;
     height: 100%;
   }
+
   .warn-item {
     display: flex;
     align-items: center;
@@ -221,16 +267,19 @@ let rulesData = reactive({
       margin-bottom: 0;
     }
   }
+
   .item-label {
     width: 100px;
     text-align: right;
     margin-right: 5px;
   }
+
   .zxm-input {
     background-color: transparent;
     border: 1px solid #2792c2;
     color: #fff;
   }
+
   .btn-box {
     width: 140px;
     display: flex;
@@ -261,6 +310,7 @@ let rulesData = reactive({
     height: 100%;
     background-color: rgba(32, 166, 169);
   }
+
   ::-webkit-scrollbar {
     display: none;
   }

+ 21 - 8
src/views/vent/monitorManager/hsqHome/components/basicDevice.vue

@@ -4,24 +4,24 @@
       <div class="icon-item"></div>
     </div>
     <div class="basic-title">
-      <div class="title-text">CAM-001</div>
+      <div class="title-text">{{deviceData.strserno || '-' }}</div>
       <div class="title-btn">
-        <div class="btn">详情</div>
-        <div class="btn">配置</div>
-        <div class="btn">重启</div>
+        <div class="btn" @click="handlerClick('detail')">详情</div>
+        <div class="btn" @click="handlerClick('config')">配置</div>
+        <div class="btn" @click="handlerClick('restart')">重启</div>
       </div>
     </div>
     <div class="basic-content">
-      <div class="content-item">双光谱摄像机#1</div>
+      <div class="content-item">{{deviceData.strname || '-' }}</div>
       <div class="content-item">
         <span class="icon-pie">
           <span class="pie-item"></span>
         </span>
-        <span>在线</span>
+        <span>{{deviceData.syswarnLevel ? '报警' : '正常' }}</span>
       </div>
-      <div class="content-item">采掘场A区</div>
+      <div class="content-item">{{deviceData.strinstallpos || '-' }}</div>
       <div class="content-item">
-        <span>32</span>
+        <span>{{deviceData.readData.temperature || '-'}}</span>
         <span>℃</span>
       </div>
     </div>
@@ -31,6 +31,19 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 
+let props = defineProps({
+  deviceData: {
+    type: Object,
+    required: true
+  }
+})
+
+let $emit=defineEmits(['detail'])
+
+function handlerClick(type) {
+  $emit('detail', {type:type,value:props.deviceData})
+}
+
 </script>
 
 <style lang="less" scoped>

+ 8 - 4
src/views/vent/monitorManager/hsqHome/components/navMenu.vue

@@ -8,21 +8,25 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 
-//当前激活menu选项索引
-let activeIndex = ref(0)
+let props=defineProps({
+  activeIndex: {
+    type: Number,
+    default: 0
+  }
+})
+
 let menuList = ref<any[]>([
   { label: '一张图', value: 'yzt' },
   { label: '监测预警', value: 'jcyj' },
   { label: '设备管理', value: 'sbgl' },
   { label: '联动配置', value: 'ldpz' },
   { label: '系统管理', value: 'xtgl' },
-  { label: '大屏展示', value: 'dpzs' },
+  // { label: '大屏展示', value: 'dpzs' },
 ])
 let $emit=defineEmits(['changeMenu'])
 
 //menu选项切换
 function handlerClick(item, index) {
-  activeIndex.value = index
   $emit('changeMenu',item.value)
 }
 </script>

+ 29 - 21
src/views/vent/monitorManager/hsqHome/deviceManger.vue

@@ -1,45 +1,53 @@
 <template>
   <div class="device-manger">
     <div class="basic-box">
-      <DeviceLeft></DeviceLeft>
+      <DeviceLeft @details="handlerDetail"></DeviceLeft>
     </div>
     <div class="basic-box">
-      <DeviceCenter></DeviceCenter>
+      <DeviceCenter :detailData="detailData"></DeviceCenter>
     </div>
     <div class="basic-box">
-      <DeviceRight></DeviceRight>
+      <DeviceRight :detailData="configData"></DeviceRight>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, computed, nextTick, onBeforeMount, watch } from 'vue';
+import { onMounted, onUnmounted, ref, computed, nextTick, onBeforeMount, watch, reactive } from 'vue';
 import DeviceLeft from './components/DeviceLeft.vue'
 import DeviceCenter from './components/DeviceCenter.vue'
 import DeviceRight from './components/DeviceRight.vue'
 
+// 设备详情
+let strserno = ref<any>({})
+let detailData = ref<any>({})
+// 配置详情
+let configData = ref<any>({})
 
-// https获取监测数据
-let timer: null | NodeJS.Timeout = null;
-// function getMonitor() {
-//   timer = setTimeout(async () => {
-//     getSystemApi({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-//     if (timer) {
-//       timer = null;
-//     }
-//     getMonitor();
-//   }, 10000);
-// }
 
+
+function handlerDetail(item: any) {
+  console.log(item, '详情--handlerDetail')
+  switch (item.type) {
+    case 'detail':
+      detailData.value = item.value
+      break
+    case 'config':
+      configData.value = item.value
+      break
+    case 'restart':
+      configData.value = item.value
+      break
+    default:
+    // 其他 cases 待添加
+  }
+}
+/** 初始化页面数据(当前已注释,包含获取配置、系统接口数据及监控信息的逻辑) */
 onMounted(() => {
-  // fetchConfigs('gas_injection').then(() => {
-  //   configs.value = testConfigDevice;
-  //   getSystemApi1({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-  // });
-  // getMonitor();
+
 });
 
 onUnmounted(() => {
-  // clearInterval(interval);
+
 });
 </script>
 

+ 123 - 75
src/views/vent/monitorManager/hsqHome/fireMonitor.vue

@@ -3,17 +3,17 @@
     <div class="basic-fire-box">
       <div class="box-top">
         <CommonBd title="设备状态总览">
-          <CustomGalleryNew :option="option" :galleryData="galleryData"></CustomGalleryNew>
+          <CustomGalleryNew :option="option" :galleryData="galleryData" :tableData="tableData"></CustomGalleryNew>
         </CommonBd>
       </div>
       <div class="box-cen">
-        <CommonBd title="实时温度曲线">
-          <CustomChart :chart-config="chartConfig" :chart-data="chartData"></CustomChart>
+        <CommonBd title="光谱摄像机">
+          <CommonTable :columns="deviceColumns" :table-data="tableDataGp"></CommonTable>
         </CommonBd>
       </div>
       <div class="box-bot">
-        <CommonBd title="最近处理记录">
-          <CommonTable :columns="recordColumns" :table-data="tableData"></CommonTable>
+        <CommonBd title="火灾探测器">
+          <CommonTable :columns="deviceColumns" :table-data="tableDataHz"></CommonTable>
         </CommonBd>
       </div>
     </div>
@@ -35,8 +35,11 @@
         </CommonBd>
       </div>
       <div class="box-cen">
-        <CommonBd title="区域温度统计">
+        <!-- <CommonBd title="区域温度统计">
           <CustomChart :chart-config="chartTempConfig" :chart-data="chartTempData"></CustomChart>
+        </CommonBd> -->
+        <CommonBd title="实时温度曲线">
+          <CustomChart :chart-config="chartConfig" :chart-data="chartData"></CustomChart>
         </CommonBd>
       </div>
       <div class="box-bot">
@@ -48,7 +51,7 @@
   </div>
 </template>
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, computed, nextTick, onBeforeMount, watch, reactive } from 'vue';
+import { onMounted, onUnmounted, ref, computed, nextTick, onBeforeMount, watch, reactive, inject, Ref } from 'vue';
 import CommonBd from './components/CommonBd.vue';
 import CustomGalleryNew from './components/CustomGalleryNew.vue';
 import CustomChart from './components/CustomChart.vue';
@@ -57,92 +60,137 @@ import AlarmMonitor from './components/AlarmMonitor.vue';
 import EnvironmentMonitor from './components/EnvironmentMonitor.vue';
 import QuickAction from './components/QuickAction.vue';
 import dwgViewer from '../../cad/dwgViewer.vue';
-import { option, chartConfig, recordColumns, quickOption, btnOption, alarmOption, chartTempConfig, envOption } from './hsqHome.data';
-
-let interval: number | undefined;
-
-let galleryData = reactive({
-  zxNum: 5,
-  lxNum: 5,
-  gzNum: 5,
-  bjNum: 5,
-})
-
-const chartData = [
-  { pos: '1', value: '12' },
-  { pos: '2', value: '44' },
-  { pos: '3', value: '44' },
-  { pos: '4', value: '20' },
-]
-
-
-let tableData = ref<any[]>([
-  { time: '14:25:10', deviceName: 'CAM-001', status: '在线' },
-  { time: '14:25:10', deviceName: 'CAM-002', status: '在线' },
-  { time: '14:25:10', deviceName: 'CAM-003', status: '报警' },
-  { time: '14:25:10', deviceName: 'CAM-001', status: '在线' },
-  { time: '14:25:10', deviceName: 'CAM-002', status: '在线' },
-  { time: '14:25:10', deviceName: 'CAM-003', status: '报警' },
-])
-
-
+import { option, chartConfig, deviceColumns, quickOption, btnOption, alarmOption, chartTempConfig, } from './hsqHome.data';
+import { managesysList, monitorSystem, deviceList, monitorDevice, getManagesysDeviceNum } from './hsqHome.api';
+
+/** 当前系统ID */
+let systemId = ref('')
+/** 设备状态总览卡片数据 */
+let galleryData = ref({})
+/** 设备列表表格数据(包含双光谱相机和火灾探测器) */
+let tableData = ref<any[]>([])
+/** 光谱摄像机表格数据 */
+let tableDataGp = ref<any[]>([])
+/** 火灾探测器表格数据 */
+let tableDataHz = ref<any[]>([])
+/** 实时温度曲线图表数据 */
+let chartData = ref<any[]>([])
+/** 报警监控卡片数据 */
 let alarmData = ref<any>({
-  dayWarn: 12,
-  warnOk: 2,
-  warnUnOk: 10
+  dayWarn: 0,
+  warnOk: 0,
+  warnUnOk: 0
 })
 
 
 let chartTempData = [
-  { x: '1', y: '12' },
-  { x: '2', y: '44' },
-  { x: '3', y: '44' },
-  { x: '4', y: '20' },
+  { x: '1', y: 12 },
+  { x: '2', y: 44 },
+  { x: '3', y: 44 },
+  { x: '4', y: 20 },
 ]
 
+/** 环境监测设备选项配置 */
+let envOption = ref<any[]>([])
+/** 环境监测数据 */
+let envData = reactive({})
 
-let envData = reactive({
-  tempHj: '-8.5',
-  fsfx: '3.2',
-  temp: '42',
-  coval: '12',
-  co2val: '380',
-  o2val: '20.9',
-  ch4val: '0.02',
-  fcnd: '0.08',
-  ywzs: '0.15',
-  tempDb: '28',
-})
-
+/** 快捷操作面板数据 */
 let quickData = reactive({
   time: '128天06:32:18',
   sjsx: '实时',
   wlyc: '12s',
 })
-// https获取监测数据
+/** 定时获取监测数据定时器 */
 let timer: null | NodeJS.Timeout = null;
-// function getMonitor() {
-//   timer = setTimeout(async () => {
-//     getSystemApi({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-//     if (timer) {
-//       timer = null;
-//     }
-//     getMonitor();
-//   }, 10000);
-// }
-
-onMounted(() => {
-  // fetchConfigs('gas_injection').then(() => {
-  //   configs.value = testConfigFire;
-  //   getSystemApi1({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-  // });
-  // getMonitor();
-});
 
+/**
+ * 定时获取监测数据
+ * 通过定时器循环调用数据获取方法,更新表格数据
+ *
+ * @param flag - 是否立即执行(首次调用时传true跳过延时)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    await getManagesysDeviceNumTj();
+    await getTableData();
+    await getDeviceMonitor();
+    if (timer) {
+      timer = null;
+    }
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
+
+/**
+ * 获取管理系统设备数量统计
+ * 调用 getManagesysDeviceNum 接口,根据当前系统ID获取设备数量统计信息
+ */
+async function getManagesysDeviceNumTj() {
+  const res = await getManagesysDeviceNum({ sysId: systemId.value });
+  galleryData.value = res || {};
+}
+/**
+ * 获取设备列表数据
+ *
+ * 调用 deviceList 接口获取双光谱相机设备列表,
+ * 并将返回数据赋值给 tableData
+ */
+async function getTableData() {
+  const res = await monitorSystem({ type: 'all', systemID: systemId.value, devicetype: '' });
+  tableData.value = [...res.deviceInfo.duaSpeCamera.datalist, ...res.deviceInfo.imgFireDet.datalist]
+  tableDataGp.value = res.deviceInfo.duaSpeCamera.datalist || [];
+  tableDataHz.value = res.deviceInfo.imgFireDet.datalist || [];
+  chartData.value = tableData.value.map(v => ({
+    pos: v.strname,
+    value: v.readData.temperature
+  }))
+  console.log(chartData.value, 'chartData.value')
+}
+
+/**
+ * 获取环境监测设备数据
+ * 调用 monitorDevice 接口,查询指定系统的户外火灾相关监测设备状态
+ */
+async function getDeviceMonitor() {
+  const res = await monitorDevice({ devicetype: 'sys', systemID: systemId.value, types: 'sys_openair_fire' });
+  envOption.value = res.msgTxt.filter(v => v.type.indexOf('modelsensor') != -1).map(el => {
+    return {
+      label: el.datalist[0].typeName,
+      value: Object.keys(el.datalist[0].readData).filter(v => v.endsWith('val') || v.endsWith('ture'))[0],
+      unit: ''
+    }
+  })
+  envData = Object.assign({}, ...res.msgTxt.filter(v => v.type.indexOf('modelsensor') != -1).map(el => el.datalist[0].readData))
+}
+/**
+ * 获取管理系统列表
+ * 调用 managesysList 接口获取户外火灾相关系统列表,并取第一个系统ID作为当前系统
+ */
+async function getManagesysList() {
+  const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
+  if (res && res.records) {
+    systemId.value = res.records[0].id
+  }
+}
+
+/**
+ * 组件挂载完成
+ * 先获取管理系统列表初始化系统ID,然后立即启动定时获取监测数据
+ */
+onMounted(async () => {
+  await getManagesysList();
+  getMonitor(true);
+});
 
 
+/**
+ * 组件卸载前
+ * 清理定时器,停止轮询获取监测数据
+ */
 onUnmounted(() => {
-  // clearInterval(interval);
+  timer = null
+  clearTimeout(timer);
 });
 </script>
 

+ 57 - 3
src/views/vent/monitorManager/hsqHome/hsqHome.api.ts

@@ -1,9 +1,18 @@
 import { defHttp } from '/@/utils/http/axios';
 
 enum Api {
-userList = '/sys/user/list',//用户列表接口
-LongList = '/sys/log/list',//日志列表接口
-getUserLoginStats= '/sys/log/getUserLoginStats'//今日登录统计
+  userList = '/sys/user/list',//用户列表接口
+  LongList = '/sys/log/list',//日志列表接口
+  getUserLoginStats = '/sys/log/getUserLoginStats',//今日登录统计
+  deviceList = '/safety/ventanalyDeviceInfo/list',//设备列表
+  autoLogList = '/safety/managesysAutoLog/list',//报警监控接口
+  monitorDevice = '/ventanaly-device/monitor/device', //设备监控
+  deviceSetLog = '/safety/ventanalyDevicesetLog/list', //设备操作记录
+  getManagesysDeviceNum = '/ventanaly-device/safety/ventanalyManageSystem/getManagesysDeviceNum', //设备数量统计
+  managesysList = '/safety/ventanalyManageSystem/list', //获取场景信息
+  monitorSystem = '/monitor/system', //场景设备列表
+  queryRoleAndUserNum = '/sys/role/queryRoleAndUserNum', //查询角色和用户数量
+
 }
 /**
 * 日志列表接口
@@ -16,8 +25,53 @@ export const LongList = (params) => defHttp.get({ url: Api.LongList, params });
 */
 export const userList = (params) => defHttp.get({ url: Api.userList, params });
 
+//设备列表
+export const deviceList = (params) => defHttp.get({ url: Api.deviceList, params });
+
 /**
 * 今日登录统计
 * @param params
 */
 export const getUserLoginStats = (params) => defHttp.post({ url: Api.getUserLoginStats, params });
+
+/**
+* 报警监控接口
+* @param params
+*/
+export const autoLogList = (params) => defHttp.get({ url: Api.autoLogList, params });
+
+/**
+* 设备监控接口
+* @param params
+*/
+export const monitorDevice = (params) => defHttp.post({ url: Api.monitorDevice, params });
+
+/**
+* 设备操作记录接口
+* @param params
+*/
+export const deviceSetLog = (params) => defHttp.get({ url: Api.deviceSetLog, params });
+
+/**
+* 设备数量统计接口
+* @param params
+*/
+export const getManagesysDeviceNum = (params) => defHttp.get({ url: Api.getManagesysDeviceNum, params });
+
+/**
+* 获取场景信息接口
+* @param params
+*/
+export const managesysList = (params) => defHttp.get({ url: Api.managesysList, params });
+
+/**
+* 场景设备列表接口
+* @param params
+*/
+export const monitorSystem = (params) => defHttp.post({ url: Api.monitorSystem, params });
+
+/**
+* 查询角色和用户数量接口
+* @param params
+*/
+export const queryRoleAndUserNum = (params) => defHttp.get({ url: Api.queryRoleAndUserNum, params });

+ 57 - 32
src/views/vent/monitorManager/hsqHome/hsqHome.data.ts

@@ -2,19 +2,19 @@
 export let option = [
   {
     label: '在线设备',
-    value: 'zxNum',
+    value: 'onlineNum',
   },
   {
     label: '离线设备',
-    value: 'lxNum',
+    value: 'offlineNum',
   },
   {
     label: '故障设备',
-    value: 'gzNum',
+    value: 'faultNum',
   },
   {
     label: '报警设备',
-    value: 'bjNum',
+    value: 'alarmNum',
   },
 ]
 
@@ -23,9 +23,9 @@ export const chartConfig: any = {
   type: 'line_area',
   grid: {
     top: 40,
-    left: 50,
+    left: 30,
     bottom: 14,
-    right: 50,
+    right: 30,
   },
   legend: { show: false },
   xAxis: [{ show: true }],
@@ -33,26 +33,27 @@ export const chartConfig: any = {
   series: [
     {
       label: '温度',
-      readFrom: 'chartData',
+      readFrom: '',
       xprop: 'pos',
       yprop: 'value',
+      color: ['rgba(52, 193, 248,.8)', 'rgba(52, 193, 248,.2)'],
     },
   ],
 };
 
-//最近处理记录
-export let recordColumns: any[] = [
+//设备状态表格
+export let deviceColumns: any[] = [
   {
-    name: '时间',
-    prop: 'time',
+    name: '设备名称',
+    prop: 'strname',
   },
   {
-    name: '设备',
-    prop: 'deviceName',
+    name: '在线状态',
+    prop: 'netStatus',
   },
   {
-    name: '状态',
-    prop: 'status',
+    name: '报警状态',
+    prop: 'syswarnLevel',
   },
 ]
 
@@ -97,22 +98,22 @@ export let chartTempConfig: any = {
   legend: { show: false },
   xAxis: [{ show: true }],
   yAxis: [{ show: true, name: '', position: 'left' }],
-  series: [{ readFrom: 'chartTempData', xprop: 'x', yprop: 'y', label: '温度' }],
+  series: [{ readFrom: '', xprop: 'x', yprop: 'y', label: '温度' }],
 }
 
 
-export let envOption = [
-  { label: '环境温度', value: 'tempHj', unit: '℃' },
-  { label: '风速风向', value: 'fsfx', unit: 'm/s' },
-  { label: '温度', value: 'temp', unit: '%' },
-  { label: 'CO浓度', value: 'coval', unit: 'ppm' },
-  { label: 'CO2浓度', value: 'co2val', unit: 'ppm' },
-  { label: 'O2浓度', value: 'o2val', unit: '%' },
-  { label: 'Ch4浓度', value: 'ch4val', unit: '%' },
-  { label: '粉尘浓度', value: 'fcnd', unit: 'mg/m³' },
-  { label: '烟雾指数', value: 'ywzs', unit: '' },
-  { label: '地表温度', value: 'tempDb', unit: '℃' },
-]
+//export let envOption = [
+//  { label: '环境温度', value: 'tempHj', unit: '℃' },
+//  { label: '风速风向', value: 'fsfx', unit: 'm/s' },
+//  { label: '温度', value: 'temp', unit: '%' },
+//  { label: 'CO浓度', value: 'coval', unit: 'ppm' },
+//  { label: 'CO2浓度', value: 'co2val', unit: 'ppm' },
+//  { label: 'O2浓度', value: 'o2val', unit: '%' },
+//  { label: 'Ch4浓度', value: 'ch4val', unit: '%' },
+//  { label: '粉尘浓度', value: 'fcnd', unit: 'mg/m³' },
+//  { label: '烟雾指数', value: 'ywzs', unit: '' },
+//  { label: '地表温度', value: 'tempDb', unit: '℃' },
+//]
 
 //用户列表
 export let optionUser = [
@@ -140,17 +141,41 @@ export let titleOption = [
 ]
 
 //今日登录统计
-export let dayLoginConfig:any = {
+export let dayLoginConfig: any = {
   type: 'bar_cylinder_wide',
   readFrom: '',
   grid: {
     top: 55,
     left: 20,
-    bottom: 35,
-    right: 50,
+    bottom: 25,
+    right: 20,
   },
   legend: { show: false },
   xAxis: [{ show: true }],
   yAxis: [{ show: true, name: '', position: 'left' }],
-  series: [{ ww: 'areaTemptureData', xprop: 'x', yprop: 'y', label: '' }],
+  series: [{ readFrom: '', xprop: 'x', yprop: 'y', label: '次数' }],
 }
+
+//报警列表表头
+export let warnTitle = [
+  { label: '时间', value: 'starttime' },
+  { label: '位置', value: 'alarmName' },
+  { label: '级别', value: 'alarmLevel' },
+  { label: '状态', value: 'isok' },
+  { label: '操作', value: 'operation' },
+]
+
+export let alarmColumns:any[] =[
+  {
+    name: '时间',
+    prop: 'starttime',
+  },
+  {
+    name: '位置',
+    prop: 'alarmName',
+  },
+  {
+    name: '报警级别',
+    prop: 'alarmLevelC',
+  },
+]

+ 40 - 28
src/views/vent/monitorManager/hsqHome/index.vue

@@ -4,7 +4,7 @@
       <div class="main-title">{{ mainTitle }}</div>
     </div>
     <div class="nav">
-      <navMenu @changeMenu="changeMenu"></navMenu>
+      <navMenu :activeIndex="activeIndex" @changeMenu="changeMenu"></navMenu>
     </div>
     <div class="main-container">
       <component :is="activeComponente"></component>
@@ -13,44 +13,56 @@
 </template>
 
 <script setup lang="ts">
-import {ref} from 'vue';
+import { ref, type Component, type Ref, watch, onMounted, provide } from 'vue';
 import navMenu from './components/navMenu.vue'
 import fireMonitor from './fireMonitor.vue'
 import deviceManger from './deviceManger.vue'
 import warnMonitor from './warnMonitor.vue'
 import systemManger from './systemManger.vue'
 import largeScreen from './largeScreen.vue'
+import linkConfiguration from './linkConfiguration.vue'
 
-let mainTitle=ref('红沙泉露天煤矿系统');
-//当前激活界面
-const activeComponente = ref<any>(fireMonitor);
 
-  //menu选项切换
-function changeMenu(param) {
-  switch (param) {
-    case 'yzt':
-      activeComponente.value = fireMonitor;
-      break;
-    case 'jcyj':
-      activeComponente.value = warnMonitor;
-      break;
-    case 'sbgl':
-      activeComponente.value = deviceManger;
-      break;
-    case 'ldpz':
-      activeComponente.value = linkConfiguration;
-      break;
-    case 'xtgl':
-      activeComponente.value = systemManger;
-      break;
-    case 'dpzs':
-        activeComponente.value = largeScreen
-      break;
-  }
+/** 菜单键值映射接口,定义各导航菜单项对应的 key 标识 */
+interface IMenuKeyMap {
+  yzt: 'yzt'
+  jcyj: 'jcyj'
+  sbgl: 'sbgl'
+  ldpz: 'ldpz'
+  xtgl: 'xtgl'
+  dpzs: 'dpzs'
 }
+/** 菜单 key 类型,从 IMenuKeyMap 中提取所有键的联合类型 */
+type MenuKey = IMenuKeyMap[keyof IMenuKeyMap]
+/** 组件引用类型,继承 Vue 的 Ref<Component>,用于动态组件切换 */
+interface IComponentReference extends Ref<Component> { }
 
-</script>
+// 当前激活的菜单项
+const activeIndex = ref<number>(0);
+// 主标题
+const mainTitle = ref<string>('红沙泉露天煤矿火灾监测预警系统');
+//当前激活界面
+const activeComponente = ref<Component>(fireMonitor) as IComponentReference;
+/** 菜单 key 与组件的映射表,用于根据导航菜单项动态切换显示组件 */
+const menuComponentMap: Record<MenuKey, Component> = {
+  yzt: fireMonitor,
+  jcyj: warnMonitor,
+  sbgl: deviceManger,
+  ldpz: linkConfiguration,
+  xtgl: systemManger,
+  dpzs: largeScreen,
+}
 
+/**
+ * 导航菜单切换事件
+ * 根据菜单 key 从映射表中查找对应组件,更新当前激活的动态组件
+ * @param param - 菜单标识 key,对应 menuComponentMap 中的键
+ */
+function changeMenu(param: MenuKey) {
+  activeComponente.value = menuComponentMap[param];
+  activeIndex.value = Object.keys(menuComponentMap).indexOf(param);
+}
+</script>
 <style lang="less" scoped>
 @import '/@/design/theme.less';
 

+ 84 - 0
src/views/vent/monitorManager/hsqHome/linkConfiguration.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="link-configuration">
+    <div class="link-left">
+      <div class="link-left-top">
+        <LinkRuleTable />
+      </div>
+      <div class="link-left-bottom">
+        <LinkRuleStats />
+      </div>
+    </div>
+    <div class="link-center">
+      <LinkRuleEditor />
+    </div>
+    <div class="link-right">
+      <LinkLogPanel />
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import LinkRuleTable from './components/LinkRuleTable.vue';
+import LinkRuleStats from './components/LinkRuleStats.vue';
+import LinkRuleEditor from './components/LinkRuleEditor.vue';
+import LinkLogPanel from './components/LinkLogPanel.vue';
+</script>
+
+<style lang="less" scoped>
+@import '/@/design/theme.less';
+
+@{theme-deepblue} {}
+
+.link-configuration {
+  --image-box-bg: url('@/assets/images/home-container/configurable/hsq/2-4.png');
+  --image-box-bg1: url('@/assets/images/home-container/configurable/hsq/2-10.png');
+  display: flex;
+  justify-content: space-between;
+  width: 100%;
+  height: calc(100% - 10px);
+  color: @white;
+  position: relative;
+  margin-bottom: 10px;
+  background-color: #09172c;
+
+  .link-left {
+    width: 430px;
+    height: 100%;
+    margin-right: 10px;
+    display: flex;
+    flex-direction: column;
+    background: var(--image-box-bg) no-repeat;
+    background-size: 100% 100%;
+  }
+
+  .link-left-top {
+    flex: 1;
+    min-height: 0;
+    overflow: hidden;
+  }
+
+  .link-left-bottom {
+    height: 190px;
+    flex-shrink: 0;
+  }
+
+  .link-center {
+    position: absolute;
+    top: 63px;
+    left: 50%;
+    transform: translate(-50%, 0);
+    width: calc(100% - 880px);
+    height: calc(100% - 63px);
+    background: var(--image-box-bg1) no-repeat;
+    background-size: 100% 100%;
+  }
+
+  .link-right {
+    width: 430px;
+    height: 100%;
+    margin-left: 10px;
+    background: var(--image-box-bg) no-repeat;
+    background-size: 100% 100%;
+  }
+}
+</style>

+ 71 - 48
src/views/vent/monitorManager/hsqHome/systemManger.vue

@@ -5,20 +5,24 @@
         <SystemLeftTop :option="optionUser" :data="listData"></SystemLeftTop>
       </div>
       <div class="system-box-t">
-        <SystemRightTop></SystemRightTop>
+        <SystemRightTop :userInfo="userInfo"></SystemRightTop>
       </div>
     </div>
     <div class="basic-bot-box">
       <div class="system-box-b">
-        <SystemLeftBottom :btnOption="logbtnOption" :titleOption="titleOption" :data="logList" @changeTab="changeTab">
+        <!-- <SystemLeftBottom :btnOption="logbtnOption" :titleOption="titleOption" :data="logList" @changeTab="changeTab">
+        </SystemLeftBottom> -->
+        <SystemLeftBottom title="登录日志"  :titleOption="titleOption" :data="logList" >
         </SystemLeftBottom>
       </div>
       <div class="system-box-b">
-        <SystemCenBottom :data="dayList"></SystemCenBottom>
+        <!-- <SystemCenBottom :data="dayList"></SystemCenBottom> -->
+          <SystemLeftBottom title="浏览日志" :titleOption="titleOption" :data="lookLogList" >
+        </SystemLeftBottom>
       </div>
-      <div class="system-box-b">
+      <!-- <div class="system-box-b">
         <SystemRightBot></SystemRightBot>
-      </div>
+      </div> -->
     </div>
   </div>
 </template>
@@ -29,18 +33,19 @@ import SystemRightTop from './components/SystemRightTop.vue';
 import SystemLeftBottom from './components/SystemLeftBottom.vue';
 import SystemCenBottom from './components/SystemCenBottom.vue';
 import SystemRightBot from './components/SystemRightBot.vue';
-import { userList, LongList, getUserLoginStats } from './hsqHome.api.js'
+import { userList, LongList, getUserLoginStats ,queryRoleAndUserNum} from './hsqHome.api.js'
 import { optionUser, logbtnOption, titleOption } from './hsqHome.data.js'
 import dayjs from 'dayjs';
 
 let listData = ref([])
-let logType = ref(1)
+// let logType = ref(1)
 let logList = ref([])
-let dayList = ref([])
-let searchData = reactive({
-  dayEnd: dayjs().format('YYYY-MM-DD') ,
-  dayStart:dayjs().startOf('date').subtract(7, 'day').format('YYYY-MM-DD'),
-})
+let lookLogList = ref([])
+// let searchData = reactive({
+//   dayEnd: dayjs().format('YYYY-MM-DD') ,
+//   dayStart:dayjs().startOf('date').subtract(7, 'day').format('YYYY-MM-DD'),
+// })
+let userInfo=ref<any[]>([])
 
 //获取用户列表
 async function getUserList() {
@@ -50,37 +55,54 @@ async function getUserList() {
 
 //获取日志列表
 async function getLogList() {
-  let res = await LongList({ logType: logType.value, pageNo: 1, pageSize: 1000 })
+  let res = await LongList({ logType: 1, pageNo: 1, pageSize: 1000 })
   logList.value = res.records || []
 }
-//日志选项切换
-function changeTab(param) {
-  switch (param) {
-    case 'login':
-      logType.value = 1
-      getLogList()
-      break;
-    case 'operate':
-      logType.value = 2
-      getLogList()
-      break;
-    case 'look':
-      logType.value = 3
-      getLogList()
-      break;
-  }
+async function getLookLogList() {
+  let res = await LongList({ logType: 3, pageNo: 1, pageSize: 1000 })
+  lookLogList.value = res.records || []
 }
+// //日志选项切换
+// function changeTab(param) {
+//   switch (param) {
+//     case 'login':
+//       logType.value = 1
+//       getLogList()
+//       break;
+//     case 'operate':
+//       logType.value = 2
+//       getLogList()
+//       break;
+//     case 'look':
+//       logType.value = 3
+//       getLogList()
+//       break;
+//   }
+// }
 //今日统计列表
-async function getDayTjList() {
-  let res = await getUserLoginStats({ pageNoL: 1, pageSize: 100, ...searchData })
-  console.log(res,'今日统计---')
-  dayList.value = res.records || []
+// async function getDayTjList() {
+//   let res = await getUserLoginStats({ pageNoL: 1, pageSize: 100, ...searchData })
+//   console.log(res,'今日统计---')
+//   dayList.value = res.records || []
+// }
+//查询角色和用户数量
+async function queryRoleAndUserNumInfo() {
+  let res = await queryRoleAndUserNum({})
+  console.log(res,'角色和用户数量---')
+  if(res && res.length){
+    userInfo.value = res.map(item => ({
+      label: item.roleName,
+      value: item.userNum,
+    }))
+  }
 }
 
 onMounted(() => {
   getUserList()
   getLogList()
-  getDayTjList()
+  getLookLogList()
+  // getDayTjList()
+  queryRoleAndUserNumInfo()
 });
 
 </script>
@@ -131,10 +153,10 @@ onMounted(() => {
 
     &:nth-child(2) {
       position: absolute;
-      top: 0px;
+      top: 63px;
       right: 0;
       width: 430px;
-      height: 100%;
+      height: calc(100% - 63px);
       margin-left: 10px;
       background: var(--image-box1-bg1) no-repeat;
       background-size: 100% 100%;
@@ -143,27 +165,28 @@ onMounted(() => {
 
   .system-box-b {
     &:nth-child(1) {
-      width: 770px;
+      width: calc(50% - 5px);
       height: 100%;
-      margin-right: 10px;
+      margin-right: 5px;
       background: var(--image-box1-bg3) no-repeat;
       background-size: 100% 100%;
     }
 
     &:nth-child(2) {
-      width: calc(100% - 1220px);
+      width: calc(50% - 5px);
       height: 100%;
-      background: var(--image-box1-bg3) no-repeat;
-      background-size: 100% 100%;
+      margin-left: 5px;
+        background: var(--image-box1-bg3) no-repeat;
+         background-size: 100% 100%;
     }
 
-    &:nth-child(3) {
-      width: 430px;
-      height: 100%;
-      margin-left: 10px;
-      background: var(--image-box1-bg2) no-repeat;
-      background-size: 100% 100%;
-    }
+    // &:nth-child(3) {
+    //   width: 430px;
+    //   height: 100%;
+    //   margin-left: 10px;
+    //   background: var(--image-box1-bg2) no-repeat;
+    //   background-size: 100% 100%;
+    // }
   }
 }
 

+ 70 - 20
src/views/vent/monitorManager/hsqHome/warnMonitor.vue

@@ -1,16 +1,22 @@
 <template>
+  <!-- 告警监控主容器 -->
   <div class="warn-monitor">
+    <!-- 顶部区域:三个面板横向排列 -->
     <div class="warn-top-box">
+      <!-- 左侧面板:告警列表 -->
       <div class="baisc-box">
-        <WarnLeftTop></WarnLeftTop>
+        <WarnLeftTop :tableData="tableData" @detail="handlerDetail"></WarnLeftTop>
       </div>
+      <!-- 中间面板:告警详情展示 -->
       <div class="baisc-box">
-        <WarnCenTop></WarnCenTop>
+        <WarnCenTop :warnData="warnData"></WarnCenTop>
       </div>
+      <!-- 右侧面板:告警统计 -->
       <div class="baisc-box">
         <WarnRightTop></WarnRightTop>
       </div>
     </div>
+    <!-- 底部区域:告警趋势/历史记录 -->
     <div class="warn-bot-box">
       <WarnBottom></WarnBottom>
     </div>
@@ -22,30 +28,74 @@ import WarnLeftTop from './components/WarnLeftTop.vue';
 import WarnCenTop from './components/WarnCenTop.vue'
 import WarnRightTop from './components/WarnRightTop.vue'
 import WarnBottom from './components/WarnBottom.vue'
+import { autoLogList, managesysList } from './hsqHome.api'
 
+// 告警列表数据
+let tableData = ref<any[]>([])
+// 当前选中的告警详情数据
+let warnData = ref({})
+// 系统ID,用于筛选数据
+let systemId = ref('')
 
-// https获取监测数据
+// 定时器引用,用于页面卸载时清理
 let timer: null | NodeJS.Timeout = null;
-// function getMonitor() {
-//   timer = setTimeout(async () => {
-//     getSystemApi({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-//     if (timer) {
-//       timer = null;
-//     }
-//     getMonitor();
-//   }, 10000);
-// }
-
-onMounted(() => {
-  // fetchConfigs('gas_injection').then(() => {
-  //   configs.value = testConfigWarn;
-  //   getSystemApi1({ devicetype: 'sys', systemID: '2036323791827165185' }).then(updateData);
-  // });
-  // getMonitor();
+/**
+ * 定时获取监测数据
+ * 
+ * @param flag 是否立即执行(默认false,延迟30秒执行)
+ */
+function getMonitor(flag?: boolean) {
+  timer = setTimeout(async () => {
+    // 获取告警列表数据
+    await getWarnList();
+    if (timer) {
+      timer = null;
+    }
+    // 递归调用实现持续轮询
+    getMonitor();
+  }, flag ? 0 : 30000);
+}
+
+/**
+ * 获取告警列表数据
+ * 根据systemId筛选告警记录,最多返回100条
+ */
+async function getWarnList() {
+  let res = await autoLogList({ pageNum: 1, pageSize: 100, sysId: systemId.value });
+  tableData.value = res.records || []
+}
+
+/**
+ * 处理告警详情点击事件
+ * 将选中的告警项数据传递给中间面板显示
+ * 
+ * @param item 选中的告警记录
+ */
+function handlerDetail(item: any) {
+  warnData.value = item
+}
+
+/**
+ * 获取系统列表并设置默认系统ID
+ * 用于初始化时获取systemId,供后续接口调用使用
+ */
+async function getManagesysList() {
+  const res = await managesysList({ strtype: 'sys_openair_fire', pagetype: 'normal' });
+  if (res && res.records) {
+    systemId.value = res.records[0].id
+  }
+}
+
+// 页面加载时初始化数据并启动定时轮询
+onMounted(async() => {
+   await getManagesysList();
+   getMonitor(true);
 });
 
+// 页面卸载时清理定时器,防止内存泄漏
 onUnmounted(() => {
-  // clearInterval(interval);
+  timer = null
+  clearTimeout(timer);
 });
 </script>