Kaynağa Gözat

perf: UI交互优化

pipipi-pikachu 5 yıl önce
ebeveyn
işleme
839d31b98f

+ 8 - 15
src/components/Contextmenu/MenuContent.vue

@@ -1,22 +1,18 @@
 <template>
   <ul class="menu-content">
-    <template v-for="(menu, index) in menus">
+    <template v-for="(menu, index) in menus" :key="menu.text || index">
       <li
         v-if="!menu.hide"
         class="menu-item"
-        :key="menu.text || index"
         @click.stop="handleClickMenuItem(menu)"
         :class="{'divider': menu.divider, 'disable': menu.disable}"
       >
-        <div class="menu-item-content" :class="{'has-sub-menu': menu.children}" v-if="!menu.divider">
+        <div class="menu-item-content" :class="{'has-children': menu.children}" v-if="!menu.divider">
           <span class="text">{{menu.text}}</span>
           <span class="sub-text" v-if="menu.subText && !menu.children">{{menu.subText}}</span>
 
           <menu-content 
-            class="sub-menu" 
-            :style="{
-              [subMenuPosition]: '112.5%',
-            }"
+            class="sub-menu"
             :menus="menu.children" 
             v-if="menu.children && menu.children.length"
             :handleClickMenuItem="handleClickMenuItem" 
