Browse Source

pref: 优化语言、控件树、快捷键等

jiaxing.liao 2 weeks ago
parent
commit
5eeafc28b3

+ 2 - 0
src/renderer/components.d.ts

@@ -70,6 +70,7 @@ declare module 'vue' {
     ElTextarea: typeof import('element-plus/es')['ElTextarea']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
     ElTree: typeof import('element-plus/es')['ElTree']
+    ElTreeV2: typeof import('element-plus/es')['ElTreeV2']
     IconButton: typeof import('./src/components/IconButton/index.vue')['default']
     LocalImage: typeof import('./src/components/LocalImage/index.vue')['default']
     MonacoEditor: typeof import('./src/components/MonacoEditor/index.vue')['default']
@@ -145,6 +146,7 @@ declare global {
   const ElTextarea: typeof import('element-plus/es')['ElTextarea']
   const ElTooltip: typeof import('element-plus/es')['ElTooltip']
   const ElTree: typeof import('element-plus/es')['ElTree']
+  const ElTreeV2: typeof import('element-plus/es')['ElTreeV2']
   const IconButton: typeof import('./src/components/IconButton/index.vue')['default']
   const LocalImage: typeof import('./src/components/LocalImage/index.vue')['default']
   const MonacoEditor: typeof import('./src/components/MonacoEditor/index.vue')['default']

+ 2 - 0
src/renderer/src/lvgl-widgets/image-button/index.ts

@@ -25,6 +25,8 @@ export default {
       width: 90,
       height: 45,
       text: '',
+      addFlags: [],
+      removeFlags: [],
       image: '',
       pressedImage: '',
       selectedImage: '',

+ 105 - 53
src/renderer/src/store/modules/action.ts

@@ -9,6 +9,7 @@ import { v4 } from 'uuid'
 import { useKeyPress } from 'vue-hooks-plus'
 import { useProjectStore } from '@/store/modules/project'
 import { useAppStore } from './app'
+import { debounce } from 'lodash-es'
 
 import type { BaseWidget } from '@/types/baseWidget'
 
@@ -240,9 +241,12 @@ export const useActionStore = defineStore('action', () => {
     projectStore.project?.screens.forEach((screen) => {
       screen.pages.forEach((page) => {
         bfsWalk(page, (child) => {
-          const index = child?.children?.findIndex((item) => widgetIds.includes(item.id)) ?? -1
-          if (index !== -1) {
-            child.children.splice(index, 1)
+          const indexs =
+            child?.children?.map((item, index) => {
+              if (widgetIds.includes(item.id)) return index
+            }) ?? []
+          if (indexs.length) {
+            child.children = child.children.filter((_item, index) => !indexs.includes(index))
           }
         })
       })
@@ -368,71 +372,119 @@ export const useActionStore = defineStore('action', () => {
   }
 
   // 键盘事件 复制
-  useKeyPress(['ctrl.c'], () => {
-    onCopy()
-  })
+  useKeyPress(
+    ['ctrl.c'],
+    debounce(() => {
+      onCopy()
+    }, 100)
+  )
   // 键盘事件 粘贴
-  useKeyPress(['ctrl.v'], () => {
-    onPaste()
-  })
+  useKeyPress(
+    ['ctrl.v'],
+    debounce(() => {
+      onPaste()
+    }, 100)
+  )
   // 键盘事件 剪切
-  useKeyPress(['ctrl.x'], () => {
-    onCut()
-  })
+  useKeyPress(
+    ['ctrl.x'],
+    debounce(() => {
+      onCut()
+    }, 100)
+  )
   // 键盘事件 复用
-  useKeyPress(['ctrl.d'], () => {
-    onCopyFrom()
-  })
+  useKeyPress(
+    ['ctrl.d'],
+    debounce(() => {
+      onCopyFrom()
+    }, 100)
+  )
   // 键盘事件 删除
-  useKeyPress(['del', 'backspace'], () => {
-    onDelete()
-  })
+  useKeyPress(
+    ['delete', 'backspace'],
+    debounce(() => {
+      onDelete()
+    }, 100)
+  )
   // 键盘事件 撤销
-  useKeyPress(['ctrl.z'], () => {
-    projectStore.undo()
-  })
+  useKeyPress(
+    ['ctrl.z'],
+    debounce(() => {
+      projectStore.undo()
+    }, 100)
+  )
   // 键盘事件 重做
-  useKeyPress(['ctrl.shift.z'], () => {
-    projectStore.redo()
-  })
+  useKeyPress(
+    ['ctrl.shift.z'],
+    debounce(() => {
+      projectStore.redo()
+    }, 100)
+  )
   // 键盘事件 隐藏
-  useKeyPress(['ctrl.h'], () => {
-    onHidden(true)
-  })
+  useKeyPress(
+    ['ctrl.h'],
+    debounce(() => {
+      onHidden(true)
+    }, 100)
+  )
   // 键盘事件 锁定
-  useKeyPress(['ctrl.l'], () => {
-    onLock(true)
-  })
+  useKeyPress(
+    ['ctrl.l'],
+    debounce(() => {
+      onLock(true)
+    }, 100)
+  )
   // 键盘事件 锁定
-  useKeyPress(['ctrl.shift.l'], () => {
-    onLock(false)
-  })
+  useKeyPress(
+    ['ctrl.shift.l'],
+    debounce(() => {
+      onLock(false)
+    }, 100)
+  )
   // 键盘事件 上移
-  useKeyPress(['ctrl.up'], () => {
-    onLevel('up')
-  })
+  useKeyPress(
+    ['ctrl.up'],
+    debounce(() => {
+      onLevel('up')
+    }, 100)
+  )
   // 键盘事件 下移
-  useKeyPress(['ctrl.down'], () => {
-    onLevel('down')
-  })
+  useKeyPress(
+    ['ctrl.down'],
+    debounce(() => {
+      onLevel('down')
+    }, 100)
+  )
   // 键盘事件 置顶
-  useKeyPress(['ctrl.shift.up'], () => {
-    onLevel('top')
-  })
+  useKeyPress(
+    ['ctrl.shift.up'],
+    debounce(() => {
+      onLevel('top')
+    }, 100)
+  )
   // 键盘事件 置底
-  useKeyPress(['ctrl.shift.down'], () => {
-    onLevel('bottom')
-  })
+  useKeyPress(
+    ['ctrl.shift.down'],
+    debounce(() => {
+      onLevel('bottom')
+    }, 100)
+  )
   // 键盘事件 全选
-  useKeyPress(['ctrl.a'], () => {
-    onSelectAll()
-  })
+  useKeyPress(
+    ['ctrl.a'],
+    debounce(() => {
+      onSelectAll()
+    }, 100)
+  )
   // 保存
-  useKeyPress(['ctrl.s'], () => {
-    if (projectStore.project) {
-      projectStore.saveProject()
-    }
-  })
+  useKeyPress(
+    ['ctrl.s'],
+    debounce(() => {
+      if (projectStore.project) {
+        projectStore.saveProject()
+      }
+    }, 100)
+  )
 
   return {
     onAlign,

+ 1 - 1
src/renderer/src/store/modules/app.ts

@@ -30,7 +30,7 @@ export const useAppStore = defineStore('app', () => {
   // 联调工具路径
   const sunmicroPath = useLocalStorage('sunmicroPath', '')
   // 底部工具
-  const showComposite = ref(true)
+  const showComposite = ref(false)
   // 底部工具当前tab
   const compositeTabAcitve = ref('log')
 

+ 3 - 3
src/renderer/src/store/modules/project.ts

@@ -174,7 +174,6 @@ export const useProjectStore = defineStore('project', () => {
       project.value.meta.path = path
     }
     // 初始化处理
-    clear()
     projectPath.value = path
     openPages.value = newProject.screens.map((screen) => screen.pages?.[0]).filter((item) => item)
     activePageId.value = newProject.screens[0].pages?.[0]?.id
@@ -183,9 +182,10 @@ export const useProjectStore = defineStore('project', () => {
       id: v4(),
       projectName: newProject.meta.name,
       projectPath: path,
-      createTime: newProject.meta.createTime,
-      modifyTime: newProject.meta.modifyTime
+      createTime: newProject.meta.createTime!,
+      modifyTime: newProject.meta.modifyTime!
     })
+    clear()
   }
 
   // 保存当前项目

+ 16 - 5
src/renderer/src/views/designer/config/LanguagesConfig.vue

@@ -36,8 +36,14 @@
                       :label="menu.label"
                     />
                   </el-select>
-                  <el-input v-model="languagesItem.value" style="flex: 1" />
+                  <el-input v-model="languagesItem.value" style="flex: 1" placeholder="内容" />
                   <el-select v-model="languagesItem.font" style="flex: 1" placeholder="字体">
+                    <el-option
+                      v-for="font in fonts"
+                      :key="font.id"
+                      :value="font.id"
+                      :label="font.fileName"
+                    />
                   </el-select>
                   <el-icon
                     class="cursor-pointer"
@@ -61,12 +67,13 @@
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { computed, ref, watch } from 'vue'
 import type { LanguagesGroup } from '@/types/languages'
 import { ArrowRight, Plus, Delete } from '@element-plus/icons-vue'
 import { ElMessageBox } from 'element-plus'
 import { languagesMenu } from '@/constants'
 import { v4 } from 'uuid'
+import { useProjectStore } from '@/store/modules/project'
 
 interface Emits {
   (e: 'update:languages', val: LanguagesGroup[]): void
@@ -79,6 +86,10 @@ const activeNames = ref<string[]>([])
 const data = ref<LanguagesGroup[]>(props.languages || ({} as LanguagesGroup[]))
 watch(data, (val) => emit('update:languages', val))
 
+const projectStore = useProjectStore()
+
+const fonts = computed(() => projectStore.project?.resources.fonts || [])
+
 const handleAddlanguagesValues = (values) => {
   values.push({
     language: '',
@@ -106,7 +117,7 @@ const handleRemoveGroup = (languages) =>
     }
   })
 const handleAdd = () => {
-  const first = data.value[0].values.map((item) => ({
+  const first = data.value?.[0]?.values.map((item) => ({
     ...item,
     value: ''
   }))
@@ -114,8 +125,8 @@ const handleAdd = () => {
   const id = v4()
   data.value.push({
     id,
-    key: '',
-    values: first.length
+    key: `lang_${data.value.length + 1}`,
+    values: first?.length
       ? first
       : [
           {

+ 52 - 19
src/renderer/src/views/designer/sidebar/Hierarchy.vue

@@ -1,9 +1,9 @@
 <template>
-  <el-tabs v-model="activeMenu" stretch>
+  <el-tabs v-model="activeMenu" stretch class="w-full h-full">
     <el-tab-pane label="屏幕" name="screen" class="flex-1">
       <!-- 屏幕层 -->
       <el-tree
-        ref="treeRef"
+        ref="screenTreeRef"
         style="max-width: 600px"
         default-expand-all
         node-key="id"
@@ -16,34 +16,67 @@
         </template>
       </el-tree>
     </el-tab-pane>
-    <el-tab-pane label="页面" name="page">
+    <el-tab-pane label="页面" name="page" class="w-full h-full">
       <!-- 页面层 -->
-      <el-tree
-        ref="treeRef"
-        style="max-width: 600px"
-        default-expand-all
-        node-key="id"
-        :highlight-current="false"
-        :data="projectStore.activePage ? [projectStore.activePage] : []"
-        :props="{ label: 'name', children: 'children' }"
-        @node-click="handleWidgetNodeClick"
-      >
-        <template #default="{ node, data }">
-          <PageTreeItem :node="node" :data="data" />
-        </template>
-      </el-tree>
+      <div class="h-full overflow-hidden" ref="pageBoxRef">
+        <el-tree-v2
+          ref="pageTreeRef"
+          style="max-width: 600px"
+          default-expand-all
+          node-key="id"
+          :height="height || 100"
+          :highlight-current="false"
+          :default-expanded-keys="projectStore.activePageId ? [projectStore.activePageId] : []"
+          :data="projectStore.activePage ? [projectStore.activePage] : []"
+          :props="{ label: 'name', children: 'children' }"
+          @node-click="handleWidgetNodeClick"
+        >
+          <template #default="{ node, data }">
+            <PageTreeItem :node="node" :data="data" />
+          </template>
+        </el-tree-v2>
+      </div>
     </el-tab-pane>
   </el-tabs>
 </template>
 
 <script setup lang="ts">
+import { ref, watch } from 'vue'
+
 import ScreenTreeItem from './components/ScreenTreeItem.vue'
 import PageTreeItem from './components/PageTreeItem.vue'
 import { useProjectStore } from '@/store/modules/project'
-import { ref } from 'vue'
+import { useElementSize } from '@vueuse/core'
+
+import type { TreeV2Instance } from 'element-plus'
 
 const projectStore = useProjectStore()
 const activeMenu = ref<string>('screen')
+const pageTreeRef = ref<TreeV2Instance>()
+const pageBoxRef = ref<HTMLDivElement>()
+
+const { height } = useElementSize(pageBoxRef, {
+  height: 100,
+  width: 100
+})
+
+// 更新控件树
+watch(
+  () => projectStore.activePage?.children,
+  (arr) => {
+    if (arr) pageTreeRef.value?.setData([projectStore.activePage!])
+  },
+  {
+    deep: true
+  }
+)
+
+watch(
+  () => projectStore.activeWidgets,
+  (arr) => {
+    if (arr.length) pageTreeRef.value?.scrollToNode(arr[0].id)
+  }
+)
 
 const handlePageNodeClick = (node: any) => {
   if (node.type === 'page') {
@@ -59,7 +92,7 @@ const handlePageNodeClick = (node: any) => {
   })
 }
 
-const handleWidgetNodeClick = (nodeData, _node, _a, e) => {
+const handleWidgetNodeClick = (nodeData, _node, e) => {
   if (nodeData.type !== 'page') {
     if (e.ctrlKey) {
       projectStore.activeWidgets.push(nodeData)

+ 32 - 6
src/renderer/src/views/designer/sidebar/Libary.vue

@@ -14,7 +14,12 @@
         :name="group.label"
       >
         <div class="px-2 pb-2 pt-1 grid grid-cols-[repeat(auto-fill,minmax(70px,1fr))] gap-row-2">
-          <LibaryItem v-for="item in group.items" :key="item.key" :comp="item" />
+          <LibaryItem
+            v-for="item in group.items"
+            :key="item.key"
+            :comp="item"
+            @click="handleAdd(item)"
+          />
         </div>
       </el-collapse-item>
     </el-collapse>
@@ -30,11 +35,15 @@
 import { computed, ref } from 'vue'
 import { ComponentArray } from '@/lvgl-widgets'
 import LibaryItem from './components/LibaryItem.vue'
+import { createWidget } from '@/model'
+import { getAddWidgetIndex } from '@/utils'
+import { useProjectStore } from '@/store/modules/project'
 
 import type { IComponentModelConfig } from '@/lvgl-widgets/type'
 
 const search = ref('')
 const activeCollapse = ref<string[]>([])
+const projectStore = useProjectStore()
 
 const groupMap = ref<{
   [key: string]: {
@@ -43,6 +52,7 @@ const groupMap = ref<{
   }
 }>({})
 
+// 变量全部控件
 ComponentArray.filter((item) => !item.hideLibary).forEach((item) => {
   if (!groupMap.value[item.group]) {
     groupMap.value[item.group] = {
@@ -57,19 +67,35 @@ ComponentArray.filter((item) => !item.hideLibary).forEach((item) => {
   return item.group
 })
 
+// 根据搜索条件获取分组信息
 const getGroups = computed(() => {
   const list = Object.values(groupMap.value).filter((item) =>
     item.items.some((item) => item.label.includes(search.value))
   )
 
-  if (search.value) {
-    activeCollapse.value = list.map((item) => item.label)
-  } else {
-    activeCollapse.value = list.length ? [list[0].label] : []
-  }
+  activeCollapse.value = list.map((item) => item.label)
 
   return list
 })
+
+// 处理点击添加控件
+function handleAdd(item: IComponentModelConfig) {
+  const page = projectStore.activePage
+  const index = getAddWidgetIndex(page!, item.key)
+  const newWidget = createWidget(item, index)
+  // 查找当前screen
+  const screen = projectStore.project?.screens.find(
+    (screen) => !!screen.pages.find((p) => page?.id === p.id)
+  )
+  // 随机位置
+  const r = Math.floor(Math.random() * 100)
+  if (screen) {
+    newWidget.props.x = screen.width / 2 - newWidget.props.width / 2 + r
+    newWidget.props.y = screen.height / 2 - newWidget.props.height / 2 + r
+  }
+
+  projectStore.activePage?.children?.unshift(newWidget)
+}
 </script>
 
 <style scoped lang="less">

+ 26 - 4
src/renderer/src/views/designer/workspace/stage/Node.vue

@@ -116,11 +116,19 @@ const zIndex = ref(props.zIndex)
 const selected = computed(() =>
   projectStore.activeWidgets.map((item) => item.id).includes(props.schema.id)
 )
+// 放置效果样式
+const dropStyle = ref<CSSProperties>({})
 
 // 组件样式
 const getStyle = computed((): CSSProperties => {
   const { style = {}, schema } = props
 
+  const other: CSSProperties = {}
+  if (pageState?.showBorder) {
+    other.border = '1px dashed #000'
+    other.transform = `translate(${schema.props.x - 1}px, ${schema.props.y - 1}px)`
+  }
+
   return {
     position: 'absolute',
     left: 0,
@@ -129,7 +137,9 @@ const getStyle = computed((): CSSProperties => {
     width: schema.props.width + 'px',
     height: schema.props.height + 'px',
     zIndex: zIndex.value,
-    ...style
+    ...style,
+    ...other,
+    ...dropStyle.value
   }
 })
 
@@ -181,6 +191,7 @@ useMutationObserver(
 useDrop(nodeRef, {
   // 元素放置
   onDom: (content, event) => {
+    dropStyle.value = {}
     if (!content) return
     // 创建控件
     const { offsetX = 0, offsetY = 0 } = event || {}
@@ -191,6 +202,17 @@ useDrop(nodeRef, {
     // 添加到前面
     props.schema.children?.unshift(newWidget)
     projectStore.setSelectWidgets([newWidget])
+  },
+  onDragOver: () => {
+    if (props.schema.type !== 'page' && props.schema.children) {
+      dropStyle.value = {
+        // 橙色阴影
+        boxShadow: '0px 0px 5px #FC7121'
+      }
+    }
+  },
+  onDragLeave: () => {
+    dropStyle.value = {}
   }
 })
 
@@ -209,12 +231,12 @@ const handleSelect = (e) => {
 }
 
 // 拖拽开始
-const onDragStart = (e) => {
-  zIndex.value = 9999
+const onDragStart = () => {
+  zIndex.value = 999
 }
 
 // 拖拽结束
-const onDragEnd = (e) => {
+const onDragEnd = () => {
   zIndex.value = props.zIndex
 }
 

+ 2 - 9
src/renderer/src/views/designer/workspace/stage/index.vue

@@ -38,7 +38,7 @@
             class="w-20px h-20px flex items-center justify-center cursor-pointer"
             @click="handleCenter"
           >
-            <LuCircleDot :size="16" />
+            <LuFocus :size="16" />
           </div>
           <div
             class="w-20px h-20px flex items-center justify-center cursor-pointer border-1px border-solid border-transparent"
@@ -87,14 +87,7 @@ import { ref, reactive, watch, nextTick, provide } from 'vue'
 import Ruler from './Ruler.vue'
 import DesignerCanvas from './DesignerCanvas.vue'
 import { throttle } from 'lodash-es'
-import {
-  LuGrid3X3,
-  LuRuler,
-  LuBoxSelect,
-  LuRows2,
-  LuColumns2,
-  LuCircleDot
-} from 'vue-icons-plus/lu'
+import { LuGrid3X3, LuRuler, LuBoxSelect, LuRows2, LuColumns2, LuFocus } from 'vue-icons-plus/lu'
 import { useProjectStore } from '@/store/modules/project'
 import { useAppStore } from '@/store/modules/app'