Quellcode durchsuchen

feat(DynamicPage):组态页面改为通用模板
feat(FlowRouteSyncSwitch):同步路由

HMY vor 7 Monaten
Ursprung
Commit
4b3921179f

+ 53 - 0
ui/src/components/GeneralComponents/control/FlowRouteSyncSwitch.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="flow-route-sync-switch">
+    <el-switch
+      v-model="flowStore.isSyncEnabled"
+      :active-text="activeText"
+      :inactive-text="inactiveText"
+    />
+  </div>
+</template>
+
+<script setup>
+import { onBeforeUnmount, watch } from 'vue'
+import { useSyncFlowRoute } from '@/utils/flowRouter/useSyncFlowRoute'
+import { useFlowStore } from '@/store/modules/useFlowStore'
+
+defineProps({
+  activeText: { type: String, default: '路由同步开' },
+  inactiveText: { type: String, default: '路由同步关' },
+})
+
+const flowStore = useFlowStore()
+const { start, stop, cleanup } = useSyncFlowRoute()
+
+// 页面加载时,自动根据 store 状态启用同步
+watch(
+  () => flowStore.isSyncEnabled,
+  (enabled) => {
+    if (enabled) start()
+    else stop()
+  },
+  { immediate: true }
+)
+
+// 卸载组件时停止同步
+onBeforeUnmount(() => {
+  stop()
+  cleanup()
+})
+</script>
+
+<style scoped>
+.flow-route-sync-switch {
+  display: inline-flex;
+  align-items: center;
+
+  :deep(.el-switch__label) {
+    color: aliceblue;
+  }
+  :deep(.el-switch__label.is-active) {
+    color: cornflowerblue;
+  }
+}
+</style>

+ 49 - 144
ui/src/components/GeneralComponents/control/PageSwitcherComponent.vue

@@ -1,17 +1,17 @@
 <template>
     <div class="page_switch_tabs">
         <!-- 参数配置 -->
-        <div class="tab_item" :class="{ active: isParamPage }" @click="goParam(flowKey)">
+        <div class="tab_item" :class="{ active: isParamPage }" @click="goParam">
             <span>参数配置</span>
         </div>
 
         <!-- 控制页面 -->
-        <div class="tab_item" :class="{ active: isControlPage }" @click="goControl(flowKey)">
+        <div class="tab_item" :class="{ active: isControlPage }" @click="goControl">
             <span>控制页面</span>
         </div>
 
-        <!-- 组态页面(可能有多个:下拉选择) -->
-        <el-dropdown trigger="click" @command="onPickConfig" v-if="configList.length > 1">
+        <!-- 组态页面 -->
+        <el-dropdown trigger="click" @command="goConfig" v-if="configList.length > 1">
             <div class="tab_item" :class="{ active: isConfiguratePage }">
                 <span>组态页面</span>
                 <i class="el-icon-arrow-down" style="font-size:12px"></i>
@@ -24,154 +24,66 @@
                 </el-dropdown-menu>
             </template>
         </el-dropdown>
-        <div v-else class="tab_item" :class="{ active: isConfiguratePage }" @click="goConfig(flowKey)">
+        <div v-else class="tab_item" :class="{ active: isConfiguratePage }" @click="goConfig()">
             <span>组态页面</span>
         </div>
 
-        <!-- 装饰性滑块 -->
+        <!-- 滑块 -->
         <div class="tab_slider" :style="sliderStyle"></div>
     </div>
 </template>
 
 <script setup>
-import { computed } from 'vue';
-import { useRouter, useRoute } from 'vue-router';
-//TODO: 数据库新增一张路由映射表,而不是在代码里写死
-/**
- * ① 以 flowKey 为核心的注册表
- *    同一 flowKey 下,收口 control / param / configurates(1~n)
- */
-const FLOW_REGISTRY = {
-    reaction: {
-        label: '反应分离',
-        control: '/controlPage/flowSelect/reaction',
-        param: '/controlPage/paramConfiguration/reaction',
-        configurates: [
-            { label: '反应分离1', code: 'Na2SO4_FY', path: '/configuratePage/Na2SO4_FY' },
-            { label: '反应分离2', code: 'Na2SO4_FYFL2', path: '/configuratePage/Na2SO4_FYFL2' },
-        ],
-    },
-    flatBeltFiltration: {
-        label: '24平真空过滤',
-        control: '/controlPage/flowSelect/flatBeltFiltration',
-        param: '/controlPage/paramConfiguration/flatBeltFiltration',
-        configurates: [
-            { label: '24平真空过滤', code: 'Na2SO4_24PDSGL', path: '/configuratePage/Na2SO4_24PDSGL' },
-        ],
-    },
-    G1Decomposition: {
-        label: 'G1分解',
-        control: '/controlPage/flowSelect/G1Decomposition',
-        param: '/controlPage/paramConfiguration/G1Decomposition',
-        configurates: [
-            { label: 'G1分解1', code: 'Na2SO4_G1FJ', path: '/configuratePage/Na2SO4_G1FJ' },
-            { label: 'G1分解2', code: 'Na2SO4_G1FJ2', path: '/configuratePage/Na2SO4_G1FJ2' },
-        ],
-    },
-    G3Decomposition: {
-        label: 'G3分解',
-        control: '/controlPage/flowSelect/G3Decomposition',
-        param: '/controlPage/paramConfiguration/G3Decomposition',
-        configurates: [
-            { label: 'G3分解1', code: 'Na2SO4_G3FJ', path: '/configuratePage/Na2SO4_G3FJ' },
-            { label: 'G3分解2', code: 'Na2SO4_G3FJ2', path: '/configuratePage/Na2SO4_G3FJ2' },
-        ],
-    },
-};
+import { computed } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { useFlowStore } from '@/store/modules/useFlowStore'
+import { FLOW_REGISTRY } from '@/utils/flowRouter/flowRegistry'
 