@@ -38,10 +34,6 @@ export default defineComponent({
       type: Array as PropType<ContextmenuItem[]>,
       required: true,
     },
-    subMenuPosition: {
-      type: String,
-      default: 'left',
-    },
     handleClickMenuItem: {
       type: Function,
       required: true,
@@ -51,7 +43,7 @@ export default defineComponent({
 </script>
 
 <style lang="scss" scoped>
-$menuWidth: 160px;
+$menuWidth: 170px;
 $menuHeight: 30px;
 $subMenuWidth: 120px;
 
@@ -104,7 +96,7 @@ $subMenuWidth: 120px;
   justify-content: space-between;
   position: relative;
 
-  &.has-sub-menu::before {
+  &.has-children::before {
     content: '';
     display: inline-block;
     width: 8px;
@@ -121,10 +113,11 @@ $subMenuWidth: 120px;
     opacity: 0.6;
   }
   .sub-menu {
+    width: $subMenuWidth;
     position: absolute;
-    top: -6px;
     display: none;
-    width: $subMenuWidth;
+    left: 112%;
+    top: -6px;
   }
 }
 </style>

+ 13 - 23
src/components/Contextmenu/index.vue

@@ -1,21 +1,20 @@
 <template>
   <div 
     class="mask"
-    @contextmenu.prevent="removeContextMenu()"
-    @mousedown="removeContextMenu()"
+    @contextmenu.prevent="removeContextmenu()"
+    @mousedown="removeContextmenu()"
   ></div>
 
   <div 
     class="contextmenu"
     :style="{
-      left: style.left,
-      top: style.top,
+      left: style.left + 'px',
+      top: style.top + 'px',
     }"
     @contextmenu.prevent
   >
     <MenuContent 
       :menus="menus"
-      :subMenuPosition="style.subMenuPosition" 
       :handleClickMenuItem="handleClickMenuItem" 
     />
   </div>
@@ -27,10 +26,10 @@ import { ContextmenuItem, Axis } from './types'
 
 import MenuContent from './MenuContent.vue'
 
-const MENU_WIDTH = 160
+const MENU_WIDTH = 170
 const MENU_HEIGHT = 30
 const DIVIDER_HEIGHT = 11
-const SUB_MENU_WIDTH = 120
+const PADDING = 5
 
 export default defineComponent({
   name: 'contextmenu',
@@ -50,7 +49,7 @@ export default defineComponent({
       type: Array as PropType<ContextmenuItem[]>,
       required: true,
     },
-    removeContextMenu: {
+    removeContextmenu: {
       type: Function,
       required: true,
     },
@@ -58,34 +57,25 @@ export default defineComponent({
   setup(props) {
     const style = computed(() => {
       const { x, y } = props.axis
-      const normalMenuCount = props.menus.filter(menu => !menu.divider && !menu.hide).length
-      const dividerMenuCount = props.menus.filter(menu => menu.divider).length
-      const padding = 10
+      const menuCount = props.menus.filter(menu => !(menu.divider || menu.hide)).length
+      const dividerCount = props.menus.filter(menu => menu.divider).length
 
       const menuWidth = MENU_WIDTH
-      const menuHeight = normalMenuCount * MENU_HEIGHT + dividerMenuCount * DIVIDER_HEIGHT + padding
-
-      const maxMenuWidth = MENU_WIDTH + SUB_MENU_WIDTH - 10
+      const menuHeight = menuCount * MENU_HEIGHT + dividerCount * DIVIDER_HEIGHT + PADDING * 2
 
       const screenWidth = document.body.clientWidth
       const screenHeight = document.body.clientHeight
 
-      const left = (screenWidth <= x + menuWidth ? x - menuWidth : x)
-      const top = (screenHeight <= y + menuHeight ? y - menuHeight : y)
-
-      const subMenuPosition = screenWidth <= left + maxMenuWidth ? 'right' : 'left'
-
       return {
-        left: left + 'px',
-        top: top + 'px',
-        subMenuPosition,
+        left: screenWidth <= x + menuWidth ? x - menuWidth : x,
+        top: screenHeight <= y + menuHeight ? y - menuHeight : y,
       }
     })
 
     const handleClickMenuItem = (item: ContextmenuItem) => {
       if (item.disable || item.children) return
       if (item.handler) item.handler(props.el)
-      props.removeContextMenu()
+      props.removeContextmenu()
     }
 
     return {

+ 6 - 0
src/hooks/useAlignElementToCanvas.ts

@@ -21,6 +21,12 @@ export default () => {
     for (const element of newElementList) {
       if (!activeElementIdList.value.includes(element.id)) continue
       
+      if (command === ElementAlignCommands.CENTER) {
+        const offsetY = minY + (maxY - minY) / 2 - viewportHeight / 2
+        const offsetX = minX + (maxX - minX) / 2 - viewportWidth / 2
+        element.top = element.top - offsetY 
+        element.left = element.left - offsetX           
+      }
       if (command === ElementAlignCommands.TOP) {
         const offsetY = minY - 0
         element.top = element.top - offsetY            

+ 6 - 6
src/plugins/contextmenu.ts

@@ -12,21 +12,21 @@ const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: Direct
 
   let container: HTMLDivElement | null = null
 
-  const removeContextMenu = () => {
+  const removeContextmenu = () => {
     if (container) {
       document.body.removeChild(container)
       container = null
     }
     el.classList.remove('contextmenu-active')
-    document.body.removeEventListener('scroll', removeContextMenu)  
-    window.removeEventListener('resize', removeContextMenu)
+    document.body.removeEventListener('scroll', removeContextmenu)  
+    window.removeEventListener('resize', removeContextmenu)
   }
 
   const options = {
     axis: { x: event.x, y: event.y },
     el,
     menus,
-    removeContextMenu,
+    removeContextmenu,
   }
   container = document.createElement('div')
   const vm = createVNode(ContextmenuComponent, options, null)
@@ -35,8 +35,8 @@ const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: Direct
 
   el.classList.add('contextmenu-active')
 
-  document.body.addEventListener('scroll', removeContextMenu)
-  window.addEventListener('resize', removeContextMenu)
+  document.body.addEventListener('scroll', removeContextmenu)
+  window.addEventListener('resize', removeContextmenu)
 }
 
 const ContextmenuDirective: Directive = {

+ 2 - 1
src/types/edit.ts

@@ -10,7 +10,7 @@ export const enum ElementOrderCommands {
   BOTTOM = 'bottom',
 }
 
-export type ElementAlignCommand = 'top'| 'bottom' | 'left' | 'right' | 'vertical' | 'horizontal'
+export type ElementAlignCommand = 'top'| 'bottom' | 'left' | 'right' | 'vertical' | 'horizontal' | 'center'
 
 export const enum ElementAlignCommands {
   TOP = 'top',
@@ -19,6 +19,7 @@ export const enum ElementAlignCommands {
   RIGHT = 'right',
   VERTICAL = 'vertical',
   HORIZONTAL = 'horizontal',
+  CENTER = 'center',
 }
 
 export type OperateBorderLine = 'top' | 'bottom' | 'left' | 'right'

+ 26 - 16
src/views/Editor/Canvas/EditableElement.vue

@@ -30,6 +30,7 @@ import useCombineElement from '@/hooks/useCombineElement'
 import useOrderElement from '@/hooks/useOrderElement'
 import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
 import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
+import useSelectAllElement from '@/hooks/useSelectAllElement'
 
 import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
 
@@ -81,7 +82,8 @@ export default defineComponent({
     const { combineElements, uncombineElements } = useCombineElement()
     const { deleteElement } = useDeleteElement()
     const { lockElement, unlockElement } = useLockElement()
-    const { copyElement, cutElement } = useCopyAndPasteElement()
+    const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()
+    const { selectAllElement } = useSelectAllElement()
 
     const contextmenus = (): ContextmenuItem[] => {
       if (props.elementInfo.lock) {
@@ -102,32 +104,35 @@ export default defineComponent({
           subText: 'Ctrl + C',
           handler: copyElement,
         },
-        { divider: true },
         {
-          text: '层级排序',
-          disable: props.isMultiSelect && !props.elementInfo.groupId,
-          children: [
-            { text: '置顶层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP) },
-            { text: '置底层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
-            { divider: true },
-            { text: '上移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.UP) },
-            { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
-          ],
+          text: '粘贴',
+          subText: 'Ctrl + V',
+          handler: pasteElement,
         },
+        { divider: true },
         {
-          text: '水平对齐',
+          text: '对齐方式',
           children: [
+            { text: '水平垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.CENTER) },
+            { divider: true },
             { text: '水平居中', handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },
             { text: '左对齐', handler: () => alignElementToCanvas(ElementAlignCommands.LEFT) },
             { text: '右对齐', handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT) },
+            { divider: true },
+            { text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },
+            { text: '顶部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },
+            { text: '底部对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },
           ],
         },
         {
-          text: '垂直对齐',
+          text: '层级排序',
+          disable: props.isMultiSelect && !props.elementInfo.groupId,
           children: [
-            { text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },
-            { text: '上对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },
-            { text: '下对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },
+            { text: '置顶层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.TOP) },
+            { text: '置底层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
+            { divider: true },
+            { text: '上移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.UP) },
+            { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
           ],
         },
         { divider: true },
@@ -138,6 +143,11 @@ export default defineComponent({
           hide: !props.isMultiSelect,
         },
         {
+          text: '全选',
+          subText: 'Ctrl + A',
+          handler: selectAllElement,
+        },
+        {
           text: '锁定',
           subText: 'Ctrl + L',
           handler: lockElement,

+ 13 - 5
src/views/Editor/Canvas/index.vue

@@ -97,6 +97,7 @@ import useDeleteElement from '@/hooks/useDeleteElement'
 import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
 import useSelectAllElement from '@/hooks/useSelectAllElement'
 import useScaleCanvas from '@/hooks/useScaleCanvas'
+import useScreening from '@/hooks/useScreening'
 
 import EditableElement from './EditableElement.vue'
 import MouseSelection from './MouseSelection.vue'
@@ -156,6 +157,7 @@ export default defineComponent({
     const { selectAllElement } = useSelectAllElement()
     const { deleteAllElements } = useDeleteElement()
     const { pasteElement } = useCopyAndPasteElement()
+    const { enterScreening } = useScreening()
 
     const handleClickBlankArea = (e: MouseEvent) => {
       store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
@@ -190,22 +192,28 @@ export default defineComponent({
     const contextmenus = (): ContextmenuItem[] => {
       return [
         {
+          text: '粘贴',
+          subText: 'Ctrl + V',
+          handler: pasteElement,
+        },
+        {
           text: '全选',
           subText: 'Ctrl + A',
           handler: selectAllElement,
         },
         {
-          text: '粘贴',
-          subText: 'Ctrl + V',
-          handler: pasteElement,
+          text: '重置当前页',
+          handler: deleteAllElements,
         },
         {
           text: showGridLines.value ? '关闭网格线' : '打开网格线',
           handler: toggleGridLines,
         },
+        { divider: true },
         {
-          text: '清空本页',
-          handler: deleteAllElements,
+          text: '从当前页演示',
+          subText: 'Ctrl+F',
+          handler: enterScreening,
         },
       ]
     }

+ 1 - 1
src/views/Editor/CanvasTool/TableGenerator.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="table-generator">
     <div class="title">
-      <div class="lef">插入表格 {{endCell.length ? `${endCell[0]} x ${endCell[1]}` : ''}}</div>
+      <div class="lef">表格 {{endCell.length ? `${endCell[0]} x ${endCell[1]}` : ''}}</div>
       <div class="right" @click="isCustom = !isCustom">{{ isCustom ? '返回' : '自定义'}}</div>
     </div>
     <table 

+ 7 - 1
src/views/Editor/Thumbnails/index.vue

@@ -103,6 +103,11 @@ export default defineComponent({
           subText: 'Enter',
           handler: createSlide,
         },
+        {
+          text: '开始演示',
+          subText: 'Ctrl+F',
+          handler: enterScreening,
+        },
       ]
     }
 
@@ -131,6 +136,7 @@ export default defineComponent({
         },
         {
           text: '复制页面',
+          subText: 'Ctrl + D',
           handler: copyAndPasteSlide,
         },
         {
@@ -140,7 +146,7 @@ export default defineComponent({
         },
         { divider: true },
         {
-          text: '从页演示',
+          text: '从当前页演示',
           subText: 'Ctrl+F',
           handler: enterScreening,
         },