Prechádzať zdrojové kódy

!85 feat: 新增JSON编辑页面 以及 保存跨窗口同步视图更新
Merge pull request !85 from 潘潘/dev

奔跑的面条 3 rokov pred
rodič
commit
179fedf622

+ 8 - 0
src/enums/editPageEnum.ts

@@ -9,6 +9,12 @@ export enum DragKeyEnum {
   DRAG_KEY = 'ChartData'
 }
 
+// 不同页面保存操作
+export enum SavePageEnum {
+  CHART = 'SaveChart',
+  JSON = 'SaveJSON'
+}
+
 // 操作枚举
 export enum MenuEnum {
   // 移动
@@ -42,6 +48,8 @@ export enum MenuEnum {
   BACK = 'back',
   // 前进
   FORWORD = 'forward',
+  // 保存
+  SAVE = 'save',
   // 锁定
   LOCK = 'lock',
   // 解除锁定

+ 6 - 0
src/enums/pageEnum.ts

@@ -12,6 +12,12 @@ export enum PreviewEnum {
   CHART_PREVIEW_NAME = 'ChartPreview',
 }
 
+export enum EditEnum {
+  //  图表JSON编辑
+  CHART_EDIT = '/chart/edit/:id(.*)*',
+  CHART_EDIT_NAME = 'ChartEdit',
+}
+
 export enum PageEnum {
   // 登录
   BASE_LOGIN = '/login',

+ 2 - 1
src/router/index.ts

@@ -21,7 +21,8 @@ const RootRoute: Array<RouteRecordRaw> = [
       ...HttpErrorPage,
       modules.projectRoutes,
       modules.chartRoutes,
-      modules.previewRoutes
+      modules.previewRoutes,
+      modules.editRoutes
     ]
   }
 ]

+ 20 - 0
src/router/modules/edit.route.ts

@@ -0,0 +1,20 @@
+import { RouteRecordRaw } from 'vue-router'
+import { EditEnum } from '@/enums/pageEnum'
+
+// 引入路径
+const importPath = {
+  [EditEnum.CHART_EDIT_NAME]: () => import('@/views/edit/index.vue')
+}
+
+const chartRoutes: RouteRecordRaw = {
+  path: EditEnum.CHART_EDIT,
+  name: EditEnum.CHART_EDIT_NAME,
+  component: importPath[EditEnum.CHART_EDIT_NAME],
+  meta: {
+    title: '编辑',
+    isRoot: true
+  }
+}
+
+
+export default chartRoutes

+ 3 - 1
src/router/modules/index.ts

@@ -1,9 +1,11 @@
 import projectRoutes from './project.router'
 import chartRoutes from './chart.route'
 import previewRoutes from './preview.route'
+import editRoutes from './edit.route'
 
 export default {
   projectRoutes,
   chartRoutes,
-  previewRoutes
+  previewRoutes,
+  editRoutes
 }

+ 1 - 1
src/router/modules/preview.route.ts

@@ -3,7 +3,7 @@ import { PreviewEnum } from '@/enums/pageEnum'
 
 // 引入路径
 const importPath = {
-  'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/index.vue')
+  'PreviewEnum.CHART_PREVIEW_NAME': () => import('@/views/preview/wrapper.vue')
 }
 
 const chartRoutes: RouteRecordRaw = {

+ 99 - 2
src/views/chart/ContentEdit/components/EditTools/index.vue

@@ -58,17 +58,107 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, h } from 'vue'
+import { ref, computed, h, watch } from 'vue'
 import { useSettingStore } from '@/store/modules/settingStore/settingStore'
 import { ToolsStatusEnum } from '@/store/modules/settingStore/settingStore.d'
+import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
+import { fetchPathByName, routerTurnByPath, setSessionStorage, getLocalStorage } from '@/utils'
+import { EditEnum, ChartEnum } from '@/enums/pageEnum'
+import { StorageEnum } from '@/enums/storageEnum'
+import { useRoute } from 'vue-router'
+import { useSync } from '@/views/chart/hooks/useSync.hook'
+import { SavePageEnum } from '@/enums/editPageEnum'
 import { GoSystemSet } from '@/components/GoSystemSet/index'
 import { exportHandle } from './utils'
 import { useFile } from './hooks/useFile.hooks'
 import { BtnListType, TypeEnum } from './index.d'
 import { icon } from '@/plugins'
 
