perf: UI交互优化

This commit is contained in:
pipipi-pikachu 2021-02-06 17:45:06 +08:00
parent fee765a20c
commit 839d31b98f
9 changed files with 86 additions and 72 deletions

View File

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

View File

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

View File

@ -21,6 +21,12 @@ export default () => {
for (const element of newElementList) { for (const element of newElementList) {
if (!activeElementIdList.value.includes(element.id)) continue 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) { if (command === ElementAlignCommands.TOP) {
const offsetY = minY - 0 const offsetY = minY - 0
element.top = element.top - offsetY element.top = element.top - offsetY

View File

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

View File

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

View File

@ -30,6 +30,7 @@ import useCombineElement from '@/hooks/useCombineElement'
import useOrderElement from '@/hooks/useOrderElement' import useOrderElement from '@/hooks/useOrderElement'
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas' import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement' import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
import useSelectAllElement from '@/hooks/useSelectAllElement'
import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit' import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
@ -81,7 +82,8 @@ export default defineComponent({
const { combineElements, uncombineElements } = useCombineElement() const { combineElements, uncombineElements } = useCombineElement()
const { deleteElement } = useDeleteElement() const { deleteElement } = useDeleteElement()
const { lockElement, unlockElement } = useLockElement() const { lockElement, unlockElement } = useLockElement()
const { copyElement, cutElement } = useCopyAndPasteElement() const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()
const { selectAllElement } = useSelectAllElement()
const contextmenus = (): ContextmenuItem[] => { const contextmenus = (): ContextmenuItem[] => {
if (props.elementInfo.lock) { if (props.elementInfo.lock) {
@ -102,7 +104,26 @@ export default defineComponent({
subText: 'Ctrl + C', subText: 'Ctrl + C',
handler: copyElement, handler: copyElement,
}, },
{
text: '粘贴',
subText: 'Ctrl + V',
handler: pasteElement,
},
{ divider: true }, { divider: true },
{
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, disable: props.isMultiSelect && !props.elementInfo.groupId,
@ -114,22 +135,6 @@ export default defineComponent({
{ text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) }, { text: '下移一层', handler: () => orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
], ],
}, },
{
text: '水平对齐',
children: [
{ text: '水平居中', handler: () => alignElementToCanvas(ElementAlignCommands.HORIZONTAL) },
{ text: '左对齐', handler: () => alignElementToCanvas(ElementAlignCommands.LEFT) },
{ text: '右对齐', handler: () => alignElementToCanvas(ElementAlignCommands.RIGHT) },
],
},
{
text: '垂直对齐',
children: [
{ text: '垂直居中', handler: () => alignElementToCanvas(ElementAlignCommands.VERTICAL) },
{ text: '上对齐', handler: () => alignElementToCanvas(ElementAlignCommands.TOP) },
{ text: '下对齐', handler: () => alignElementToCanvas(ElementAlignCommands.BOTTOM) },
],
},
{ divider: true }, { divider: true },
{ {
text: props.elementInfo.groupId ? '取消组合' : '组合', text: props.elementInfo.groupId ? '取消组合' : '组合',
@ -137,6 +142,11 @@ export default defineComponent({
handler: props.elementInfo.groupId ? uncombineElements : combineElements, handler: props.elementInfo.groupId ? uncombineElements : combineElements,
hide: !props.isMultiSelect, hide: !props.isMultiSelect,
}, },
{
text: '全选',
subText: 'Ctrl + A',
handler: selectAllElement,
},
{ {
text: '锁定', text: '锁定',
subText: 'Ctrl + L', subText: 'Ctrl + L',

View File

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

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="table-generator"> <div class="table-generator">
<div class="title"> <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 class="right" @click="isCustom = !isCustom">{{ isCustom ? '返回' : '自定义'}}</div>
</div> </div>
<table <table

View File

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