Przeglądaj źródła

feat(components): 添加2D可视化编辑器及预览组件

wangcong 3 tygodni temu
rodzic
commit
6b319198f3

+ 124 - 0
src/components/visual-2d/Visual2DEditor.vue

@@ -0,0 +1,124 @@
+<script setup lang="ts">
+import { computed, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
+import qs from 'qs';
+
+import { useViewVisible } from '@/hooks/view-visible';
+
+import { getVisual2DMsgType, visual2DEditorPageUrl, Visual2DMsgType } from '.';
+
+import type { AllDevicesList, GroupModuleInfo, IframeMsg } from '@/types';
+
+interface Props {
+  info: GroupModuleInfo;
+  deviceList?: AllDevicesList[];
+}
+
+const props = defineProps<Props>();
+
+const { visible, showView, hideView } = useViewVisible();
+const iframeRef = useTemplateRef('editorIframe');
+const isIframeLoaded = ref(false);
+
+const iframeUrl = computed(() => {
+  const { leId, moduleType } = props.info;
+
+  return `${visual2DEditorPageUrl}?${qs.stringify({
+    id: leId,
+    moduleType,
+  })}`;
+});
+
+onMounted(() => {
+  window.addEventListener('message', handleIframeMsg);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('message', handleIframeMsg);
+});
+
+watch(visible, () => {
+  if (visible.value && isIframeLoaded.value) {
+    // 刷新画布
+  }
+});
+
+const handleIframeMsg = (e: MessageEvent<IframeMsg>) => {
+  const { msgType } = e.data;
+
+  if (msgType === getVisual2DMsgType(Visual2DMsgType.EditLoaded)) {
+    isIframeLoaded.value = true;
+    sendDeviceData();
+  } else if (msgType === getVisual2DMsgType(Visual2DMsgType.CloseEditor)) {
+    hideView();
+  }
+};
+
+const sendDeviceData = () => {
+  if (props.deviceList) {
+    const msg: IframeMsg = {
+      msgType: getVisual2DMsgType(Visual2DMsgType.SendDeviceData),
+      ...JSON.parse(JSON.stringify(props.deviceList)),
+    };
+
+    iframeRef.value?.contentWindow?.postMessage(msg, '*');
+  }
+};
+
+defineExpose({
+  showView,
+  hideView,
+});
+</script>
+
+<template>
+  <AModal
+    v-model:open="visible"
+    wrap-class-name="visual-2d-editor-modal"
+    width="100%"
+    centered
+    :closable="false"
+    :mask-closable="false"
+    :footer="null"
+  >
+    <iframe ref="editorIframe" :src="iframeUrl"></iframe>
+    <ASpin v-if="!isIframeLoaded" class="center-loading visual-2d-editor-loading" :spinning="true" />
+  </AModal>
+</template>
+
+<style lang="scss">
+.visual-2d-editor-modal {
+  padding: 32px;
+
+  .ant-modal {
+    max-width: 1700px;
+  }
+
+  .ant-modal,
+  .ant-modal > div,
+  .ant-modal-content {
+    height: 812px;
+  }
+
+  .ant-modal-content {
+    padding: 0;
+  }
+
+  .ant-modal-body,
+  iframe {
+    width: 100%;
+    height: 100%;
+  }
+
+  iframe {
+    overflow: hidden;
+    background: transparent;
+    border: none;
+    border-radius: 8px;
+  }
+
+  .visual-2d-editor-loading {
+    background-color: #fff;
+    border-radius: 8px;
+  }
+}
+</style>

+ 92 - 0
src/components/visual-2d/Visual2DPreview.vue

@@ -0,0 +1,92 @@
+<script setup lang="ts">
+import { computed, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
+import qs from 'qs';
+
+import { getVisual2DMsgType, Visual2DMsgType, visual2DPreviewPageUrl } from '.';
+
+import type { AllDevicesList, GroupModuleInfo, IframeMsg } from '@/types';
+
+interface Props {
+  info: GroupModuleInfo;
+  deviceList?: AllDevicesList[];
+}
+
+const props = defineProps<Props>();
+
+const emit = defineEmits<{
+  click: [];
+}>();
+
+const iframeRef = useTemplateRef('previewIframe');
+const isIframeLoaded = ref(false);
+
+const iframeUrl = computed(() => {
+  const { leId, moduleType } = props.info;
+
+  return `${visual2DPreviewPageUrl}?${qs.stringify({
+    id: leId,
+    moduleType,
+  })}`;
+});
+
+onMounted(() => {
+  window.addEventListener('message', handleIframeMsg);
+});
+
+onUnmounted(() => {
+  window.removeEventListener('message', handleIframeMsg);
+});
+
+watch(
+  () => props.deviceList,
+  () => {
+    if (isIframeLoaded.value) {
+      sendDeviceData();
+    }
+  },
+  { deep: true },
+);
+
+const handleIframeMsg = (e: MessageEvent<IframeMsg>) => {
+  const { msgType } = e.data;
+
+  if (msgType === getVisual2DMsgType(Visual2DMsgType.PreviewLoaded)) {
+    isIframeLoaded.value = true;
+    sendDeviceData();
+  } else if (msgType === getVisual2DMsgType(Visual2DMsgType.PreviewClicked)) {
+    emit('click');
+  }
+};
+
+const sendDeviceData = () => {
+  if (props.deviceList) {
+    const msg: IframeMsg = {
+      msgType: getVisual2DMsgType(Visual2DMsgType.SendDeviceData),
+      ...JSON.parse(JSON.stringify(props.deviceList)),
+    };
+
+    iframeRef.value?.contentWindow?.postMessage(msg, '*');
+  }
+};
+</script>
+
+<template>
+  <iframe class="visual-2d-preview" ref="previewIframe" :src="iframeUrl"></iframe>
+  <ASpin v-if="!isIframeLoaded" class="center-loading visual-2d-preview-loading" :spinning="true" />
+</template>
+
+<style lang="scss" scoped>
+.visual-2d-preview {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  background: transparent;
+  border: none;
+  border-radius: 12px;
+}
+
+.visual-2d-preview-loading {
+  background-color: inherit;
+  border-radius: inherit;
+}
+</style>

+ 23 - 0
src/components/visual-2d/index.ts

@@ -0,0 +1,23 @@
+export const visual2DEditorPageUrl = import.meta.env.VITE_VIEW_URL;
+
+export const visual2DPreviewPageUrl = `${import.meta.env.VITE_VIEW_URL}/preview`;
+
+/**
+ * 实时监测和大屏等模块与其可视化 Iframe 内嵌网页通信的消息前缀
+ */
+export const visual2DMsgPrefix = 'hvac-visual-2d';
+
+/**
+ * 实时监测和大屏模块与其可视化 Iframe 内嵌网页通信的消息类型
+ */
+export const enum Visual2DMsgType {
+  EditLoaded = 'edit-loaded',
+  CloseEditor = 'close-editor',
+  SendDeviceData = 'send-device-data',
+  PreviewLoaded = 'preview-loaded',
+  PreviewClicked = 'preview-clicked',
+}
+
+export const getVisual2DMsgType = (type: Visual2DMsgType) => {
+  return visual2DMsgPrefix + '-' + type;
+};