Przeglądaj źródła

fix: 修改历史记录方案、调整快捷键

jiaxing.liao 2 tygodni temu
rodzic
commit
8bb211f1d8

+ 3 - 0
.vscode/settings.json

@@ -9,6 +9,9 @@
   "[json]": {
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
+  "[vue]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
   "i18n-ally.localesPaths": [
     "src/renderer/src/locales"
   ],

+ 94 - 0
src/renderer/src/hooks/useHistory.ts

@@ -0,0 +1,94 @@
+import { ref, computed } from 'vue'
+import { useDebouncedRefHistory, useManualRefHistory } from '@vueuse/core'
+import { klona } from 'klona'
+import { isEqual } from 'lodash'
+
+import type { IProject } from '@/store/modules/project'
+import type { BaseWidget } from '@/types/baseWidget'
+
+const DEBOUNCE_TIME = 300
+const MAX_HISTORY_LENGTH = 20
+
+export const useHistory = () => {
+  const project = ref<IProject>()
+  const activePageId = ref<string>()
+  const openPageIds = ref<string[]>([])
+  const activeWidgets = ref<BaseWidget[]>([])
+
+  const operationInfo = computed({
+    get: () => ({
+      activePageId: activePageId.value,
+      openPageIds: openPageIds.value,
+      activeWidgets: activeWidgets.value
+    }),
+    set: (value) => {
+      activePageId.value = value.activePageId
+      openPageIds.value = value.openPageIds
+      activeWidgets.value = value.activeWidgets
+    }
+  })
+
+  const operationHistory = useManualRefHistory(operationInfo, {
+    clone: klona,
+    capacity: MAX_HISTORY_LENGTH
+  })
+
+  // 历史记录
+  const {
+    history,
+    undo: _undo,
+    redo: _redo,
+    canRedo,
+    canUndo,
+    clear,
+    last,
+    reset
+  } = useDebouncedRefHistory(project, {
+    capacity: MAX_HISTORY_LENGTH,
+    clone: klona,
+    debounce: DEBOUNCE_TIME,
+    deep: true,
+    shouldCommit: (_, newValue) => {
+      const should = !isEqual(newValue, last.value?.snapshot)
+      if (should) {
+        operationHistory.commit()
+      }
+      return should
+    }
+  })
+
+  const undo = () => {
+    _undo()
+    operationHistory.undo()
+  }
+  const redo = () => {
+    _redo()
+    operationHistory.redo()
+  }
+
+  /**
+   * 初始化历史记录
+   */
+  const initHistory = async () => {
+    setTimeout(() => {
+      clear()
+      reset()
+      operationHistory.clear()
+    }, DEBOUNCE_TIME + 100)
+  }
+
+  return {
+    history,
+    undo,
+    redo,
+    canRedo,
+    canUndo,
+    clear,
+
+    initHistory,
+    project,
+    activePageId,
+    openPageIds,
+    activeWidgets
+  }
+}

+ 11 - 3
src/renderer/src/store/modules/action.ts

@@ -465,6 +465,8 @@ export const useActionStore = defineStore('action', () => {
         (e.target as HTMLElement)?.tagName === 'TEXTAREA'
       )
         return
+      // 阻止ctrl.shift.z同时触发
+      if (e.ctrlKey && e.shiftKey) return
       projectStore.undo()
     }, 100)
   )