-const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon } = icon.ionicons5
+const { DownloadIcon, ShareIcon, PawIcon, SettingsSharpIcon, CodeSlashIcon } = icon.ionicons5
 const settingStore = useSettingStore()
+const chartEditStore = useChartEditStore()
+const routerParamsInfo = useRoute()
+const { updateComponent } = useSync()
+
+// 编辑
+const editHandle = () => {
+  // 获取id路径
+  const path = fetchPathByName(EditEnum.CHART_EDIT_NAME, 'href')
+  if (!path) return
+  let { id } = routerParamsInfo.params as any
+  id = typeof id === 'string' ? id : id[0]
+  updateToSession(id)
+  routerTurnByPath(path, [id], undefined, true)
+}
+
+// 把内存中的数据同步到SessionStorage 便于传递给新窗口初始化数据
+function updateToSession(id: string) {
+  const storageInfo = chartEditStore.getStorageInfo
+  const sessionStorageInfo = getLocalStorage(StorageEnum.GO_CHART_STORAGE_LIST) || []
+
+  if (sessionStorageInfo?.length) {
+    const repeateIndex = sessionStorageInfo.findIndex((e: { id: string }) => e.id === id)
+    // 重复替换
+    if (repeateIndex !== -1) {
+      sessionStorageInfo.splice(repeateIndex, 1, { ...storageInfo, id })
+      setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
+    } else {
+      sessionStorageInfo.push({ ...storageInfo, id})
+      setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, sessionStorageInfo)
+    }
+  } else {
+    setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...storageInfo, id }])
+  }
+}
+
+function useSyncUpdate() {
+  // 定义侦听器变量
+  let timer: any;
+  const updateFn = (e: any) => updateComponent(e!.detail, true, false)
+  const syncData = () => {
+    if (routerParamsInfo.name == ChartEnum.CHART_HOME_NAME) {
+      dispatchEvent(new CustomEvent(SavePageEnum.CHART, { detail: chartEditStore.getStorageInfo }))
+    }
+  }
+  
+  // 开启侦听
+  const use = () => {
+    // 1、定时同步数据
+    timer = setInterval(() => {
+      // 窗口激活并且处于工作台
+      document.hasFocus() && syncData()
+    }, 5000)
+    // 2、失焦同步数据
+    addEventListener('blur', syncData)
+
+    // 【监听JSON代码 刷新工作台图表】
+    addEventListener(SavePageEnum.JSON, updateFn)
+  }
+
+  // 关闭侦听
+  const unUse = () => {
+    clearInterval(timer)
+    removeEventListener(SavePageEnum.JSON, updateFn)
+    removeEventListener('blur', syncData)
+  }
+
+  // 路由变更时处理
+  const watchHandler = (toName: any, fromName: any) => {
+    if (fromName == ChartEnum.CHART_HOME_NAME) {
+      unUse()
+    }
+    if (toName == ChartEnum.CHART_HOME_NAME) {
+      use()
+    }
+  }
+  return watchHandler
+}
+
+watch(
+  () => routerParamsInfo.name,
+  useSyncUpdate(),
+  { immediate: true }
+)
 
 // 鼠标悬停定时器
 let mouseTime: any = null
@@ -95,6 +185,13 @@ const btnList: BtnListType[] = [
     name: '导入',
     icon: DownloadIcon
   },
