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>
<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%',
}"
: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 {
position: absolute;
top: -6px;
display: none;
width: $subMenuWidth;
position: absolute;
display: none;
left: 112%;
top: -6px;
}
}
</style>

View File

@ -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 {

View File

@ -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

View File

@ -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 = {

View File

@ -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'

View File

@ -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,7 +104,26 @@ export default defineComponent({
subText: 'Ctrl + C',
handler: copyElement,
},
{
text: '粘贴',
subText: 'Ctrl + V',
handler: pasteElement,
},
{ 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: '层级排序',
disable: props.isMultiSelect && !props.elementInfo.groupId,
@ -114,22 +135,6 @@ export default defineComponent({
{ 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 },
{
text: props.elementInfo.groupId ? '取消组合' : '组合',
@ -137,6 +142,11 @@ export default defineComponent({
handler: props.elementInfo.groupId ? uncombineElements : combineElements,
hide: !props.isMultiSelect,
},
{
text: '全选',
subText: 'Ctrl + A',
handler: selectAllElement,
},
{
text: '锁定',
subText: 'Ctrl + L',

View File

@ -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, [])
@ -189,23 +191,29 @@ export default defineComponent({
const contextmenus = (): ContextmenuItem[] => {
return [
{
text: '全选',
subText: 'Ctrl + A',
handler: selectAllElement,
},
{
text: '粘贴',
subText: 'Ctrl + V',
handler: pasteElement,
},
{
text: '全选',
subText: 'Ctrl + A',
handler: selectAllElement,
},
{
text: '重置当前页',
handler: deleteAllElements,
},
{
text: showGridLines.value ? '关闭网格线' : '打开网格线',
handler: toggleGridLines,
},
{ divider: true },
{
text: '清空本页',
handler: deleteAllElements,
text: '从当前页演示',
subText: 'Ctrl+F',
handler: enterScreening,
},
]
}

View File

@ -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

View File

@ -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,
},