Przeglądaj źródła

fix:监控网关查询

yanghao 2 dni temu
rodzic
commit
f5a2d9f841

+ 107 - 23
src/components/custom-components/sys-button-vue/index.vue

@@ -1,12 +1,12 @@
 <template>
-  <div style="width: 100%; height: 100%">
-    <button class="w-1/1 h-1/1" :style="getStyle(props.type, props.round)">
-      <el-text>{{ props.text }}</el-text>
+  <div class="sys-button-wrapper">
+    <button class="sys-button" :style="buttonStyle">
+      <span class="sys-button-text">{{ props.text }}</span>
     </button>
   </div>
 </template>
 <script setup lang="ts">
-import { ElText } from 'element-plus'
+import { computed } from 'vue'
 import type { PropType } from 'vue'
 type ButtonType = '' | 'default' | 'success' | 'warning' | 'info' | 'primary' | 'danger'
 const props = defineProps({
@@ -21,28 +21,112 @@ const props = defineProps({
   round: {
     type: Boolean,
     default: false
+  },
+  backgroundColor: {
+    type: String,
+    default: ''
+  },
+  borderColor: {
+    type: String,
+    default: ''
+  },
+  fontColor: {
+    type: String,
+    default: ''
+  },
+  fontFamily: {
+    type: String,
+    default: ''
+  },
+  fontSize: {
+    type: Number,
+    default: 14
+  },
+  borderRadius: {
+    type: Number,
+    default: 4
   }
 })
-const getStyle = (type: ButtonType, round: boolean) => {
-  let bg_color = ''
-  let border_radius = '4px'
-  if (type == 'primary') {
-    bg_color = '#409eff'
-  } else if (type == 'success') {
-    bg_color = '#67c23a'
-  } else if (type == 'warning') {
-    bg_color = '#e6a23c'
-  } else if (type == 'danger') {
-    bg_color = '#f56c6c'
-  } else if (type == 'info') {
-    bg_color = '#909399'
-  }
-  if (round) {
-    border_radius = '20px'
+
+const buttonTypeStyleMap: Record<
+  ButtonType,
+  { background: string; border: string; color: string }
+> = {
+  '': {
+    background: '#ffffff',
+    border: '#dcdfe6',
+    color: '#606266'
+  },
+  default: {
+    background: '#ffffff',
+    border: '#dcdfe6',
+    color: '#606266'
+  },
+  primary: {
+    background: '#409eff',
+    border: '#409eff',
+    color: '#ffffff'
+  },
+  success: {
+    background: '#67c23a',
+    border: '#67c23a',
+    color: '#ffffff'
+  },
+  warning: {
+    background: '#e6a23c',
+    border: '#e6a23c',
+    color: '#ffffff'
+  },
+  danger: {
+    background: '#f56c6c',
+    border: '#f56c6c',
+    color: '#ffffff'
+  },
+  info: {
+    background: '#909399',
+    border: '#909399',
+    color: '#ffffff'
   }
+}
+
+const buttonStyle = computed(() => {
+  const typeStyle = buttonTypeStyleMap[props.type] || buttonTypeStyleMap.default
+  const borderRadius = props.round ? 20 : props.borderRadius
   return {
-    backgroundColor: bg_color,
-    borderRadius: border_radius
+    backgroundColor: props.backgroundColor || typeStyle.background,
+    borderColor: props.borderColor || typeStyle.border,
+    color: props.fontColor || typeStyle.color,
+    fontFamily: props.fontFamily || undefined,
+    fontSize: `${props.fontSize}px`,
+    borderRadius: `${borderRadius}px`
   }
-}
+})
 </script>
+<style scoped>
+.sys-button-wrapper {
+  width: 100%;
+  height: 100%;
+}
+
+.sys-button {
+  width: 100%;
+  height: 100%;
+  padding: 0 12px;
+  overflow: hidden;
+  line-height: 1;
+  cursor: pointer;
+  border: 1px solid;
+  transition:
+    background-color 0.2s,
+    border-color 0.2s,
+    color 0.2s;
+}
+
+.sys-button-text {
+  display: block;
+  overflow: hidden;
+  color: inherit;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+</style>

+ 56 - 1
src/components/mt-edit/components/layout/right-aside/select-item-event-setting/index.vue

@@ -32,7 +32,10 @@
               </el-select>
             </el-form-item>
             <el-form-item label="事件行为" size="small">
-              <el-select placeholder="事件行为" v-model="event_list[event_list_index].action">
+              <el-select
+                placeholder="事件行为"
+                v-model="event_list[event_list_index].action"
+                @change="onEventActionChange(event_list_index)">
                 <el-option
                   v-for="item in event_action"
                   :key="item.value"
@@ -56,6 +59,20 @@
                 >点击编写</el-button
               >
             </el-form-item>
+            <el-form-item
+              v-else-if="event_list_item.action == 'openWebtopo'"
+              label="目标组态"
+              size="small">
+              <div class="webtopo-project-field">
+                <el-button @click="onWebtopoProjectClick(event_list_index)">选择组态</el-button>
+                <el-text
+                  truncated
+                  class="webtopo-project-name"
+                  :title="event_list_item.webtopo_project?.projectName">
+                  {{ event_list_item.webtopo_project?.projectName || '未选择' }}
+                </el-text>
+              </div>
+            </el-form-item>
             <el-form-item label="触发规则" size="small">
               <el-text>(不填直接触发)</el-text>
             </el-form-item>
@@ -198,6 +215,10 @@
           enableLiveAutocompletion: true
         }" />
     </el-dialog>
+    <webtopo-project-select-dialog
+      v-model:visible="webtopo_project_dialog_visible"
+      :model-value="event_list[select_event_index]?.webtopo_project"
+      @update:model-value="onWebtopoProjectSelected" />
   </div>
 </template>
 <script setup lang="ts">
@@ -224,11 +245,13 @@ import type {
   IDoneJson,
   IDoneJsonActionChangeAttr,
   IDoneJsonEventList,
+  IDoneJsonEventWebtopoProject,
   ILeftAsideConfigItemPublicPropsType
 } from '@/components/mt-edit/store/types'
 import InputTargetValue from './input-target-value.vue'
 import '@/components/mt-edit/ace-edit'
 import { VAceEditor } from 'vue3-ace-editor'
+import WebtopoProjectSelectDialog from './webtopo-project-select-dialog.vue'
 
 type SelectItemEventSettingProps = {
   doneJson: IDoneJson[]
@@ -277,6 +300,10 @@ const event_action: {
   {
     label: '自定义代码',
     value: 'customCode'
+  },
+  {
+    label: '打开组态',
+    value: 'openWebtopo'
   }
 ]
 const all_component_options = computed(() => {
@@ -296,6 +323,7 @@ const drawer_change_attr = ref<IDoneJsonActionChangeAttr[]>([]) // 属性更改
 const dialog_visiable = ref(false)
 const dialog_title = ref('自定义代码编写')
 const dialog_code = ref('')
+const webtopo_project_dialog_visible = ref(false)
 const onAddEvent = () => {
   event_list.value.push({
     id: randomString(),
@@ -303,6 +331,7 @@ const onAddEvent = () => {
     action: 'changeAttr',
     change_attr: [],
     custom_code: '',
+    webtopo_project: undefined,
     trigger_rule: {
       trigger_id: undefined,
       trigger_attr: undefined,
@@ -344,6 +373,18 @@ const onTargetAttrChange = (change_attr_index: number) => {
     drawer_change_attr.value[change_attr_index].target_value = undefined
   }
 }
+const onEventActionChange = (event_list_index: number) => {
+  if (event_list.value[event_list_index].action == 'openWebtopo') {
+    event_list.value[event_list_index].webtopo_project = {}
+  }
+}
+const onWebtopoProjectClick = (index: number) => {
+  select_event_index.value = index
+  webtopo_project_dialog_visible.value = true
+}
+const onWebtopoProjectSelected = (project: IDoneJsonEventWebtopoProject) => {
+  event_list.value[select_event_index.value].webtopo_project = project
+}
 /**
  * 根据id获取属性
  * @param id
@@ -475,3 +516,17 @@ watch(event_list.value, (val) => {
   selectItemEventSettingEmits('update:itemEvents', val)
 })
 </script>
+<style scoped>
+.webtopo-project-field {
+  display: flex;
+  width: 100%;
+  min-width: 0;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 8px;
+}
+
+.webtopo-project-name {
+  max-width: 100%;
+}
+</style>

+ 296 - 0
src/components/mt-edit/components/layout/right-aside/select-item-event-setting/webtopo-project-select-dialog.vue

@@ -0,0 +1,296 @@
+<template>
+  <el-dialog
+    v-model="visible"
+    title="选择组态"
+    width="980px"
+    class="webtopo-select-dialog"
+    destroy-on-close
+    :close-on-click-modal="false"
+    @open="loadList">
+    <div class="webtopo-select-toolbar">
+      <el-input
+        v-model="query.projectName"
+        clearable
+        class="!w-260px"
+        placeholder="请输入组态名称"
+        @keydown.enter.prevent="handleQuery()" />
+      <el-button type="primary" @click="handleQuery()">
+        <Icon icon="ep:search" class="mr-5px" /> 搜索
+      </el-button>
+      <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
+    </div>
+
+    <el-scrollbar v-loading="loading" height="520px" view-class="webtopo-select-list">
+      <el-empty v-if="!loading && !list.length" description="暂无组态项目" />
+      <div v-else class="webtopo-select-grid">
+        <button
+          v-for="item in list"
+          :key="item.id"
+          type="button"
+          :class="['webtopo-select-card', modelValue?.id === item.id ? 'is-selected' : '']"
+          @click="onSelect(item)">
+          <div class="webtopo-select-thumb">
+            <el-image v-if="item.thumbnail" :src="item.thumbnail" fit="cover" class="w-full h-full">
+              <template #error>
+                <div class="webtopo-select-thumb-empty">
+                  <Icon icon="ep:picture" />
+                  缩略图加载失败
+                </div>
+              </template>
+            </el-image>
+            <div v-else class="webtopo-select-thumb-empty">
+              <Icon icon="ep:picture" />
+              暂无缩略图
+            </div>
+            <div class="webtopo-select-device">
+              <Icon icon="ep:cpu" />
+              {{ getLinkedDeviceCount(item) }} 台设备
+            </div>
+          </div>
+          <div class="webtopo-select-content">
+            <div class="webtopo-select-title" :title="item.projectName">
+              {{ item.projectName || '-' }}
+            </div>
+            <div class="webtopo-select-time">
+              <Icon icon="ep:clock" />
+              {{ formatCreateTime(item.createTime) }}
+            </div>
+            <div class="webtopo-select-remark" :title="item.remark">
+              {{ item.remark || '暂无备注' }}
+            </div>
+          </div>
+        </button>
+      </div>
+    </el-scrollbar>
+
+    <div class="webtopo-select-footer">
+      <el-pagination
+        v-show="total > 0"
+        v-model:current-page="query.pageNo"
+        v-model:page-size="query.pageSize"
+        background
+        :page-sizes="[12, 20, 30, 50]"
+        :total="total"
+        layout="total, sizes, prev, pager, next"
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange" />
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import {
+  WebtopoProjectApi,
+  type WebtopoProjectPageReqVO,
+  type WebtopoProjectVO
+} from '@/api/pms/maotu'
+import type { IDoneJsonEventWebtopoProject } from '@/components/mt-edit/store/types'
+import { useUserStore } from '@/store/modules/user'
+import { useDebounceFn } from '@vueuse/core'
+import dayjs from 'dayjs'
+
+type WebtopoProjectSelectDialogProps = {
+  modelValue?: IDoneJsonEventWebtopoProject
+}
+
+const props = defineProps<WebtopoProjectSelectDialogProps>()
+const emit = defineEmits(['update:modelValue', 'select'])
+const visible = defineModel<boolean>('visible', { default: false })
+
+const deptId = useUserStore().getUser.deptId
+const loading = ref(false)
+const list = ref<WebtopoProjectVO[]>([])
+const total = ref(0)
+const initQuery: WebtopoProjectPageReqVO = {
+  pageNo: 1,
+  pageSize: 12,
+  deptId,
+  projectName: ''
+}
+const query = ref<WebtopoProjectPageReqVO>({ ...initQuery })
+const modelValue = computed(() => props.modelValue)
+
+const loadList = useDebounceFn(async () => {
+  loading.value = true
+  try {
+    const data = await WebtopoProjectApi.getWebtopoProjectPage(query.value)
+    if (Array.isArray(data)) {
+      list.value = data
+      total.value = data.length
+      return
+    }
+
+    list.value = data?.list || []
+    total.value = data?.total || 0
+  } finally {
+    loading.value = false
+  }
+}, 200)
+
+const getLinkedDeviceIds = (row: WebtopoProjectVO) => {
+  return row.linkedDeviceIds || row.linkedDevices?.map((item) => item.id) || []
+}
+
+const getLinkedDeviceCount = (row: WebtopoProjectVO) => {
+  return getLinkedDeviceIds(row).length
+}
+
+const formatCreateTime = (value?: number | string) => {
+  if (!value) return '-'
+  const time = typeof value === 'number' && value.toString().length === 10 ? value * 1000 : value
+  return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
+}
+
+const handleSizeChange = (val: number) => {
+  query.value.pageSize = val
+  handleQuery()
+}
+
+const handleCurrentChange = (val: number) => {
+  query.value.pageNo = val
+  loadList()
+}
+
+const handleQuery = (setPage = true) => {
+  if (setPage) {
+    query.value.pageNo = 1
+  }
+  loadList()
+}
+
+const resetQuery = () => {
+  query.value = { ...initQuery }
+  handleQuery()
+}
+
+const onSelect = (item: WebtopoProjectVO) => {
+  const targetProject = {
+    id: item.id,
+    projectName: item.projectName
+  }
+  emit('update:modelValue', targetProject)
+  emit('select', targetProject)
+  visible.value = false
+}
+</script>
+
+<style scoped>
+.webtopo-select-toolbar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.webtopo-select-list {
+  min-height: 360px;
+  padding: 2px 4px 8px;
+}
+
+.webtopo-select-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+  gap: 16px;
+}
+
+.webtopo-select-card {
+  padding: 0;
+  overflow: hidden;
+  text-align: left;
+  cursor: pointer;
+  background: var(--el-bg-color);
+  border: 1px solid var(--el-border-color-lighter);
+  border-radius: 8px;
+  transition:
+    border-color 0.2s,
+    box-shadow 0.2s,
+    transform 0.2s;
+}
+
+.webtopo-select-card:hover,
+.webtopo-select-card.is-selected {
+  border-color: var(--el-color-primary);
+  transform: translateY(-2px);
+  box-shadow: 0 10px 24px rgb(15 23 42 / 12%);
+}
+
+.webtopo-select-thumb {
+  position: relative;
+  height: 146px;
+  overflow: hidden;
+  background: var(--el-fill-color-light);
+}
+
+.webtopo-select-thumb-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  gap: 8px;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  background: linear-gradient(135deg, var(--el-fill-color-blank), var(--el-fill-color-light));
+}
+
+.webtopo-select-thumb-empty .iconify {
+  font-size: 30px;
+  opacity: 0.65;
+}
+
+.webtopo-select-device {
+  position: absolute;
+  top: 10px;
+  left: 10px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 4px 10px;
+  font-size: 12px;
+  color: #fff;
+  background: rgb(0 0 0 / 45%);
+  border-radius: 999px;
+  backdrop-filter: blur(8px);
+}
+
+.webtopo-select-content {
+  padding: 14px;
+}
+
+.webtopo-select-title {
+  overflow: hidden;
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--el-text-color-primary);
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.webtopo-select-time {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  margin-top: 10px;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+}
+
+.webtopo-select-remark {
+  display: -webkit-box;
+  height: 40px;
+  margin-top: 8px;
+  overflow: hidden;
+  font-size: 12px;
+  line-height: 20px;
+  color: var(--el-text-color-placeholder);
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+}
+
+.webtopo-select-footer {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 16px;
+}
+</style>

+ 78 - 3
src/components/mt-edit/store/config.ts

@@ -635,10 +635,85 @@ const sysComponentItems: ILeftAsideConfigItem[] = [
           }
         ]
       },
-      round: {
+      backgroundColor: {
+        title: '背景色',
+        type: 'color',
+        val: ''
+      },
+      borderColor: {
+        title: '边框颜色',
+        type: 'color',
+        val: ''
+      },
+      fontColor: {
+        title: '文字颜色',
+        type: 'color',
+        val: ''
+      },
+      fontFamily: {
+        title: '字体',
+        type: 'select',
+        val: '',
+        options: [
+          {
+            value: '',
+            label: '默认'
+          },
+          {
+            value: 'Microsoft YaHei, PingFang SC, sans-serif',
+            label: '微软雅黑'
+          },
+          {
+            value: 'SimSun, Songti SC, serif',
+            label: '宋体'
+          },
+          {
+            value: 'SimHei, Heiti SC, sans-serif',
+            label: '黑体'
+          },
+          {
+            value: 'PingFang SC, Microsoft YaHei, sans-serif',
+            label: '苹方'
+          },
+          {
+            value: 'Noto Sans SC, Microsoft YaHei, sans-serif',
+            label: '思源黑体'
+          },
+          {
+            value: 'YouSheBiaoTiHei, Microsoft YaHei, sans-serif',
+            label: '优设标题黑'
+          },
+          {
+            value: 'KaiTi, Kaiti SC, serif',
+            label: '楷体'
+          },
+          {
+            value: 'FangSong, STFangsong, serif',
+            label: '仿宋'
+          },
+          {
+            value: 'Arial, Helvetica, sans-serif',
+            label: 'Arial'
+          },
+          {
+            value: 'DIN Alternate, Arial Narrow, Arial, sans-serif',
+            label: 'DIN 数字'
+          },
+          {
+            value: 'Consolas, Monaco, monospace',
+            label: '等宽字体'
+          }
+        ]
+      },
+      fontSize: {
+        title: '文字大小',
+        type: 'number',
+        val: 14
+      },
+      borderRadius: {
         title: '圆角',
-        type: 'switch',
-        val: false
+        type: 'number',
+        val: 4
       }
     },
     common_animations: {

+ 6 - 1
src/components/mt-edit/store/types.ts

@@ -90,19 +90,24 @@ export interface IGlobalStoreGridCfg {
   size: number
 }
 export type DoneJsonEventListType = 'click' | 'dblclick' | 'mouseover' | 'mouseout'
-export type DoneJsonEventListAction = 'changeAttr' | 'customCode'
+export type DoneJsonEventListAction = 'changeAttr' | 'customCode' | 'openWebtopo'
 export interface IDoneJsonActionChangeAttr {
   id: string
   target_id: string
   target_attr: string | undefined
   target_value: any
 }
+export interface IDoneJsonEventWebtopoProject {
+  id?: number
+  projectName?: string
+}
 export interface IDoneJsonEventList {
   id: string
   type: DoneJsonEventListType // 事件类型
   action: DoneJsonEventListAction // 事件行为
   change_attr: IDoneJsonActionChangeAttr[] //属性更改
   custom_code: string
+  webtopo_project?: IDoneJsonEventWebtopoProject
   trigger_rule: {
     trigger_id?: string //触发图形的id
     trigger_attr?: string //触发图形的属性

+ 19 - 14
src/components/mt-edit/utils/index.ts

@@ -835,6 +835,12 @@ export const eventToVOn = (item: IDoneJson) => {
   const event_obj: Record<string, string> = {}
   item.events.forEach((event) => {
     let code_str = ''
+    const has_trigger_rule =
+      event.trigger_rule &&
+      event.trigger_rule.trigger_id &&
+      event.trigger_rule.trigger_attr &&
+      event.trigger_rule.value !== undefined &&
+      event.trigger_rule.operator
     if (event.action === 'changeAttr') {
       if (event.change_attr.length < 1) {
         return
@@ -843,13 +849,7 @@ export const eventToVOn = (item: IDoneJson) => {
         if (!attr.target_id || !attr.target_attr || attr.target_value === undefined) {
           return
         }
-        if (
-          !event.trigger_rule ||
-          !event.trigger_rule.trigger_id ||
-          !event.trigger_rule.trigger_attr ||
-          event.trigger_rule.value === undefined ||
-          !event.trigger_rule.operator
-        ) {
+        if (!has_trigger_rule) {
           if (typeof attr.target_value == 'boolean') {
             code_str += `$setItemAttrByID('${attr.target_id}', '${attr.target_attr}', ${attr.target_value});`
           } else {
@@ -864,17 +864,22 @@ export const eventToVOn = (item: IDoneJson) => {
         }
       })
     } else if (event.action === 'customCode') {
-      if (
-        !event.trigger_rule ||
-        !event.trigger_rule.trigger_id ||
-        !event.trigger_rule.trigger_attr ||
-        event.trigger_rule.value === undefined ||
-        !event.trigger_rule.operator
-      ) {
+      if (!has_trigger_rule) {
         code_str += event.custom_code + ';'
       } else {
         code_str += `if($previewCompareVal($getItemAttrByID('${event.trigger_rule.trigger_id}', '${event.trigger_rule.trigger_attr}'), '${event.trigger_rule.operator}', '${event.trigger_rule.value}')){${event.custom_code}};`
       }
+    } else if (event.action === 'openWebtopo') {
+      const target_project_id = event.webtopo_project?.id
+      if (!target_project_id) {
+        return
+      }
+      const callback_code = `$mtEventCallBack('openWebtopo', '${item.id}', ${target_project_id});`
+      if (!has_trigger_rule) {
+        code_str += callback_code
+      } else {
+        code_str += `if($previewCompareVal($getItemAttrByID('${event.trigger_rule.trigger_id}', '${event.trigger_rule.trigger_attr}'), '${event.trigger_rule.operator}', '${event.trigger_rule.value}')){${callback_code}};`
+      }
     }
     if (!Object.prototype.hasOwnProperty.call(event_obj, event.type)) {
       event_obj[event.type] = code_str

+ 4 - 2
src/views/maotu/index.vue

@@ -224,11 +224,13 @@ function getLinkedDeviceCount(row: WebtopoProjectVO) {
 }
 
 function handleEdit(row: WebtopoProjectVO) {
-  router.push(`/maotu/edit/${row.id}`)
+  const routeInfo = router.resolve(`/maotu/edit/${row.id}`)
+  window.open(routeInfo.href, '_blank')
 }
 
 function handlePreview(row: WebtopoProjectVO) {
-  router.push(`/maotu/preview/${row.id}`)
+  const routeInfo = router.resolve(`/maotu/preview/${row.id}`)
+  window.open(routeInfo.href, '_blank')
 }
 
 function handleConfig(row: WebtopoProjectVO) {

+ 15 - 3
src/views/maotu/preview.vue

@@ -7,10 +7,11 @@ import { IotDeviceApi } from '@/api/pms/device'
 import type { IExportJson } from '@/components/mt-edit/components/types'
 import { MtPreview } from '@/export'
 import { ElMessage } from 'element-plus'
-import { nextTick, onMounted, onUnmounted, ref } from 'vue'
-import { useRoute } from 'vue-router'
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
 
 const route = useRoute()
+const router = useRouter()
 const MtPreviewRef = ref<InstanceType<typeof MtPreview>>()
 const previewData = ref<IExportJson>()
 const pollingTimer = ref<number>()
@@ -120,6 +121,7 @@ const stopDeviceBindPolling = () => {
 }
 
 const loadProject = async () => {
+  stopDeviceBindPolling()
   const id = getProjectId()
   if (!id) {
     const exportJson = sessionStorage.getItem('exportJson')
@@ -141,11 +143,14 @@ const loadProject = async () => {
   }
 }
 
-const onEventCallBack = (type: string, item_id: string) => {
+const onEventCallBack = (type: string, item_id: string, targetProjectId?: number) => {
   console.log(type, item_id)
 
   if (type == 'test-dialog') {
     ElMessage.success(`获取到了id:${item_id}`)
+  } else if (type == 'openWebtopo' && targetProjectId) {
+    const routeInfo = router.resolve(`/maotu/preview/${targetProjectId}`)
+    window.open(routeInfo.href, '_blank')
   }
 }
 
@@ -153,6 +158,13 @@ onMounted(() => {
   loadProject()
 })
 
+watch(
+  () => route.params.id,
+  () => {
+    loadProject()
+  }
+)
+
 onUnmounted(() => {
   stopDeviceBindPolling()
 })