@@ -485,7 +487,9 @@ export const useActionStore = defineStore('action', () => {
   // 键盘事件 锁定
   useKeyPress(
     ['ctrl.l'],
-    debounce(() => {
+    debounce((e: KeyboardEvent) => {
+      // 阻止ctrl.z同时触发
+      if (e.ctrlKey && e.shiftKey) return
       onLock(true)
     }, 100)
   )
@@ -499,14 +503,18 @@ export const useActionStore = defineStore('action', () => {
   // 键盘事件 上移
   useKeyPress(
     ['ctrl.up'],
-    debounce(() => {
+    debounce((e: KeyboardEvent) => {
+      // 阻止ctrl.z同时触发
+      if (e.ctrlKey && e.shiftKey) return
       onLevel('up')
     }, 100)
   )
   // 键盘事件 下移
   useKeyPress(
     ['ctrl.down'],
-    debounce(() => {
+    debounce((e: KeyboardEvent) => {
+      // 阻止ctrl.z同时触发
+      if (e.ctrlKey && e.shiftKey) return
       onLevel('down')
     }, 100)
   )

+ 50 - 30
src/renderer/src/store/modules/project.ts

@@ -14,7 +14,6 @@ import { computed, ref, watch } from 'vue'
 import { defineStore } from 'pinia'
 import { klona } from 'klona'
 import { createBin, createScreen } from '@/model'
-import { useDebouncedRefHistory } from '@vueuse/core'
 import { useRecentProject } from './recentProject'
 import { v4 } from 'uuid'
 import dayjs from 'dayjs'
@@ -22,6 +21,7 @@ import { ElMessage } from 'element-plus'
 import { useI18n } from 'vue-i18n'
 import { ComponentArray } from '@/lvgl-widgets'
 import { bfsWalk } from 'simple-mind-map/src/utils'
+import { useHistory } from '@/hooks/useHistory'
 
 export interface IProject {
   version: string
@@ -42,21 +42,25 @@ export interface IProject {
 }
 
 export const useProjectStore = defineStore('project', () => {
-  // 项目信息
-  const project = ref<IProject>()
+  const {
+    initHistory,
+    history,
+    undo,
+    redo,
+    canRedo,
+    canUndo,
+    clear,
+    project,
+    openPageIds,
+    activePageId,
+    activeWidgets
+  } = useHistory()
+
   // 项目文件夹
   const projectDirectory = ref<string>()
   // 全局样式
   const globalStyle = ref<any[]>()
 
-  // 历史记录
-  const { history, undo, redo, canRedo, canUndo, clear } = useDebouncedRefHistory(project, {
-    debounce: 300,
-    deep: true,
-    capacity: 20,
-    clone: klona
-  })
-
   const recentProjectStore = useRecentProject()
   const { t } = useI18n()
 
@@ -65,8 +69,6 @@ export const useProjectStore = defineStore('project', () => {
   // 当前最大化屏幕
   const currentMaxScreen = ref<string | null>(null)
 
-  // 激活页面ID
-  const activePageId = ref<string>()
   // 当前激活的页面
   const activePage = computed(() => {
     const pages = project.value?.screens.map((screen) => screen.pages)
@@ -85,11 +87,21 @@ export const useProjectStore = defineStore('project', () => {
 
     return `${project.value.meta.path}\\${projectDirectory.value}`
   })
-  // 打开页面 用以记录每个屏幕打开的页面
-  const openPages = ref<Page[]>([])
 
-  // 当前选中元素列表
-  const activeWidgets = ref<BaseWidget[]>([])
+  // 打开页面 用以记录每个屏幕打开的页面
+  const openPages = computed(() => {
+    const pages: Page[] = []
+    openPageIds.value.forEach((id) => {
+      project.value?.screens.forEach((screen) =>
+        screen.pages.find((page) => {
+          if (page.id === id) {
+            pages.push(page)
+          }
+        })
+      )
+    })
+    return pages
+  })
 
   // 当前激活的元素
   const activeWidget = computed(() => {
@@ -107,15 +119,18 @@ export const useProjectStore = defineStore('project', () => {
     () => project.value?.resources.fonts,
     (fonts?: FontResource[]) => {
       // 动态加载全部字体
-      const fontPromises = fonts?.map((font) => {
-        const fontFace = new FontFace(
-          font.fileName,
-          `url('local:///${projectPath.value + font.path}')`.replaceAll('\\', '/')
-        )
-        return fontFace.load()
-      })
+      // 判断字体是否已经加载
+      const fontPromises = fonts
+        ?.filter((font) => !document.fonts.check(font.fileName))
+        .map((font) => {
+          const fontFace = new FontFace(
+            font.fileName,
+            `url('local:///${projectPath.value + font.path}')`.replaceAll('\\', '/')
+          )
+          return fontFace.load()
+        })
 
-      if (fontPromises) {
+      if (fontPromises?.length) {
         Promise.all(fontPromises)
           .then((loadedFonts) => {
             console.log('已加载字体:', loadedFonts)
@@ -168,12 +183,12 @@ export const useProjectStore = defineStore('project', () => {
     // 2、构建屏幕信息
     activePageId.value = ''
     currentMaxScreen.value = null
-    openPages.value = []
+    openPageIds.value = []
     meta.screens.forEach((screen, index) => {
       const newScreen = createScreen(screen)
       newScreen.name += `_${index + 1}`
       project.value?.screens.push(newScreen)
-      openPages.value.push(newScreen.pages[0])
+      openPageIds.value.push(newScreen.pages[0].id)
     })
     activePageId.value = project.value.screens[0].pages[0].id
     // 3、创建BIN
@@ -230,7 +245,7 @@ export const useProjectStore = defineStore('project', () => {
 
     imageCompressFormat.value = meta.imageCompress
     // 清除记录
-    clear()
+    initHistory()
     recentProjectStore.addProject({
       id: v4(),
       projectName: meta.name,
@@ -261,7 +276,9 @@ export const useProjectStore = defineStore('project', () => {
     }
     // 初始化处理
     // projectPath.value = path
-    openPages.value = newProject.screens.map((screen) => screen.pages?.[0]).filter((item) => item)
+    openPageIds.value = newProject.screens
+      .map((screen) => screen.pages?.[0]?.id)
+      .filter((item) => item)
     activePageId.value = newProject.screens[0].pages?.[0]?.id
     imageCompressFormat.value = newProject.meta.imageCompress
     recentProjectStore.addProject({
@@ -271,7 +288,9 @@ export const useProjectStore = defineStore('project', () => {
       createTime: newProject.meta.createTime!,
       modifyTime: newProject.meta.modifyTime!
     })
-    clear()
+
+    initHistory()
+
     // 锁定项目文件夹,防止用户在资源管理器中删除正在使用的项目目录
     window.electron.ipcRenderer.invoke('lock-project-folder', path)
   }
@@ -378,6 +397,7 @@ export const useProjectStore = defineStore('project', () => {
     openLocalProject,
     saveProject,
     activeWidgets,
+    openPageIds,
     openPages,
     setSelectWidgets,
     activeWidgetMap,

+ 1 - 1
src/renderer/src/views/designer/sidebar/Hierarchy.vue

@@ -70,7 +70,7 @@ const handlePageNodeClick = (node: any) => {
   projectStore.project?.screens.forEach((screen, index) => {
     screen.pages.forEach((page) => {
       if (page.id === node.id) {
-        projectStore.openPages[index] = page
+        projectStore.openPageIds[index] = page.id
       }
     })
   })

+ 3 - 0
src/renderer/src/views/designer/workspace/stage/DesignerCanvas.vue

@@ -234,15 +234,18 @@ onMounted(() => {
   position: absolute;
   overflow: auto;
 }
+
 .stage {
   position: relative;
 }
+
 .tip-txt {
   position: absolute;
   font-size: 12px;
   line-height: 20px;
   color: #999;
 }
+
 .transparent-bg {
   transform: scale(1) !important;
   width: 100%;