-// 反查:config code -> flowKey
-const CONFIG_CODE_TO_FLOW_KEY = Object.entries(FLOW_REGISTRY).reduce((m, [fk, v]) => {
-    v.configurates.forEach(c => (m[c.code] = fk));
-    return m;
-}, {});
+const router = useRouter()
+const route = useRoute()
+const flowStore = useFlowStore()
 
-const router = useRouter();
-const route = useRoute();
+// 当前 flowKey
+const flowKey = computed(() => flowStore.flowKey)
 
-/**
- * ② 从任意路由解析 flowKey + 当前页类型
- *    - 控制页:/controlPage/flowSelect/:flowKey
- *    - 参数页:/controlPage/paramConfiguration/:flowKey
- *    - 组态页:/configuratePage/:configCode -> 反查出 flowKey
- */
-const flowKey = computed(() => {
-    const segs = route.path.split('/').filter(Boolean);
-    console.log(segs);
-    if (route.path.startsWith('/controlPage/flowSelect/')) {
-        return segs[2]; // .../flowSelect/:flowKey
-    }
-    if (route.path.startsWith('/controlPage/paramConfiguration/')) {
-        return segs[2]; // .../paramConfiguration/:flowKey
-    }
-    if (route.path.startsWith('/configuratePage/')) {
-        const code = segs[1]; // .../configuratePage/:code
-        return CONFIG_CODE_TO_FLOW_KEY[code];
-    }
-    console.warn(`未知路由:${route.path}`);
-    return undefined;
-});
-
-const isControlPage = computed(() => route.path.startsWith('/controlPage/flowSelect/'));
-const isParamPage = computed(() => route.path.startsWith('/controlPage/paramConfiguration/'));
-const isConfiguratePage = computed(() => route.path.startsWith('/configuratePage/'));
-
-const activeIndex = computed(() => {
-    if (isParamPage.value) return 0;
-    if (isControlPage.value) return 1;
-    return 2; // 组态页
-});
+// 当前 flow 下的组态候选
+const configList = computed(() => FLOW_REGISTRY[flowKey.value]?.configurates ?? [])
 
-// 当前 flow 的组态候选
-const configList = computed(() => {
-    const fk = flowKey.value;
-    if (!fk) return [];
-    return FLOW_REGISTRY[fk]?.configurates ?? [];
-});
+// 当前页面类型
+const isControlPage = computed(() => route.path.startsWith('/controlPage/flowSelect/'))
+const isParamPage = computed(() => route.path.startsWith('/controlPage/paramConfiguration/'))
+const isConfiguratePage = computed(() => route.path.startsWith('/configuratePage/'))
 
-// ③ 最近使用的组态 code(每个 flowKey 记一次)
-const getLastConfigCode = (fk) => localStorage.getItem(`last_config_code_${fk}`) || '';
-const setLastConfigCode = (fk, code) => localStorage.setItem(`last_config_code_${fk}`, code);
-
-const getDefaultConfig = (fk) => {
-    const list = FLOW_REGISTRY[fk]?.configurates || [];
-    if (!list.length) return undefined;
-    const hit = list.find(x => x.code === getLastConfigCode(fk));
-    return hit || list[0];
-};
-
-// ④ 跳转函数(任意页 → 其余两类)
-const goControl = (fk) => {
-    const path = FLOW_REGISTRY[fk]?.control;
-    if (path) router.push(path);
-};
-
-const goParam = (fk) => {
-    console.log(fk);
-    const path = FLOW_REGISTRY[fk]?.param;
-    console.log(path);
-    if (path) router.push(path);
-};
-
-const goConfig = (fk, code) => {
-    const list = FLOW_REGISTRY[fk]?.configurates || [];
-    if (!list.length) return;
-    const target = code ? list.find(x => x.code === code) : getDefaultConfig(fk);
+// 滑块位置
+const activeIndex = computed(() => (isParamPage.value ? 0 : isControlPage.value ? 1 : 2))
+const sliderStyle = computed(() => ({
+    transform: `translateX(${activeIndex.value * 100}%)`
+}))
+
+// 跳转函数(同时更新 store + router)
+const goControl = () => {
+    const path = FLOW_REGISTRY[flowKey.value]?.control
+    if (path) {
+        flowStore.setFlowKey(flowKey.value)
+        router.push(path)
+    }
+}
+const goParam = () => {
+    const path = FLOW_REGISTRY[flowKey.value]?.param
+    if (path) {
+        flowStore.setFlowKey(flowKey.value)
+        router.push(path)
+    }
+}
+const goConfig = (code) => {
+    const list = FLOW_REGISTRY[flowKey.value]?.configurates || []
+    if (!list.length) return
+    const target = code ? list.find(x => x.code === code) : list[0]
     if (target) {
-        setLastConfigCode(fk, target.code);
-        router.push(target.path);
+        flowStore.setFlowKey(flowKey.value)
+        router.push(target.path)
     }
-};
-
-// 下拉选择具体组态
-const onPickConfig = (code) => {
-    if (!flowKey.value) return;
-    goConfig(flowKey.value, code);
-};
-
-// ⑤ 滑块样式(3个 Tab)
-const sliderStyle = computed(() => ({
-    transform: `translateX(${activeIndex.value * 100}%)`,
-}));
+}
 </script>
 
 <style scoped lang="scss">
