This commit is contained in:
pipipi-pikachu 2020-12-15 18:02:44 +08:00
parent 2d245dd88b
commit 7f301cadfd
15 changed files with 367 additions and 77 deletions

View File

@ -1,5 +1,4 @@
export enum KEYCODE {
S = 83,
C = 67,
X = 88,
Z = 90,

View File

@ -9,7 +9,6 @@ export enum MutationTypes {
SET_EDITORAREA_FOCUS = 'setEditorAreaFocus',
SET_DISABLE_HOTKEYS_STATE = 'setDisableHotkeysState',
SET_AVAILABLE_FONTS = 'setAvailableFonts',
SET_SAVE_STATE = 'setSaveState',
// slides
SET_SLIDES = 'setSlides',

View File

@ -1,5 +1,5 @@
import { MutationTypes } from './constants'
import { State, SaveState } from './state'
import { State } from './state'
import { Slide, PPTElement } from '@/types/slides'
import { FONT_NAMES } from '@/configs/fontName'
import { isSupportFontFamily } from '@/utils/index'
@ -36,7 +36,6 @@ export type Mutations = {
[MutationTypes.SET_EDITORAREA_FOCUS](state: State, isFocus: boolean): void;
[MutationTypes.SET_DISABLE_HOTKEYS_STATE](state: State, disable: boolean): void;
[MutationTypes.SET_AVAILABLE_FONTS](state: State): void;
[MutationTypes.SET_SAVE_STATE](state: State, saveState: SaveState ): void;
[MutationTypes.SET_SLIDES](state: State, slides: Slide[]): void;
[MutationTypes.ADD_SLIDES](state: State, data: AddSlidesData): void;
[MutationTypes.SET_SLIDE](state: State, data: SetSlideData): void;
@ -91,10 +90,6 @@ export const mutations: Mutations = {
state.availableFonts = FONT_NAMES.filter(font => isSupportFontFamily(font.en))
},
[MutationTypes.SET_SAVE_STATE](state, saveState) {
state.saveState = saveState
},
// slides
[MutationTypes.SET_SLIDES](state, slides) {

View File

@ -2,8 +2,6 @@ import { Slide } from '@/types/slides'
import { slides } from '@/mocks/index'
import { FontName } from '@/configs/fontName'
export type SaveState = 'complete' | 'pending'
export type State = {
activeElementIdList: string[];
handleElementId: string;
@ -13,7 +11,6 @@ export type State = {
editorAreaFocus: boolean;
disableHotkeys: boolean;
availableFonts: FontName[];
saveState: SaveState;
slides: Slide[];
slideIndex: number;
cursor: number;
@ -29,7 +26,6 @@ export const state: State = {
editorAreaFocus: false,
disableHotkeys: false,
availableFonts: [],
saveState: 'complete',
slides: slides,
slideIndex: 0,
cursor: -1,

28
src/types/edit.ts Normal file
View File

@ -0,0 +1,28 @@
export type ElementOrderCommand = 'up' | 'down' | 'top' | 'bottom'
export enum ElementOrderCommands {
UP = 'up',
DOWN = 'down',
TOP = 'top',
BOTTOM = 'bottom',
}
export type ElementAlignCommand = 'top'| 'bottom' | 'left' | 'right' | 'vertical' | 'horizontal'
export enum ElementAlignCommands {
TOP = 'top',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right',
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
}
export type ElementScaleHandler = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
export type ElementLockCommand = 'lock' | 'unlock'
export enum ElementLockCommands {
LOCK = 'lock',
UNLOCK = 'unlock',
}

View File

@ -19,7 +19,6 @@ export interface PPTElementBorderProps {
export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
type: 'text';
textType: string;
content: string;
rotate?: number;
fill?: string;
@ -28,20 +27,18 @@ export interface PPTTextElement extends PPTElementBaseProps, PPTElementSizeProps
segmentSpacing?: number;
letterSpacing?: number;
shadow?: string;
padding?: number;
}
interface ImageClip {
range: [[number, number], [number, number]];
shape: string;
}
export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
type: 'image';
lockRatio: boolean;
imgUrl: string;
rotate?: number;
filter?: string;
clip?: ImageClip;
clip?: {
range: [[number, number], [number, number]];
shape: string;
};
flip?: string;
shadow?: string;
}
@ -58,15 +55,6 @@ export interface PPTShapeElement extends PPTElementBaseProps, PPTElementSizeProp
textAlign?: string;
}
export interface PPTIconElement extends PPTElementBaseProps, PPTElementSizeProps {
type: 'icon';
color: string;
lockRatio: boolean;
svgCode: string;
rotate?: number;
shadow?: string;
}
export interface PPTLineElement extends PPTElementBaseProps {
type: 'line';
start: [number, number];
@ -78,23 +66,11 @@ export interface PPTLineElement extends PPTElementBaseProps {
lineType: string;
}
export interface BarChartSeries {
name: string;
data: number[];
}
export interface BarChartData {
axisData: string[];
series: BarChartSeries[];
}
export interface PieChartData {
name: string;
value: number
}
export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
type: 'chart';
chartType: string;
theme: string;
data: PieChartData[] | BarChartData;
data: Object;
}
export interface TableCell {
@ -111,19 +87,13 @@ export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProp
colSizes: number[];
data: TableCell[][];
}
export interface PPTIframeElement extends PPTElementBaseProps, PPTElementSizeProps, PPTElementBorderProps {
type: 'iframe';
src: string;
}
export type PPTElement = PPTTextElement |
PPTImageElement |
PPTShapeElement |
PPTIconElement |
PPTLineElement |
PPTChartElement |
PPTTableElement |
PPTIframeElement
PPTTableElement
export interface PPTAnimation {
elId: string;

View File

@ -169,6 +169,17 @@ export const copyText = (text: string) => {
})
}
// 读取剪贴板
export const readClipboard = () => {
if(navigator.clipboard) {
navigator.clipboard.readText().then(text => {
if(!text) return { err: '剪贴板为空或者不包含文本' }
return { text }
})
}
return { err: '浏览器不支持或禁止访问剪贴板' }
}
// 加密函数
export const encrypt = (msg: string) => {
return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()

View File

@ -0,0 +1,44 @@
import { PPTElement } from '@/types/slides'
import { ElementAlignCommand, ElementAlignCommands } from '@/types/edit'
import { getElementListRange } from './elementRange'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
// 将元素对齐到屏幕
export const alignElement = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[], command: ElementAlignCommand) => {
const viewportWidth = VIEWPORT_SIZE
const viewportHeight = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
const { minX, maxX, minY, maxY } = getElementListRange(activeElementList)
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
for(const element of copyOfElementList) {
if(!activeElementIdList.includes(element.elId)) continue
if(command === ElementAlignCommands.TOP) {
const offsetY = minY - 0
element.top = element.top - offsetY
}
else if(command === ElementAlignCommands.VERTICAL) {
const offsetY = minY + (maxY - minY) / 2 - viewportHeight / 2
element.top = element.top - offsetY
}
else if(command === ElementAlignCommands.BOTTOM) {
const offsetY = maxY - viewportHeight
element.top = element.top - offsetY
}
else if(command === ElementAlignCommands.LEFT) {
const offsetX = minX - 0
element.left = element.left - offsetX
}
else if(command === ElementAlignCommands.HORIZONTAL) {
const offsetX = minX + (maxX - minX) / 2 - viewportWidth / 2
element.left = element.left - offsetX
}
else if(command === ElementAlignCommands.RIGHT) {
const offsetX = maxX - viewportWidth
element.left = element.left - offsetX
}
}
return copyOfElementList
}

View File

@ -0,0 +1,41 @@
import { createRandomCode } from '@/utils/index'
import { PPTElement } from '@/types/slides'
// 组合元素为当前所有激活元素添加一个相同的groupId
export const combineElements = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[]) => {
if(!activeElementList.length) return null
let copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
const groupId = createRandomCode()
const combineElementList: PPTElement[] = []
for(const element of copyOfElementList) {
if(activeElementIdList.includes(element.elId)) {
element.groupId = groupId
combineElementList.push(element)
}
}
// 注意,组合元素的层级应该是连续的,所以需要获取该组元素中最顶层的元素,将组内其他成员从原位置移动到最顶层的元素的下面
const combineElementMaxIndex = copyOfElementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
const combineElementIdList = combineElementList.map(_element => _element.elId)
copyOfElementList = copyOfElementList.filter(_element => !combineElementIdList.includes(_element.elId))
const insertIndex = combineElementMaxIndex - combineElementList.length + 1
copyOfElementList.splice(insertIndex, 0, ...combineElementList)
return copyOfElementList
}
// 取消组合元素移除所有被激活元素的groupId
export const uncombineElements = (elementList: PPTElement[], activeElementList: PPTElement[], activeElementIdList: string[]) => {
if(!activeElementList.length) return null
const hasElementInGroup = activeElementList.some(item => item.groupId)
if(!hasElementInGroup) return null
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
for(const element of copyOfElementList) {
if(activeElementIdList.includes(element.elId) && element.groupId) delete element.groupId
}
return copyOfElementList
}

View File

@ -0,0 +1,34 @@
import { PPTElement } from '@/types/slides'
import { ElementLockCommand, ElementLockCommands } from '@/types/edit'
const lock = (copyOfElementList: PPTElement[], handleElement: PPTElement, activeElementIdList: string[]) => {
for(const element of copyOfElementList) {
if(activeElementIdList.includes(handleElement.elId)) element.isLock = true
}
return copyOfElementList
}
const unlock = (copyOfElementList: PPTElement[], handleElement: PPTElement) => {
if(handleElement.groupId) {
for(const element of copyOfElementList) {
if(element.groupId === handleElement.groupId) element.isLock = false
}
return copyOfElementList
}
for(const element of copyOfElementList) {
if(element.elId === handleElement.elId) {
element.isLock = false
break
}
}
return copyOfElementList
}
// 锁定&解锁 元素
export const lockElement = (elementList: PPTElement[], handleElement: PPTElement, activeElementIdList: string[], command: ElementLockCommand) => {
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
if(command === ElementLockCommands.LOCK) return lock(copyOfElementList, handleElement, activeElementIdList)
return unlock(copyOfElementList, handleElement)
}

View File

@ -0,0 +1,170 @@
import { PPTElement } from '@/types/slides'
import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
// 获取组合元素层级范围(组合成员中的最大层级和最小层级)
const getCombineElementIndexRange = (elementList: PPTElement[], combineElementList: PPTElement[]) => {
const minIndex = elementList.findIndex(_element => _element.elId === combineElementList[0].elId)
const maxIndex = elementList.findIndex(_element => _element.elId === combineElementList[combineElementList.length - 1].elId)
return { minIndex, maxIndex }
}
// 上移一层,返回移动后新的元素列表(下移一层逻辑类似)
const moveUpElement = (elementList: PPTElement[], element: PPTElement) => {
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
// 被操作的元素是组合元素成员
if(element.groupId) {
// 获取该组合元素全部成员,以及组合元素层级范围
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
const { minIndex, maxIndex } = getCombineElementIndexRange(elementList, combineElementList)
// 无法移动(已经处在顶层)
if(maxIndex === elementList.length - 1) return null
// 该组合元素上一层的元素以下简称为【元素next】
const nextElement = copyOfElementList[maxIndex + 1]
// 从元素列表中移除该组合元素全部成员
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
// 如果【元素next】也是组合元素成员另一个组合不是上面被移除的那一组以下简称为【组合next】
// 需要获取【组合next】全部成员的长度将上面移除的组合元素全部成员添加到【组合next】全部成员的上方
if(nextElement.groupId) {
const nextCombineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)
copyOfElementList.splice(minIndex + nextCombineElementList.length, 0, ...movedElementList)
}
// 如果【元素next】是单独的元素非组合成员将上面移除的组合元素全部成员添加到【元素next】上方
else copyOfElementList.splice(minIndex + 1, 0, ...movedElementList)
}
// 被操作的元素是单独的元素(非组合成员)
else {
// 元素在元素列表中的层级
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
// 无法移动(已经处在顶层)
if(elementIndex === elementList.length - 1) return null
// 上一层的元素以下简称为【元素next】
const nextElement = copyOfElementList[elementIndex + 1]
// 从元素列表中移除被操作的元素
const movedElement = copyOfElementList.splice(elementIndex, 1)[0]
// 如果【元素next】是组合元素成员
// 需要获取该组合全部成员的长度,将上面移除的元素添加到该组合全部成员的上方
if(nextElement.groupId) {
const combineElementList = copyOfElementList.filter(_element => _element.groupId === nextElement.groupId)
copyOfElementList.splice(elementIndex + combineElementList.length, 0, movedElement)
}
// 如果【元素next】是单独的元素非组合成员将上面移除的元素添加到【元素next】上方
else copyOfElementList.splice(elementIndex + 1, 0, movedElement)
}
return copyOfElementList
}
// 下移一层
const moveDownElement = (elementList: PPTElement[], element: PPTElement) => {
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
if(element.groupId) {
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
const { minIndex } = getCombineElementIndexRange(elementList, combineElementList)
if(minIndex === 0) return null
const prevElement = copyOfElementList[minIndex - 1]
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
if(prevElement.groupId) {
const prevCombineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)
copyOfElementList.splice(minIndex - prevCombineElementList.length, 0, ...movedElementList)
}
else copyOfElementList.splice(minIndex - 1, 0, ...movedElementList)
}
else {
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
if(elementIndex === 0) return null
const prevElement = copyOfElementList[elementIndex - 1]
const movedElement = copyOfElementList.splice(elementIndex, 1)[0]
if(prevElement.groupId) {
const combineElementList = copyOfElementList.filter(_element => _element.groupId === prevElement.groupId)
copyOfElementList.splice(elementIndex - combineElementList.length, 0, movedElement)
}
else copyOfElementList.splice(elementIndex - 1, 0, movedElement)
}
return copyOfElementList
}
// 置顶层,返回移动后新的元素列表(置底层逻辑类似)
const moveTopElement = (elementList: PPTElement[], element: PPTElement) => {
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
// 被操作的元素是组合元素成员
if(element.groupId) {
// 获取该组合元素全部成员,以及组合元素层级范围
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
const { minIndex, maxIndex } = getCombineElementIndexRange(elementList, combineElementList)
// 无法移动(已经处在顶层)
if(maxIndex === elementList.length - 1) return null
// 从元素列表中移除该组合元素全部成员,然后添加到元素列表最上方
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
copyOfElementList.push(...movedElementList)
}
// 被操作的元素是单独的元素(非组合成员)
else {
// 元素在元素列表中的层级
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
// 无法移动(已经处在顶层)
if(elementIndex === elementList.length - 1) return null
// 从元素列表中移除该元素,然后添加到元素列表最上方
copyOfElementList.splice(elementIndex, 1)
copyOfElementList.push(element)
}
return copyOfElementList
}
// 置底层
const moveBottomElement = (elementList: PPTElement[], element: PPTElement) => {
const copyOfElementList: PPTElement[] = JSON.parse(JSON.stringify(elementList))
if(element.groupId) {
const combineElementList = copyOfElementList.filter(_element => _element.groupId === element.groupId)
const { minIndex } = getCombineElementIndexRange(elementList, combineElementList)
if(minIndex === 0) return null
const movedElementList = copyOfElementList.splice(minIndex, combineElementList.length)
copyOfElementList.unshift(...movedElementList)
}
else {
const elementIndex = elementList.findIndex(item => item.elId === element.elId)
if(elementIndex === 0) return null
copyOfElementList.splice(elementIndex, 1)
copyOfElementList.unshift(element)
}
return copyOfElementList
}
export const setElementOrder = (elementList: PPTElement[], element: PPTElement, command: ElementOrderCommand) => {
let newElementList = null
if(command === ElementOrderCommands.UP) newElementList = moveUpElement(elementList, element)
else if(command === ElementOrderCommands.DOWN) newElementList = moveDownElement(elementList, element)
else if(command === ElementOrderCommands.TOP) newElementList = moveTopElement(elementList, element)
else if(command === ElementOrderCommands.BOTTOM) newElementList = moveBottomElement(elementList, element)
return newElementList
}

View File

@ -1,7 +1,7 @@
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
import { PPTElement, PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
// 获取矩形旋转后在画布中的位置范围
export const getRectRotatedRange = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement) => {
export const getRectRotatedRange = (element: PPTTextElement | PPTImageElement | PPTShapeElement) => {
const { left, top, width, height, rotate = 0 } = element
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2

View File

@ -1,4 +1,4 @@
import { PPTTextElement, PPTImageElement, PPTShapeElement, PPTIconElement } from '@/types/slides'
import { PPTTextElement, PPTImageElement, PPTShapeElement } from '@/types/slides'
import { OPERATE_KEYS } from '@/configs/element'
// 给定一个坐标,计算该坐标到(0, 0)点连线的弧度值
@ -11,7 +11,7 @@ export const getAngleFromCoordinate = (x: number, y: number) => {
}
// 计算元素被旋转一定角度后,八个操作点的新坐标
export const getRotateElementPoints = (element: PPTTextElement | PPTImageElement | PPTShapeElement | PPTIconElement, angle: number) => {
export const getRotateElementPoints = (element: PPTTextElement | PPTImageElement | PPTShapeElement, angle: number) => {
const { left, top, width, height } = element
const radius = Math.sqrt( Math.pow(width, 2) + Math.pow(height, 2) ) / 2

View File

@ -45,9 +45,6 @@ export default defineComponent({
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
const disableHotkeys = computed(() => store.state.disableHotkeys)
const save = () => {
message.success('save')
}
const copy = () => {
message.success('copy')
}
@ -90,10 +87,6 @@ export default defineComponent({
if(!editorAreaFocus.value && !thumbnailsFocus.value) return
if(ctrlKey && keyCode === KEYCODE.S) {
e.preventDefault()
save()
}
if(ctrlKey && keyCode === KEYCODE.C) {
e.preventDefault()
copy()

View File

@ -23,6 +23,16 @@
import { computed, defineComponent, PropType } from 'vue'
import { PPTElement } from '@/types/slides'
import {
ElementOrderCommand,
ElementOrderCommands,
ElementAlignCommand,
ElementAlignCommands,
ElementScaleHandler,
ElementLockCommand,
ElementLockCommands,
} from '@/types/edit'
import ImageElement from './ImageElement.index.vue'
import TextElement from './TextElement.index.vue'
@ -62,11 +72,11 @@ export default defineComponent({
required: true,
},
scaleElement: {
type: Function as PropType<(e: MouseEvent, element: PPTElement, direction: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9) => void>,
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: ElementScaleHandler) => void>,
required: true,
},
updateZIndex: {
type: Function as PropType<(element: PPTElement, operation: 'up' | 'down' | 'top' | 'bottom') => void>,
orderElement: {
type: Function as PropType<(element: PPTElement, command: ElementOrderCommand) => void>,
required: true,
},
combineElements: {
@ -78,7 +88,7 @@ export default defineComponent({
required: true,
},
alignElement: {
type: Function as PropType<(direction: 'top' | 'verticalCenter' | 'bottom' | 'left' | 'horizontalCenter' | 'right') => void>,
type: Function as PropType<(command: ElementAlignCommand) => void>,
required: true,
},
deleteElement: {
@ -86,7 +96,7 @@ export default defineComponent({
required: true,
},
lockElement: {
type: Function as PropType<(element: PPTElement, handle: 'lock' | 'unlock') => void>,
type: Function as PropType<(element: PPTElement, command: ElementLockCommand) => void>,
required: true,
},
copyElement: {
@ -112,7 +122,7 @@ export default defineComponent({
return [{
text: '解锁',
icon: 'icon-unlock',
action: () => props.lockElement(props.elementInfo, 'unlock'),
action: () => props.lockElement(props.elementInfo, ElementLockCommands.UNLOCK),
}]
}
@ -135,29 +145,29 @@ export default defineComponent({
icon: 'icon-top-layer',
disable: props.isMultiSelect && !props.elementInfo.groupId,
children: [
{ text: '置顶层', action: () => props.updateZIndex(props.elementInfo, 'top') },
{ text: '置底层', action: () => props.updateZIndex(props.elementInfo, 'bottom') },
{ text: '置顶层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.TOP) },
{ text: '置底层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.BOTTOM) },
{ divider: true },
{ text: '上移一层', action: () => props.updateZIndex(props.elementInfo, 'up') },
{ text: '下移一层', action: () => props.updateZIndex(props.elementInfo, 'down') },
{ text: '上移一层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.UP) },
{ text: '下移一层', action: () => props.orderElement(props.elementInfo, ElementOrderCommands.DOWN) },
],
},
{
text: '水平对齐',
icon: 'icon-align-left',
children: [
{ text: '水平居中', action: () => props.alignElement('horizontalCenter') },
{ text: '左对齐', action: () => props.alignElement('left') },
{ text: '右对齐', action: () => props.alignElement('right') },
{ text: '水平居中', action: () => props.alignElement(ElementAlignCommands.HORIZONTAL) },
{ text: '左对齐', action: () => props.alignElement(ElementAlignCommands.LEFT) },
{ text: '右对齐', action: () => props.alignElement(ElementAlignCommands.RIGHT) },
],
},
{
text: '垂直对齐',
icon: 'icon-align-bottom',
children: [
{ text: '垂直居中', action: () => props.alignElement('verticalCenter') },
{ text: '上对齐', action: () => props.alignElement('top') },
{ text: '下对齐', action: () => props.alignElement('bottom') },
{ text: '垂直居中', action: () => props.alignElement(ElementAlignCommands.VERTICAL) },
{ text: '上对齐', action: () => props.alignElement(ElementAlignCommands.TOP) },
{ text: '下对齐', action: () => props.alignElement(ElementAlignCommands.BOTTOM) },
],
},
{ divider: true },
@ -172,7 +182,7 @@ export default defineComponent({
text: '锁定',
subText: 'Ctrl + L',
icon: 'icon-lock',
action: () => props.lockElement(props.elementInfo, 'lock'),
action: () => props.lockElement(props.elementInfo, ElementLockCommands.LOCK),
},
{
text: '删除',