mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
perf: UI交互优化
This commit is contained in:
parent
fee765a20c
commit
839d31b98f
@ -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>
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -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'
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user