@@ -197,7 +109,6 @@ const sliderStyle = computed(() => ({
     transition: color .3s ease;
     display: flex;
     align-items: center;
-    // gap: 8px;
 
     &:hover {
         color: #e2e8f0;
@@ -207,10 +118,6 @@ const sliderStyle = computed(() => ({
         color: #fff;
         font-weight: 500;
     }
-
-    i {
-        font-size: 15px;
-    }
 }
 
 .tab_slider {
@@ -218,11 +125,9 @@ const sliderStyle = computed(() => ({
     top: 3px;
     left: 3px;
     width: calc(33.333% - 3px);
-    /* 由 50% 改为三等分 */
     height: calc(100% - 6px);
     background-color: #0ea5e9;
     border-radius: 6px;
     transition: transform .3s cubic-bezier(0.34, 1.56, 0.64, 1);
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
 }
 </style>

+ 35 - 16
ui/src/hooks/useEquipmentLayout.js

@@ -1,10 +1,18 @@
 // 管理设备分组和模型映射,处理布局参数
-import { ref } from 'vue'
-import { getPageConfig } from '@/api/dcs/configurePage'
+import { ref, reactive, watch } from "vue"
+import { getPageConfig } from "@/api/dcs/configurePage"
 
 export function useEquipmentLayout(pageCode) {
-    const deviceConfigGroup = ref({})// 设备配置分组
-    const pipeConfig= ref({})// 管道配置
+    const pipeConfig = ref([]) // 管道配置
+    const deviceConfigGroup = reactive({
+        VALVES: [],
+        PUMPS: [],
+        SENSORS: [],
+        TANKS: [],
+        OTHERS: [],
+        ICONS: [],
+        ARROWS: []
+    }) // 设备分组,保证不会 undefined
     const modelMap = ref({})
     const pageParams = ref({})
 
@@ -13,6 +21,7 @@ export function useEquipmentLayout(pageCode) {
         const result = {}
         for (const groupKey in group) {
             const devices = group[groupKey]
+            if (!Array.isArray(devices)) continue
             const codes = devices
                 .map(item => item.code)
                 .filter(code => code) // 过滤 null/空值
@@ -24,26 +33,36 @@ export function useEquipmentLayout(pageCode) {
     }
 
     // 获取页面配置
-    async function fetchPageConfig(pageCode) {
+    async function fetchPageConfig(code) {
+        if (!code) return
         try {
-            const res = await getPageConfig(pageCode)
-            console.log('获取页面配置成功:', res)
-            deviceConfigGroup.value = res.data.deviceConfigGroup// 设备分组
-            modelMap.value = res.data.modelMap// 模型映射
-            pipeConfig.value = res.data.pipeConfig// 管道配置
-            pageParams.value = generatePageParams(res.data.deviceConfigGroup)// 页面参数
-            // console.log('pipeConfig:', pipeConfig.value)
+            const res = await getPageConfig(code)
+            console.log("获取页面配置成功:", res)
+
+            pipeConfig.value = res.data.pipeConfig || []
+            Object.keys(deviceConfigGroup).forEach(key => {
+                deviceConfigGroup[key] = res.data.deviceConfigGroup?.[key] || []
+            })
+            modelMap.value = res.data.modelMap || {}
+            pageParams.value = generatePageParams(deviceConfigGroup)
         } catch (error) {
-            console.error('获取页面配置失败:', error)
+            console.error("获取页面配置失败:", error)
         }
     }
 
+    // 如果传入的是 ref 或 computed 的 pageCode → 自动监听
+    if (pageCode && pageCode.value !== undefined) {
+        watch(pageCode, (newVal) => {
+            if (newVal) fetchPageConfig(newVal)
+        }, { immediate: true })
+    }
+
     return {
         pipeConfig,// 管道配置
-        deviceConfigGroup,// 设备配置分组
+        deviceConfigGroup,// 设备分组
         modelMap,// 模型映射
         pageParams,// 页面参数
-        fetchPageConfig,
-        generatePageParams,
+        fetchPageConfig,// 获取页面配置
+        generatePageParams,// 生成页面参数
     }
 }

+ 2 - 1
ui/src/main.js

@@ -9,6 +9,7 @@ import DataVVue3 from '@kjgl77/datav-vue3'//dataV组件库
 import HeaderComponent from '@/components/DCS/HeaderComponent.vue'
 import PageDrawer from '@/components/HnyzDcs/PageDrawerComponent.vue';
 import PageSwitcher from '@/components/GeneralComponents/control/PageSwitcherComponent.vue';
+import FlowRouteSyncSwitch from '@/components/GeneralComponents/control/FlowRouteSyncSwitch.vue';//同步路由开关
 import ElementPlus from 'element-plus'
 import 'element-plus/dist/index.css'
 import 'element-plus/theme-chalk/dark/css-vars.css'
@@ -80,7 +81,7 @@ app.component('svg-icon', SvgIcon)
 app.component('HeaderComponent', HeaderComponent)//header组件
 app.component('PageDrawer', PageDrawer)//侧边栏组件
 app.component('PageSwitcher', PageSwitcher)//控制、参数配置、组态页面切换组件
-
+app.component('FlowRouteSyncSwitch', FlowRouteSyncSwitch)//同步路由开关组件
 directive(app)
 
 // 使用element-plus 并且设置全局的大小

+ 59 - 45
ui/src/router/index.js

@@ -153,7 +153,7 @@ export const constantRoutes = [
   //   name: 'SolidLiquidSeparation_Control',//一次固液分离控制页面
   // },
   //Na2SO4控制页面(动态路由)
-   {
+  {
     path: '/controlPage/flowSelect/:flowCode',
     name: 'FlowControlPage',
     component: () => import('@/views/controlPage/flowSelect/FlowControlPage.vue')
@@ -185,6 +185,7 @@ export const constantRoutes = [
     component: () => import('@/views/configuratePage/blockOut/index'),
     name: 'blockOut',
   },
+
   //湖南组态页面
   {
     path: '/CaSO4_1',//硫酸钙
@@ -226,52 +227,65 @@ export const constantRoutes = [
     component: () => import('@/views/hnyzConfiguratePage/MGM/index'),
     name: 'MGM',
   },
-  //湖南硫酸钠组态页面
-  {
-    path: '/configuratePage/Na2SO4_FY',//硫酸钠-反应
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_FY/index'),
-    name: 'Na2SO4_FY',
-  },
-  {
-    path: '/configuratePage/Na2SO4_FYFL2',//硫酸钠-反应分离2
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_FYFL2/index'),
-    name: 'Na2SO4_FYFL2',
-  },
-  {
-    path: '/configuratePage/Na2SO4_GYFL',//硫酸钠-固液分离
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_GYFL/index'),
-    name: 'Na2SO4_GYFL',
-  },
-  {
-    path: '/configuratePage/Na2SO4_24PDSGL',//硫酸钠-24平带式过滤
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_24PDSGL/index'),
-    name: 'Na2SO4_24PDSGL',
-  },
-  {
-    path: '/configuratePage/Na2SO4_G1FJ',//硫酸钠-G1分解
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G1FJ/index'),
-    name: 'Na2SO4_G1FJ',
-  },
-  {
-    path: '/configuratePage/Na2SO4_G1FJ2',//硫酸钠-G1附属分离
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G1FJ2/index'),
-    name: 'Na2SO4_G1FJ2',
-  },
+  // TODO: 改为动态配置路由
+  // router/index.js 或者 routes.js
   {
-    path: '/configuratePage/Na2SO4_RJ',//硫酸钠-溶晶
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_RJ/index'),
-    name: 'Na2SO4_RJ',
-  },
-  {
-    path: '/configuratePage/Na2SO4_G3FJ',//硫酸钠-G3分解
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G3FJ/index'),
-    name: 'Na2SO4_G3FJ',
-  },
-  {
-    path: '/configuratePage/Na2SO4_G3FJ2',//硫酸钠-G3分解2
-    component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G3FJ2/index'),
-    name: 'Na2SO4_G3FJ2',
+    path: '/configuratePage/:pageCode',
+    name: 'configuratePage',
+    component: () => import('@/views/hnyzConfiguratePage/DynamicPage.vue'),
+    props: true
   },
+  //湖南硫酸钠组态页面
+  // {
+  //   path: '/configuratePage/Na2SO4_HR',//硫酸钠-换热
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_HR/index'),
+  //   name: 'Na2SO4_HR',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_FY',//硫酸钠-反应
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_FY/index'),
+  //   name: 'Na2SO4_FY',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_FYFL2',//硫酸钠-反应分离2
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_FYFL2/index'),
+  //   name: 'Na2SO4_FYFL2',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_GYFL',//硫酸钠-固液分离
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_GYFL/index'),
+  //   name: 'Na2SO4_GYFL',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_24PDSGL',//硫酸钠-24平带式过滤
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_24PDSGL/index'),
+  //   name: 'Na2SO4_24PDSGL',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_G1FJ',//硫酸钠-G1分解
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G1FJ/index'),
+  //   name: 'Na2SO4_G1FJ',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_G1FJ2',//硫酸钠-G1附属分离
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G1FJ2/index'),
+  //   name: 'Na2SO4_G1FJ2',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_RJ',//硫酸钠-溶晶
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_RJ/index'),
+  //   name: 'Na2SO4_RJ',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_G3FJ',//硫酸钠-G3分解
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G3FJ/index'),
+  //   name: 'Na2SO4_G3FJ',
+  // },
+  // {
+  //   path: '/configuratePage/Na2SO4_G3FJ2',//硫酸钠-G3分解2
+  //   component: () => import('@/views/hnyzConfiguratePage/Na2SO4_G3FJ2/index'),
+  //   name: 'Na2SO4_G3FJ2',
+  // },
   { // 页面设计器
     path: '/deviceState',
     hidden: false,

+ 27 - 0
ui/src/store/modules/useFlowStore.js

@@ -0,0 +1,27 @@
+import { defineStore } from 'pinia'
+
+export const useFlowStore = defineStore('flow', {
+  state: () => ({
+    isSyncEnabled: false,
+    flowKey: '',
+    configCode: '',
+    // 新增:用于跟踪最后同步时间戳,避免旧状态覆盖新状态
+    lastSyncTimestamp: 0
+  }),
+  actions: {
+    setIsSyncEnabled(enabled) {
+      this.isSyncEnabled = enabled
+    },
+    setFlowKey(key) {
+      this.flowKey = key
+    },
+    setConfigCode(code) {
+      this.configCode = code
+    }
+  },
+  // 关键:开启持久化,默认存储到 localStorage
+  persist: {
+    key: 'flow_store', // 存储键名
+    paths: ['isSyncEnabled', 'flowKey', 'configCode'] // 需要持久化的字段
+  }
+})

+ 52 - 0
ui/src/utils/flowRouter/flowRegistry.js

@@ -0,0 +1,52 @@
+//相关路由映射
+export const FLOW_REGISTRY = {
+  reaction: {
+    label: '反应分离',
+    control: '/controlPage/flowSelect/reaction',
+    param: '/controlPage/paramConfiguration/reaction',
+    configurates: [
+      { label: '反应分离1', code: 'Na2SO4_FY', path: '/configuratePage/Na2SO4_FY' },
+      { label: '反应分离2', code: 'Na2SO4_FYFL2', path: '/configuratePage/Na2SO4_FYFL2' },
+    ],
+  },
+  flatBeltFiltration: {
+    label: '24平真空过滤',
+    control: '/controlPage/flowSelect/flatBeltFiltration',
+    param: '/controlPage/paramConfiguration/flatBeltFiltration',
+    configurates: [
+      { label: '24平真空过滤', code: 'Na2SO4_24PDSGL', path: '/configuratePage/Na2SO4_24PDSGL' },
+    ],
+  },
+  G1Decomposition: {
+    label: 'G1分解',
+    control: '/controlPage/flowSelect/G1Decomposition',
+    param: '/controlPage/paramConfiguration/G1Decomposition',
+    configurates: [
+      { label: 'G1分解1', code: 'Na2SO4_G1FJ', path: '/configuratePage/Na2SO4_G1FJ' },
+      { label: 'G1分解2', code: 'Na2SO4_G1FJ2', path: '/configuratePage/Na2SO4_G1FJ2' },
+    ],
+  },
+  G3Decomposition: {
+    label: 'G3分解',
+    control: '/controlPage/flowSelect/G3Decomposition',
+    param: '/controlPage/paramConfiguration/G3Decomposition',
+    configurates: [
+      { label: 'G3分解1', code: 'Na2SO4_G3FJ', path: '/configuratePage/Na2SO4_G3FJ' },
+      { label: 'G3分解2', code: 'Na2SO4_G3FJ2', path: '/configuratePage/Na2SO4_G3FJ2' },
+    ],
+  },
+  heatExchange:{
+    label: 'S3-S5换热',
+    control: '/controlPage/flowSelect/heatExchange',
+    param: '/controlPage/paramConfiguration/heatExchange',
+    configurates: [
+      { label: 'S3-S5换热', code: 'Na2SO4_HR', path: '/configuratePage/Na2SO4_HR' },
+    ],
+  }
+}
+
+// 自动生成:configCode → flowKey
+export const CONFIG_CODE_TO_FLOW_KEY = Object.entries(FLOW_REGISTRY).reduce((map, [fk, v]) => {
+  v.configurates.forEach(c => (map[c.code] = fk))
+  return map
+}, {})

+ 182 - 0
ui/src/utils/flowRouter/useSyncFlowRoute.js

@@ -0,0 +1,182 @@
+// composables/useSyncFlowRoute.js
+import { watch, nextTick } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { useFlowStore } from '@/store/modules/useFlowStore'
+import { FLOW_REGISTRY } from './flowRegistry'
+
+const STORAGE_KEY = 'global_flow_state'
+const INSTANCE_ID = Math.random().toString(36).substring(2, 10)
+
+export function useSyncFlowRoute() {
+  const router = useRouter()
+  const route = useRoute()
+  const flowStore = useFlowStore()
+
+  let stopFns = []
+  let isStarted = false
+  let isInternalNavigation = false // 内部跳转标记
+  /**
+   * 广播状态到localStorage
+   */
+  const broadcastState = () => {
+    if (!flowStore.isSyncEnabled) return
+    const state = {
+      instanceId: INSTANCE_ID,
+      flowKey: flowStore.flowKey,
+      timestamp: Date.now()
+    }
+    localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
+  }
+
+  /**
+   * 解析控制页面路由 → 更新flowKey(确保标记重置)
+   */
+  const parseControlRoute = () => {
+    // 1. 若为内部跳转,仅重置标记,不处理逻辑
+    if (isInternalNavigation) {
+      isInternalNavigation = false // 关键:每次内部跳转后强制重置
+      return
+    }
+
+    // 2. 同步关闭或非控制页面,不处理
+    if (!flowStore.isSyncEnabled || !route.path.startsWith('/controlPage/flowSelect/')) {
+      return
+    }
+
+    // 3. 提取控制页面的flowKey并更新Store
+    const segs = route.path.split('/').filter(Boolean)
+    const targetFlowKey = segs[2] || ''
+    if (targetFlowKey && targetFlowKey !== flowStore.flowKey) {
+      flowStore.setFlowKey(targetFlowKey)
+      flowStore.setConfigCode('')
+      broadcastState() // 广播新状态
+      // console.log(`控制页面路由变化 → 更新flowKey: ${targetFlowKey}`)
+    }
+  }
+
+  /**
+   * 跨Tab同步:监听localStorage变化
+   */
+  const onStorage = (e) => {
+    if (e.key !== STORAGE_KEY || !e.newValue) return
+    try {
+      const newState = JSON.parse(e.newValue)
+      // 过滤自身、无效、过期状态(5秒有效期)
+      if (
+        !newState ||
+        newState.instanceId === INSTANCE_ID ||
+        !newState.flowKey ||
+        newState.timestamp < Date.now() - 5000
+      ) {
+        return
+      }
+
+      // 更新Store(标记为内部操作,避免循环)
+      if (newState.flowKey !== flowStore.flowKey) {
+        isInternalNavigation = true
+        flowStore.setFlowKey(newState.flowKey)
+        flowStore.setConfigCode('')
+        // console.log(`跨Tab同步 → 更新flowKey: ${newState.flowKey}`)
+      }
+    } catch (err) {
+      console.error('跨Tab同步失败:', err)
+      isInternalNavigation = false // 错误时强制重置标记
+    }
+  }
+
+  /**
+   * 同步参数页/组态页路由(修复:跳转后立即重置标记)
+   */
+  const syncTargetPages = async () => {
+    // 前置校验:同步关闭或无flowKey,不处理
+    if (!flowStore.isSyncEnabled || !flowStore.flowKey) {
+      isInternalNavigation = false // 空状态时重置标记
+      return
+    }
+
+    const currentFlowConfig = FLOW_REGISTRY[flowStore.flowKey]
+    if (!currentFlowConfig) {
+      console.warn(`未找到flowKey: ${flowStore.flowKey} 的配置`)
+      isInternalNavigation = false // 无配置时重置标记
+      return
+    }
+
+    // 标记为内部跳转(准备跳转)
+    isInternalNavigation = true
+    await nextTick() // 等待DOM更新完成
+
+    try {
+      // 1. 同步参数配置页面
+      if (route.path.startsWith('/controlPage/paramConfiguration/')) {
+        const targetParamPath = currentFlowConfig.param || ''
+        if (targetParamPath && route.path !== targetParamPath) {
+          await router.push(targetParamPath) // 等待跳转完成
+          // console.log(`参数页面同步 → 目标路由: ${targetParamPath}`)
+        }
+      }
+
+      // 2. 同步组态页面(默认第一个组态页)
+      if (route.path.startsWith('/configuratePage/')) {
+        const targetConfig = currentFlowConfig.configurates[0] || {}
+        const targetConfigPath = targetConfig.path || ''
+        if (targetConfigPath && route.path !== targetConfigPath) {
+          await router.push(targetConfigPath) // 等待跳转完成
+          // console.log(`组态页面同步 → 目标路由: ${targetConfigPath}`)
+        }
+      }
+    } finally {
+      // 关键修复:无论跳转成功/失败,都强制重置标记
+      isInternalNavigation = false
+    }
+  }
+
+  /**
+   * 启动同步(优化监听时序)
+   */
+  function start() {
+    if (isStarted) return
+    isStarted = true
+
+    // 1. 监听控制页面路由变化(优先执行,确保flowKey先更新)
+    const stopWatchControlRoute = watch(
+      () => route.path,
+      parseControlRoute,
+      { immediate: true, flush: 'post' } // flush:post 确保路由完全更新后再解析
+    )
+
+    // 2. 监听flowKey变化 → 同步目标页面(使用immediate确保初始状态同步)
+    const stopWatchFlowKey = watch(
+      () => flowStore.flowKey,
+      syncTargetPages,
+      { immediate: true, flush: 'post' }
+    )
+
+    stopFns = [stopWatchControlRoute, stopWatchFlowKey]
+  }
+
+  /**
+   * 停止同步
+   */
+  function stop() {
+    if (!isStarted) return
+    isStarted = false
+    isInternalNavigation = false // 停止时重置标记
+    stopFns.forEach(fn => fn && fn())
+    stopFns = []
+  }
+
+  /**
+   * 初始化跨Tab监听
+   */
+  window.addEventListener('storage', onStorage)
+
+  /**
+   * 清理资源(组件卸载时调用)
+   */
+  const cleanup = () => {
+    window.removeEventListener('storage', onStorage)
+    stop()
+  }
+
+  return { start, stop, cleanup }
+}

+ 9 - 3
ui/src/views/controlPage/flowSelect/FlowControlPage.vue

@@ -5,10 +5,11 @@
             <HeaderComponent title="硫酸钠 - 控制页面" backTo="/controlPage/flowSelect" />
             <PageNav :items="navItems" :currentCode="flowCode" />
             <PageSwitcher />
+            <FlowRouteSyncSwitch active-text="同步开启" inactive-text="同步关闭" class="flow_route_sync_switch" />
         </div>
 
         <!-- 主内容区 -->
-        <div class="main-container">
+        <div class="main-container page-main-container">
             <div class="main-content">
                 <!-- 设备区域 -->
                 <div class="content-area">
@@ -56,7 +57,6 @@ import PumpControlComponent from '@/components/GeneralComponents/control/PumpCon
 import ValveControlComponent from '@/components/GeneralComponents/control/ValveControl2Component.vue';
 import WorkModeControl from '@/components/GeneralComponents/control/WorkModeControl2Component.vue';
 import TankNavigation from '@/components/GeneralComponents/control/TankNavigationComponent.vue';
-
 import { getPageEquipmentGroupByTankByFlowCode } from '@/api/hnyz/equipment';
 import { useEquipmentLayout } from '@/hooks/useEquipmentLayout';
 import { stompClient } from '@/utils/ws/stompClient';
@@ -191,6 +191,11 @@ onBeforeUnmount(() => {
     z-index: 1000;
     background-color: #141414;
     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+     .flow_route_sync_switch{
+        position: absolute;
+        top: 95px;
+        left: 10px;
+    }
 }
 
 // 主容器 - 添加顶部内边距,避免被固定头部遮挡
@@ -367,7 +372,8 @@ onBeforeUnmount(() => {
 
     .tank_section {
         padding: 16px 18px;
-        gap: 16px;      scroll-margin-top: 140px;
+        gap: 16px;
+        scroll-margin-top: 140px;
     }
 
     .equipment_item {

+ 11 - 4
ui/src/views/controlPage/paramConfiguration/ParamConfigPage.vue

@@ -3,11 +3,12 @@
         <!-- 固定头部 -->
         <div class="fixed_header" ref="fixedHeader">
             <HeaderComponent title="硫酸钠 - 参数配置" />
-            <PageNav :items="navItems" :currentCode="pageCode"/>
+            <PageNav :items="navItems" :currentCode="pageCode" />
+            <FlowRouteSyncSwitch active-text="同步开启" inactive-text="同步关闭" class="flow_route_sync_switch" />
             <PageSwitcher />
         </div>
 
-        <div class="main_container">
+        <div class="main_container page-main-container">
             <div class="main_content">
                 <div class="content_area">
                     <!-- 加载状态 -->
@@ -81,7 +82,7 @@ const initialParamData = ref({})
 const tankNavList = ref([])
 const currentNavIndex = ref(-1)
 
-// 过滤阀门设备
+// 过滤阀门设备(阀门暂时无参数配置)
 const filteredDeviceConfigGroup = computed(() => {
     const filtered = {}
     Object.entries(deviceConfigGroup.value).forEach(([tankName, equipments]) => {
@@ -215,6 +216,12 @@ onBeforeUnmount(() => { window.removeEventListener('resize', calculateHeaderHeig
     z-index: 1000;
     background-color: #252525;
     box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+
+    .flow_route_sync_switch {
+        position: absolute;
+        top: 95px;
+        left: 10px;
+    }
 }
 
 .main_container {
@@ -258,7 +265,7 @@ onBeforeUnmount(() => { window.removeEventListener('resize', calculateHeaderHeig
 
 .params_grid {
     display: grid;
-    grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+    grid-template-columns: repeat(auto-fill, minmax(265px, 1fr));
     gap: 20px;
 }
 

+ 188 - 0
ui/src/views/hnyzConfiguratePage/DynamicPage.vue

@@ -0,0 +1,188 @@
+<template>
+    <!-- 组态页面通用模版 -->
+    <div class="preview">
+        <div ref="pageRef" class="page">
+            <HeaderComponent :title="title" />
+            <div class="content_page">
+                <PageDrawer :pages="pageItems" class="page_drawer" />
+                <FlowRouteSyncSwitch active-text="同步开启" inactive-text="同步关闭" class="flow_route_sync_switch" />
+                <PageSwitcher class="page_switcher" />
+
+                <!-- 阀门 -->
+                <div class="valves">
+                    <component v-for="item in deviceConfigGroup.VALVES" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.equipmentName)"
+                        :valveStatusArr="getDataArrByCode(item.code)" :rotateAngle="item.rotate || 0"
+                        :iconSize="item.size" :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 泵 -->
+                <div class="pumps">
+                    <component v-for="item in deviceConfigGroup.PUMPS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.title)"
+                        :pumpDataArr="pumpHelper.getDataArrByCode(item.code)" :isReverse="item.isReverse"
+                        :iconSize="item.size" :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 罐体 -->
+                <div class="tanks">
+                    <component v-for="item in deviceConfigGroup.TANKS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.title)"
+                        :iconSize="item.size" :iconWidth="item.width" :iconHeight="item.height"
+                        :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 传感器 -->
+                <div class="sensors">
+                    <component v-for="item in deviceConfigGroup.SENSORS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.equipmentName)"
+                        :sensorSize="item.size" :specialCondition="JSON.parse(item.specialCondition || '{}')"
+                        :sensorValue="sensorHelper.getDataArrByCode(item.code)" :sensorWidth="item.width"
+                        :sensorHeight="item.height" :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 单icon设备 -->
+                <div class="icon_others">
+                    <component v-for="item in deviceConfigGroup.ICONS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :iconSize="item.size"
+                        :iconClass="getIconClass(item.modelId)" :style="getComponentStyle(item)"
+                        :title="getEquipmentTitle(item.equipmentName)" />
+                </div>
+
+                <!-- 其他组件 -->
+                <div class="other_components">
+                    <component v-for="item in deviceConfigGroup.OTHERS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :iconSize="item.size" :style="getComponentStyle(item)"
+                        :title="getEquipmentTitle(item.title)" />
+                </div>
+
+                <!-- 管道 -->
+                <div class="pipelines">
+                    <component v-for="item in pipeConfig" :key="item.id" :is="getComponentName(item.modelId)"
+                        :pipeStatus="evaluateCondition(item.flowCondition, true)" :strokeWidth="item.pipeWidth"
+                        :style="getPipelineStyle(item, evaluateCondition(item.reverseCondition))"
+                        :class="getPipelineClass(item.pipeClass, getPipeType(item.dynamicPipelineCondition))" />
+                </div>
+
+                <!-- 大箭头指向 -->
+                <div class="arrows">
+                    <component v-for="item in deviceConfigGroup.ARROWS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :arrowText="getEquipmentTitle(item.equipmentName)"
+                        :iconSize="item.size" :specialCondition="JSON.parse(item.specialCondition)"
+                        :style="getComponentStyle(item)" />
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import PageDrawer from '@/components/GeneralComponents/PageDrawerComponent.vue'
+import { computed, reactive, onBeforeUnmount } from 'vue'
+import { useValveHelper } from '@/hooks/useValveHelper'
+import { stompClient } from '@/utils/ws/stompClient'
+import { useEquipmentLayout } from '@/hooks/useEquipmentLayout'
+import { useComponentHelper } from '@/hooks/useComponentHelper'
+import { updateZTPageConfig } from '@/api/dcs/configurePage'
+
+import { useRoute } from 'vue-router'
+// 路由
+const route = useRoute()
+const pageCode = computed(() => route.params.pageCode)
+
+import { usePageScale } from '@/utils/dcs/usePageScale'
+// 页面缩放
+const { pageRef } = usePageScale()
+
+import { getNavLabel } from '@/api/hnyz/pageControl'
+// 页面配置项
+const pageItems = ref([])
+async function loadPageItems() {
+  try {
+    const { data } = await getNavLabel()
+    pageItems.value = data
+  } catch (error) {
+    console.error('加载页面列表失败:', error)
+  }
+}
+onMounted(async () => {
+  await loadPageItems()
+})
+
+const title = computed(() => {
+    const pageItem = pageItems.value.find(item => item.code === pageCode.value)
+    return pageItem ? pageItem.label : "组态页面"
+})
+
+// 使用 composable 获取配置
+const {
+    pipeConfig,
+    deviceConfigGroup,
+    modelMap,
+    pageParams
+} = useEquipmentLayout(pageCode) //  直接传 computed,自动监听切换
+
+// 组件辅助方法
+const {
+    getComponentName,
+    getComponentStyle,
+    getIconClass,
+    getEquipmentTitle,
+    getPipelineClass,
+    getPipelineStyle
+} = useComponentHelper(modelMap)
+
+// 数据分组(实时数据)
+const deviceDataGroup = reactive({
+    VALVES: [],
+    PUMPS: [],
+    SENSORS: []
+})
+
+// 更新实时数据订阅
+function updatePageConfig() {
+    updateZTPageConfig(pageCode.value, pageParams.value)
+        .then(() => {
+            stompClient.subscribeToPage(pageCode.value, (data) => {
+                deviceDataGroup.VALVES = Object.values(data?.VALVES || {})
+                deviceDataGroup.PUMPS = Object.values(data?.PUMPS || {})
+                deviceDataGroup.SENSORS = Object.values(data?.SENSORS || {})
+            })
+        })
+        .catch(err => {
+            console.log("页面配置失败:", err)
+        })
+}
+
+// 页面卸载时取消订阅
+onBeforeUnmount(() => {
+    stompClient.unsubscribeFromPage(pageCode.value)
+})
+
+// 数据 helper
+const pumpHelper = useValveHelper(computed(() => deviceDataGroup.PUMPS))
+const { getDataArrByCode } = useValveHelper(computed(() => deviceDataGroup.VALVES))
+const sensorHelper = useValveHelper(computed(() => deviceDataGroup.SENSORS))
+const { evaluateCondition, getPipeType } = useValveHelper(
+    computed(() => [...deviceDataGroup.VALVES, ...deviceDataGroup.PUMPS])
+)
+
+// 初始化订阅
+updatePageConfig()
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/styles/dcs/hnyzConfiguratePage.scss';
+
+.preview {
+    .page {
+        .content_page {
+            .flow_route_sync_switch {
+                position: absolute;
+                top: -62px;
+                right: 315px;
+            }
+        }
+    }
+}
+</style>

+ 159 - 0
ui/src/views/hnyzConfiguratePage/Na2SO4_HR/index.vue

@@ -0,0 +1,159 @@
+<template>
+    <div class="preview">
+        <div ref="pageRef" class="page">
+            <HeaderComponent title="S3-S5换热"></HeaderComponent>
+            <div class="content_page">
+            <FlowRouteSyncSwitch active-text="同步开启" inactive-text="同步关闭" class="flow_route_sync_switch"/>
+
+                <!-- <PageDrawer :pages="pageItems_Na2SO4" class="page_drawer" /> -->
+                <!-- <PageSwitcher class="page_switcher" /> -->
+                <!-- 阀门 -->
+                <div class="valves">
+                    <component v-for="item in deviceConfigGroup.VALVES" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.equipmentName)"
+                        :valveStatusArr="getDataArrByCode(item.code)" :rotateAngle="item.rotate || 0"
+                        :iconSize="item.size" :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 泵 -->
+                <div class="pumps">
+                    <component v-for="item in deviceConfigGroup.PUMPS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.equipmentName)"
+                        :pumpDataArr="pumpHelper.getDataArrByCode(item.code)" :isReverse="item.isReverse"
+                        :iconSize="item.size" :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 罐体 -->
+                <div class="tanks">
+                    <component v-for="item in deviceConfigGroup.TANKS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.title)"
+                        :iconSize="item.size" :iconWidth="item.width" :iconHeight="item.height"
+                        :style="getComponentStyle(item)" />
+                </div>
+
+                <!-- 传感器 -->
+                <div class="sensors">
+                    <component v-for="item in deviceConfigGroup.SENSORS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :title="getEquipmentTitle(item.equipmentName)"
+                        :sensorSize="item.size" :sensorValue="sensorHelper.getDataArrByCode(item.code)"
+                        :sensorWidth="item.width" :sensorHeight="item.height" :style="getComponentStyle(item)"
+                        :specialCondition="JSON.parse(item.specialCondition)" />
+                </div>
+
+                <!-- 其他设备 -->
+                <div class="icon_others">
+                    <component v-for="item in deviceConfigGroup.ICONS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :iconSize="item.size"
+                        :iconClass="getIconClass(item.modelId)" :style="getComponentStyle(item)"
+                        :title="getEquipmentTitle(item.title)" />
+                </div>
+
+                <!-- 管道  -->
+                <div class="pipelines">
+                    <component v-for="item in pipeConfig" :key="item.id" :is="getComponentName(item.modelId)"
+                        :pipeStatus="evaluateCondition(item.flowCondition, true)" :strokeWidth="item.pipeWidth"
+                        :style="getPipelineStyle(item, evaluateCondition(item.reverseCondition))"
+                        :class="getPipelineClass(item.pipeClass, getPipeType(item.dynamicPipelineCondition))" />
+                </div>
+
+                <!-- 大箭头指向 -->
+                <div class="arrows">
+                    <component v-for="item in deviceConfigGroup.ARROWS" :key="item.id"
+                        :is="getComponentName(item.modelId)" :arrowText="getEquipmentTitle(item.equipmentName)"
+                        :iconSize="item.size" :specialCondition="JSON.parse(item.specialCondition)"
+                        :style="getComponentStyle(item)" />
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import PageDrawer from '@/components/GeneralComponents/PageDrawerComponent.vue';
+import { computed, onMounted, onBeforeUnmount, reactive } from 'vue';
+import { useValveHelper } from '@/hooks/useValveHelper'
+import { stompClient } from '@/utils/ws/stompClient';
+import { useEquipmentLayout } from '@/hooks/useEquipmentLayout'
+import { useComponentHelper } from '@/hooks/useComponentHelper'
+import { updateZTPageConfig } from '@/api/dcs/configurePage';
+// import { pageItems_Na2SO4 } from "@/config"
+import { useRoute } from 'vue-router'
+
+import { usePageScale } from '@/utils/dcs/usePageScale'
+const { pageRef } = usePageScale()
+
+const route = useRoute()
+const pageCode = route.name
+// const title = pageItems_Na2SO4.find(item => item.code === pageCode).label
+// 获取设备布局 & 数据订阅
+const {
+    pipeConfig,//管道配置信息
+    deviceConfigGroup,//设备配置分组
+    modelMap,//设备模型映射
+    pageParams,//页面参数
+    fetchPageConfig,//获取页面配置
+} = useEquipmentLayout(pageCode)
+
+// 组件渲染辅助方法
+const {
+    getComponentName,
+    getComponentStyle,
+    getIconClass,
+    getEquipmentTitle,
+    getPipelineClass,
+    getPipelineStyle,
+} = useComponentHelper(modelMap)
+
+onMounted(async () => {
+    await fetchPageConfig(pageCode).then(res => {
+        updatePageConfig()
+    })
+})
+
+onBeforeUnmount(() => {
+    // 页面销毁时取消订阅
+    stompClient.unsubscribeFromPage(pageCode);
+});
+
+//阀门数据初始化
+const valveArr = [
+]
+//传感器数据初始化
+const sensorArr = [
+]
+//泵数据初始化
+const pumpArr = [
+]
+
+//设备数据分组
+const deviceDataGroup = reactive({
+    VALVES: valveArr,
+    PUMPS: pumpArr,
+    SENSORS: sensorArr,
+})
+
+//更新页面配置
+function updatePageConfig() {
+    updateZTPageConfig(pageCode, pageParams.value)
+        .then(res => {
+            stompClient.subscribeToPage(pageCode, (data) => {
+                deviceDataGroup.VALVES = Object.values(data.VALVES)
+                deviceDataGroup.PUMPS = Object.values(data.PUMPS)
+                deviceDataGroup.SENSORS = Object.values(data.SENSORS)
+            });
+        })
+        .catch(err => {
+            console.log('页面配置失败:', err);
+        });
+}
+const pumpHelper = useValveHelper(computed(() => deviceDataGroup.PUMPS))
+const { getDataArrByCode } = useValveHelper(computed(() => deviceDataGroup.VALVES))
+const sensorHelper = useValveHelper(computed(() => deviceDataGroup.SENSORS))
+const {
+    evaluateCondition, getPipeType
+} = useValveHelper(computed(() => [...deviceDataGroup.VALVES, ...deviceDataGroup.PUMPS]))
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/styles/dcs/hnyzConfiguratePage.scss';
+</style>