+  {
+    key: 'edit',
+    type: TypeEnum.BUTTON,
+    name: '编辑JSON',
+    icon: CodeSlashIcon,
+    handle: editHandle
+  },
   {
     key: 'setting',
     type: TypeEnum.BUTTON,

+ 133 - 0
src/views/edit/index.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="go-edit">
+    <n-layout>
+      <n-layout-header class="go-edit-header go-px-5 go-flex-items-center" bordered>
+        <div>
+          <n-text class="go-edit-title go-mr-4">页面在线编辑器</n-text>
+          <n-button v-if="showOpenFilePicker" class="go-mr-3" size="medium" @click="importJSON">
+            <template #icon>
+              <n-icon>
+                <download-icon></download-icon>
+              </n-icon>
+            </template>
+            导入
+          </n-button>
+          <n-button class="go-mr-4" size="medium" @click="back">
+            <template #icon>
+              <n-icon>
+                <chevron-back-outline-icon></chevron-back-outline-icon>
+              </n-icon>
+            </template>
+            返回工作台
+          </n-button>
+        </div>
+        <n-tag :bordered="false" type="warning"> 「按 ctrl + s 保存/更新」 </n-tag>
+      </n-layout-header>
+      <n-layout-content>
+        <monaco-editor
+          v-model:modelValue="content"
+          language="json"
+          :editorOptions="{
+            lineNumbers: 'on',
+            minimap: { enabled: true }
+          }"
+      />
+      </n-layout-content>
+    </n-layout>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, watch } from 'vue'
+import { MonacoEditor } from '@/components/Pages/MonacoEditor'
+import { SavePageEnum } from '@/enums/editPageEnum'
+import { getSessionStorageInfo } from '../preview/utils'
+import type { ChartEditStorageType } from '../preview/index.d'
+import { setSessionStorage } from '@/utils'
+import { StorageEnum } from '@/enums/storageEnum'
+import { icon } from '@/plugins'
+
+const { ChevronBackOutlineIcon, DownloadIcon } = icon.ionicons5
+const showOpenFilePicker: Function = (window as any).showOpenFilePicker
+let content = ref('')
+
+// 从sessionStorage 获取数据
+function getDataBySession() {
+  const localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
+  content.value = JSON.stringify(localStorageInfo, undefined, 2)
+}
+getDataBySession()
+
+// 返回父窗口
+function back() {
+  opener.name = Date.now()
+  window.open(opener.location.href, opener.name)
+}
+
+// 导入json文本
+async function importJSON() {
+  const files = await showOpenFilePicker()
+  const file = await files[0].getFile()
+  const fr = new FileReader()
+  fr.readAsText(file)
+  fr.onloadend = () => {
+    content.value = (fr.result || '').toString()
+  }
+}
+
+// 同步 [画布页失去焦点时同步数据到JSON页,JSON页Ctrl+S 时同步数据到画布页]
+opener.addEventListener(SavePageEnum.CHART, (e: any) => {
+  setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [e.detail])
+  content.value = JSON.stringify(e.detail, undefined, 2)
+})
+
+// 窗口失焦 + 保存 => 同步数据
+document.addEventListener('keydown', function (e) {
+  if (e.keyCode == 83 && (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)) {
+    e.preventDefault()
+    updateSync()
+  }
+})
+addEventListener('blur', updateSync)
+
+// 同步更新
+function updateSync() {
+  if (!opener) {
+    return window['$message'].error('源窗口已关闭,视图同步失败')
+  }
+  try {
+    const detail = JSON.parse(content.value)
+    delete detail.id
+    // 保持id不变
+    opener.dispatchEvent(new CustomEvent(SavePageEnum.JSON, { detail }))
+  } catch (e) {
+    window['$message'].error('内容格式有误')
+    console.log(e)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.go-edit {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  .go-edit-header {
+    display: flex;
+    align-items: center;
+    height: 60px;
+    justify-content: space-between;
+    .go-edit-title {
+      position: relative;
+      bottom: 3px;
+      font-size: 18px;
+      font-weight: bold;
+    }
+  }
+  @include deep() {
+    .go-editor-area {
+      height: calc(100vh - 60px) !important;
+    }
+  }
+}
+</style>

+ 25 - 0
src/views/preview/wrapper.vue

@@ -0,0 +1,25 @@
+<template>
+  <Preview :key="key"></Preview>
+</template>
+
+<script setup lang="ts">
+import { getSessionStorageInfo } from './utils'
+import type { ChartEditStorageType } from './index.d'
+import { SavePageEnum } from '@/enums/editPageEnum'
+import { setSessionStorage } from '@/utils'
+import { StorageEnum } from '@/enums/storageEnum'
+import { ref } from 'vue'
+import Preview from './index.vue'
+
+let key = ref(Date.now())
+let localStorageInfo: ChartEditStorageType = getSessionStorageInfo() as ChartEditStorageType
+
+// 数据变更 -> 同步sessionStorage -> reload页面 (重新执行Mounted)
+;[SavePageEnum.JSON, SavePageEnum.CHART].forEach((saveEvent: string) => {
+  opener.addEventListener(saveEvent, (e: any) => {
+    setSessionStorage(StorageEnum.GO_CHART_STORAGE_LIST, [{ ...e.detail, id: localStorageInfo.id }])
+    key.value = Date.now()
+  })
+})
+
+</script>