mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
update
This commit is contained in:
parent
6c65908a07
commit
ce9069d941
@ -1,12 +1,12 @@
|
|||||||
const DEFAULT_COLOR = '#41464b'
|
const DEFAULT_COLOR = '#41464b'
|
||||||
|
|
||||||
export enum ELEMENTS {
|
export enum ElementTypes {
|
||||||
text = '文本',
|
TEXT = '文本',
|
||||||
image = '图片',
|
IMAGE = '图片',
|
||||||
shape = '形状',
|
SHAPE = '形状',
|
||||||
line = '线条',
|
LINE = '线条',
|
||||||
chart = '图表',
|
CHART = '图表',
|
||||||
table = '表格',
|
TABLE = '表格',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_TEXT = {
|
export const DEFAULT_TEXT = {
|
||||||
|
@ -3,7 +3,7 @@ import { Slide } from '@/types/slides'
|
|||||||
export const slides: Slide[] = [
|
export const slides: Slide[] = [
|
||||||
{
|
{
|
||||||
id: 'xxx1',
|
id: 'xxx1',
|
||||||
background: ['solid', '#323f4f'],
|
background: ['solid', '#fff'],
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
elId: 'xxx1',
|
elId: 'xxx1',
|
||||||
|
@ -24,4 +24,8 @@ export enum MutationTypes {
|
|||||||
UNDO = 'undo',
|
UNDO = 'undo',
|
||||||
REDO = 'redo',
|
REDO = 'redo',
|
||||||
SET_HISTORY_RECORD_LENGTH = 'setHistoryRecordLength',
|
SET_HISTORY_RECORD_LENGTH = 'setHistoryRecordLength',
|
||||||
|
|
||||||
|
// keyboard
|
||||||
|
SET_CTRL_KEY_STATE = 'setCtrlKeyState',
|
||||||
|
SET_SHIFT_KEY_STATE = 'setShiftKeyState',
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ export type Getters = {
|
|||||||
handleElement(state: State): PPTElement | null;
|
handleElement(state: State): PPTElement | null;
|
||||||
canUndo(state: State): boolean;
|
canUndo(state: State): boolean;
|
||||||
canRedo(state: State): boolean;
|
canRedo(state: State): boolean;
|
||||||
|
ctrlOrShiftKeyActive(state: State): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getters: Getters = {
|
export const getters: Getters = {
|
||||||
@ -45,4 +46,8 @@ export const getters: Getters = {
|
|||||||
canRedo(state) {
|
canRedo(state) {
|
||||||
return state.cursor < state.historyRecordLength - 1
|
return state.cursor < state.historyRecordLength - 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ctrlOrShiftKeyActive(state) {
|
||||||
|
return state.ctrlKeyState || state.shiftKeyState
|
||||||
|
},
|
||||||
}
|
}
|
@ -36,6 +36,9 @@ export type Mutations = {
|
|||||||
[MutationTypes.UNDO](state: State): void;
|
[MutationTypes.UNDO](state: State): void;
|
||||||
[MutationTypes.REDO](state: State): void;
|
[MutationTypes.REDO](state: State): void;
|
||||||
[MutationTypes.SET_HISTORY_RECORD_LENGTH](state: State, length: number): void;
|
[MutationTypes.SET_HISTORY_RECORD_LENGTH](state: State, length: number): void;
|
||||||
|
|
||||||
|
[MutationTypes.SET_CTRL_KEY_STATE](state: State, isActive: boolean): void;
|
||||||
|
[MutationTypes.SET_SHIFT_KEY_STATE](state: State, isActive: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations: Mutations = {
|
export const mutations: Mutations = {
|
||||||
@ -143,4 +146,13 @@ export const mutations: Mutations = {
|
|||||||
[MutationTypes.SET_HISTORY_RECORD_LENGTH](state, length) {
|
[MutationTypes.SET_HISTORY_RECORD_LENGTH](state, length) {
|
||||||
state.historyRecordLength = length
|
state.historyRecordLength = length
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// keyBoard
|
||||||
|
|
||||||
|
[MutationTypes.SET_CTRL_KEY_STATE](state, isActive) {
|
||||||
|
state.ctrlKeyState = isActive
|
||||||
|
},
|
||||||
|
[MutationTypes.SET_SHIFT_KEY_STATE](state, isActive) {
|
||||||
|
state.shiftKeyState = isActive
|
||||||
|
},
|
||||||
}
|
}
|
@ -15,6 +15,8 @@ export type State = {
|
|||||||
slideIndex: number;
|
slideIndex: number;
|
||||||
cursor: number;
|
cursor: number;
|
||||||
historyRecordLength: number;
|
historyRecordLength: number;
|
||||||
|
ctrlKeyState: boolean;
|
||||||
|
shiftKeyState: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const state: State = {
|
export const state: State = {
|
||||||
@ -30,4 +32,6 @@ export const state: State = {
|
|||||||
slideIndex: 0,
|
slideIndex: 0,
|
||||||
cursor: -1,
|
cursor: -1,
|
||||||
historyRecordLength: 0,
|
historyRecordLength: 0,
|
||||||
|
ctrlKeyState: false,
|
||||||
|
shiftKeyState: false,
|
||||||
}
|
}
|
@ -1,5 +1,14 @@
|
|||||||
export type ElementType = 'text' | 'image' | 'shape' | 'line' | 'chart' | 'table'
|
export type ElementType = 'text' | 'image' | 'shape' | 'line' | 'chart' | 'table'
|
||||||
|
|
||||||
|
export enum ElementTypes {
|
||||||
|
TEXT = 'text',
|
||||||
|
IMAGE = 'image',
|
||||||
|
SHAPE = 'shape',
|
||||||
|
LINE = 'line',
|
||||||
|
CHART = 'chart',
|
||||||
|
TABLE = 'table',
|
||||||
|
}
|
||||||
|
|
||||||
export interface PPTElementBaseProps {
|
export interface PPTElementBaseProps {
|
||||||
elId: string;
|
elId: string;
|
||||||
isLock: boolean;
|
isLock: boolean;
|
||||||
@ -41,7 +50,7 @@ export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProp
|
|||||||
range: [[number, number], [number, number]];
|
range: [[number, number], [number, number]];
|
||||||
shape: 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'pentagon' | 'rhombus' | 'star';
|
shape: 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'pentagon' | 'rhombus' | 'star';
|
||||||
};
|
};
|
||||||
flip?: { x?: number, y?: number };
|
flip?: { x?: number; y?: number };
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +79,7 @@ export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProp
|
|||||||
type: 'chart';
|
type: 'chart';
|
||||||
chartType: string;
|
chartType: string;
|
||||||
theme: string;
|
theme: string;
|
||||||
data: Object;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableElementCell {
|
export interface TableElementCell {
|
||||||
|
132
src/views/Editor/Canvas/MultiSelectOperate.vue
Normal file
132
src/views/Editor/Canvas/MultiSelectOperate.vue
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="multi-select-operate"
|
||||||
|
:style="{
|
||||||
|
left: minX + 'px',
|
||||||
|
top: minY + 'px',
|
||||||
|
transform: `scale(${1 / canvasScale})`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<BorderLine v-for="line in borderLines" :key="line.type" :type="line.type" :style="line.style" />
|
||||||
|
|
||||||
|
<template v-if="!disableResizablePoint">
|
||||||
|
<ResizablePoint
|
||||||
|
v-for="point in resizablePoints"
|
||||||
|
:key="point.type"
|
||||||
|
:type="point.type"
|
||||||
|
:style="point.style"
|
||||||
|
@mousedown.stop="scaleMultiElement($event, { minX, maxX, minY, maxY }, point.direction)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, reactive, PropType, watch, toRefs, onMounted } from 'vue'
|
||||||
|
import { OPERATE_KEYS } from '@/configs/element'
|
||||||
|
import { PPTElement, ElementTypes } from '@/types/slides'
|
||||||
|
import { getElementListRange } from './utils/elementRange'
|
||||||
|
import { ElementScaleHandler, OperateResizablePointTypes, OperateBorderLineTypes } from '@/types/edit'
|
||||||
|
|
||||||
|
import ResizablePoint from '@/views/_common/_operate/ResizablePoint.vue'
|
||||||
|
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||||
|
|
||||||
|
interface Range {
|
||||||
|
minX: number;
|
||||||
|
maxX: number;
|
||||||
|
minY: number;
|
||||||
|
maxY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'multi-select-operate',
|
||||||
|
components: {
|
||||||
|
ResizablePoint,
|
||||||
|
BorderLine,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
canvasScale: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
activeElementList: {
|
||||||
|
type: Array as PropType<PPTElement[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
scaleMultiElement: {
|
||||||
|
type: Function as PropType<(e: MouseEvent, range: Range, command: ElementScaleHandler) => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const range = reactive({
|
||||||
|
minX: 0,
|
||||||
|
maxX: 0,
|
||||||
|
minY: 0,
|
||||||
|
maxY: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
const width = computed(() => (range.maxX - range.minX) * props.canvasScale)
|
||||||
|
const height = computed(() => (range.maxY - range.minY) * props.canvasScale)
|
||||||
|
|
||||||
|
const resizablePoints = computed(() => {
|
||||||
|
return [
|
||||||
|
{ type: OperateResizablePointTypes.TL, direction: OPERATE_KEYS.LEFT_TOP, style: {} },
|
||||||
|
{ type: OperateResizablePointTypes.TC, direction: OPERATE_KEYS.TOP, style: {left: width.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.TR, direction: OPERATE_KEYS.RIGHT_TOP, style: {left: width.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.ML, direction: OPERATE_KEYS.LEFT, style: {top: height.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.MR, direction: OPERATE_KEYS.RIGHT, style: {left: width.value + 'px', top: height.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BL, direction: OPERATE_KEYS.LEFT_BOTTOM, style: {top: height.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BC, direction: OPERATE_KEYS.BOTTOM, style: {left: width.value / 2 + 'px', top: height.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BR, direction: OPERATE_KEYS.RIGHT_BOTTOM, style: {left: width.value + 'px', top: height.value + 'px'} },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const borderLines = computed(() => {
|
||||||
|
return [
|
||||||
|
{ type: OperateBorderLineTypes.T, style: {width: width.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.B, style: {top: height.value + 'px', width: width.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.L, style: {height: height.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.R, style: {left: width.value + 'px', height: height.value + 'px'} },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const disableResizablePoint = computed(() => {
|
||||||
|
return props.activeElementList.some(item => {
|
||||||
|
if(
|
||||||
|
(item.type === ElementTypes.IMAGE || item.type === ElementTypes.SHAPE) &&
|
||||||
|
!item.rotate
|
||||||
|
) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const setRange = () => {
|
||||||
|
const { minX, maxX, minY, maxY } = getElementListRange(props.activeElementList)
|
||||||
|
range.minX = minX
|
||||||
|
range.maxX = maxX
|
||||||
|
range.minY = minY
|
||||||
|
range.maxY = maxY
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(setRange)
|
||||||
|
watch(props.activeElementList, setRange)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toRefs(range),
|
||||||
|
borderLines,
|
||||||
|
disableResizablePoint,
|
||||||
|
resizablePoints,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.multi-select-operate {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
@ -36,6 +36,13 @@
|
|||||||
:type="line.type" :axis="line.axis" :length="line.length"
|
:type="line.type" :axis="line.axis" :length="line.length"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MultiSelectOperate
|
||||||
|
v-if="activeElementIdList.length > 1"
|
||||||
|
:activeElementList="activeElementList"
|
||||||
|
:canvasScale="canvasScale"
|
||||||
|
:scaleMultiElement="scaleMultiElement"
|
||||||
|
/>
|
||||||
|
|
||||||
<EditableElement
|
<EditableElement
|
||||||
v-for="(element, index) in elementList"
|
v-for="(element, index) in elementList"
|
||||||
:key="element.elId"
|
:key="element.elId"
|
||||||
@ -63,13 +70,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, reactive, ref, watch } from 'vue'
|
import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
|
import uniq from 'lodash/uniq'
|
||||||
import { State } from '@/store/state'
|
import { State } from '@/store/state'
|
||||||
import { MutationTypes } from '@/store/constants'
|
import { MutationTypes } from '@/store/constants'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
|
||||||
import { getImageDataURL } from '@/utils/image'
|
import { getImageDataURL } from '@/utils/image'
|
||||||
|
import { getElementRange } from './utils/elementRange'
|
||||||
|
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
|
||||||
import useDropImage from '@/hooks/useDropImage'
|
import useDropImage from '@/hooks/useDropImage'
|
||||||
import useSetViewportSize from './hooks/useSetViewportSize'
|
import useSetViewportSize from './hooks/useSetViewportSize'
|
||||||
@ -77,6 +88,7 @@ import useSetViewportSize from './hooks/useSetViewportSize'
|
|||||||
import EditableElement from '@/views/_common/_element/EditableElement.vue'
|
import EditableElement from '@/views/_common/_element/EditableElement.vue'
|
||||||
import MouseSelection from './MouseSelection.vue'
|
import MouseSelection from './MouseSelection.vue'
|
||||||
import SlideBackground from './SlideBackground.vue'
|
import SlideBackground from './SlideBackground.vue'
|
||||||
|
import MultiSelectOperate from './MultiSelectOperate.vue'
|
||||||
import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
|
import AlignmentLine, { AlignmentLineProps } from './AlignmentLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -85,22 +97,29 @@ export default defineComponent({
|
|||||||
EditableElement,
|
EditableElement,
|
||||||
MouseSelection,
|
MouseSelection,
|
||||||
SlideBackground,
|
SlideBackground,
|
||||||
|
MultiSelectOperate,
|
||||||
AlignmentLine,
|
AlignmentLine,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<State>()
|
const store = useStore<State>()
|
||||||
const elementList = computed(() => {
|
|
||||||
const currentSlide = store.getters.currentSlide
|
|
||||||
return currentSlide ? JSON.parse(JSON.stringify(currentSlide.elements)) : []
|
|
||||||
})
|
|
||||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
|
||||||
const handleElementId = computed(() => store.state.handleElementId)
|
|
||||||
const activeGroupElementId = ref('')
|
|
||||||
|
|
||||||
|
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||||
|
const activeElementList = computed(() => store.getters.activeElementList)
|
||||||
|
const handleElementId = computed(() => store.state.handleElementId)
|
||||||
|
const ctrlOrShiftKeyActive = computed(() => store.getters.ctrlOrShiftKeyActive)
|
||||||
|
|
||||||
|
const activeGroupElementId = ref('')
|
||||||
const viewportRef = ref<HTMLElement | null>(null)
|
const viewportRef = ref<HTMLElement | null>(null)
|
||||||
const isShowGridLines = ref(false)
|
const isShowGridLines = ref(false)
|
||||||
const alignmentLines = ref<AlignmentLineProps[]>([])
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
||||||
|
|
||||||
const currentSlide = computed(() => store.getters.currentSlide)
|
const currentSlide = computed(() => store.getters.currentSlide)
|
||||||
|
const elementList = ref<PPTElement[]>([])
|
||||||
|
const setLocalElementList = () => {
|
||||||
|
elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
||||||
|
}
|
||||||
|
onMounted(setLocalElementList)
|
||||||
|
watch(currentSlide, setLocalElementList)
|
||||||
|
|
||||||
const dropImageFile = useDropImage(viewportRef)
|
const dropImageFile = useDropImage(viewportRef)
|
||||||
watch(dropImageFile, () => {
|
watch(dropImageFile, () => {
|
||||||
@ -180,6 +199,67 @@ export default defineComponent({
|
|||||||
document.onmouseup = null
|
document.onmouseup = null
|
||||||
isMouseDown = false
|
isMouseDown = false
|
||||||
|
|
||||||
|
// 计算当前页面中的每一个元素是否处在鼠标选择范围中(必须完全包裹)
|
||||||
|
// 将选择范围中的元素添加为激活元素
|
||||||
|
let inRangeElementList: PPTElement[] = []
|
||||||
|
for(let i = 0; i < elementList.value.length; i++) {
|
||||||
|
const element = elementList.value[i]
|
||||||
|
const mouseSelectionLeft = mouseSelectionState.left
|
||||||
|
const mouseSelectionTop = mouseSelectionState.top
|
||||||
|
const mouseSelectionWidth = mouseSelectionState.width
|
||||||
|
const mouseSelectionHeight = mouseSelectionState.height
|
||||||
|
|
||||||
|
const quadrant = mouseSelectionState.quadrant
|
||||||
|
|
||||||
|
const { minX, maxX, minY, maxY } = getElementRange(element)
|
||||||
|
|
||||||
|
let isInclude = false
|
||||||
|
if(quadrant === 4) {
|
||||||
|
isInclude = minX > mouseSelectionLeft &&
|
||||||
|
maxX < mouseSelectionLeft + mouseSelectionWidth &&
|
||||||
|
minY > mouseSelectionTop &&
|
||||||
|
maxY < mouseSelectionTop + mouseSelectionHeight
|
||||||
|
}
|
||||||
|
else if(quadrant === 1) {
|
||||||
|
isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) &&
|
||||||
|
maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth &&
|
||||||
|
minY > (mouseSelectionTop - mouseSelectionHeight) &&
|
||||||
|
maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight
|
||||||
|
}
|
||||||
|
else if(quadrant === 2) {
|
||||||
|
isInclude = minX > mouseSelectionLeft &&
|
||||||
|
maxX < mouseSelectionLeft + mouseSelectionWidth &&
|
||||||
|
minY > (mouseSelectionTop - mouseSelectionHeight) &&
|
||||||
|
maxY < (mouseSelectionTop - mouseSelectionHeight) + mouseSelectionHeight
|
||||||
|
}
|
||||||
|
else if(quadrant === 3) {
|
||||||
|
isInclude = minX > (mouseSelectionLeft - mouseSelectionWidth) &&
|
||||||
|
maxX < (mouseSelectionLeft - mouseSelectionWidth) + mouseSelectionWidth &&
|
||||||
|
minY > mouseSelectionTop &&
|
||||||
|
maxY < mouseSelectionTop + mouseSelectionHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
// 被锁定的元素除外
|
||||||
|
if(isInclude && !element.isLock) inRangeElementList.push(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于组合元素成员,必须所有成员都在选择范围中才算被选中
|
||||||
|
inRangeElementList = inRangeElementList.filter(inRangeElement => {
|
||||||
|
if(inRangeElement.groupId) {
|
||||||
|
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
||||||
|
const groupElementList = elementList.value.filter(element => element.groupId === inRangeElement.groupId)
|
||||||
|
return groupElementList.every(groupElement => inRangeElementIdList.includes(groupElement.elId))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
const inRangeElementIdList = inRangeElementList.map(inRangeElement => inRangeElement.elId)
|
||||||
|
|
||||||
|
// 原本就存在激活元素(可能需要清空),或者本次选择了至少一个元素(可能需要选择),才会具体更新激活元素状态
|
||||||
|
// 否则不做多余的激活元素状态更新(原本就没有激活元素,本次也没有选择任何元素,只是点击了一下空白区域,状态为:空 -> 空)
|
||||||
|
if(activeElementIdList.value.length > 0 || inRangeElementIdList.length) {
|
||||||
|
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, inRangeElementIdList)
|
||||||
|
}
|
||||||
|
|
||||||
mouseSelectionState.isShow = false
|
mouseSelectionState.isShow = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,7 +267,7 @@ export default defineComponent({
|
|||||||
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
||||||
|
|
||||||
const handleClickBlankArea = (e: MouseEvent) => {
|
const handleClickBlankArea = (e: MouseEvent) => {
|
||||||
updateMouseSelection(e)
|
if(!ctrlOrShiftKeyActive.value) updateMouseSelection(e)
|
||||||
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,8 +275,76 @@ export default defineComponent({
|
|||||||
if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
|
if(editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectElement = () => {
|
const moveElement = (e: MouseEvent, element: PPTElement) => {
|
||||||
console.log('selectElement')
|
console.log(e, element)
|
||||||
|
}
|
||||||
|
const selectElement = (e: MouseEvent, element: PPTElement, canMove = true) => {
|
||||||
|
if(!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
||||||
|
|
||||||
|
// 如果被点击的元素处于未激活状态,则将他设置为激活元素(单选),或者加入到激活元素中(多选)
|
||||||
|
if(!activeElementIdList.value.includes(element.elId)) {
|
||||||
|
let newActiveIdList: string[] = []
|
||||||
|
|
||||||
|
if(ctrlOrShiftKeyActive.value) {
|
||||||
|
newActiveIdList = [...activeElementIdList.value, element.elId]
|
||||||
|
}
|
||||||
|
else newActiveIdList = [element.elId]
|
||||||
|
|
||||||
|
// 同时如果该元素是分组成员,需要将和他同组的元素一起激活
|
||||||
|
if(element.groupId) {
|
||||||
|
const groupMembersId: string[] = []
|
||||||
|
elementList.value.forEach((el: PPTElement) => {
|
||||||
|
if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
||||||
|
})
|
||||||
|
newActiveIdList = [...newActiveIdList, ...groupMembersId]
|
||||||
|
}
|
||||||
|
|
||||||
|
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, uniq(newActiveIdList))
|
||||||
|
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果被点击的元素已激活,且按下了多选按钮,则取消其激活状态(除非该元素或分组是最后的一个激活元素)
|
||||||
|
else if(ctrlOrShiftKeyActive.value) {
|
||||||
|
let newActiveIdList: string[] = []
|
||||||
|
|
||||||
|
// 同时如果该元素是分组成员,需要将和他同组的元素一起取消
|
||||||
|
if(element.groupId) {
|
||||||
|
const groupMembersId: string[] = []
|
||||||
|
elementList.value.forEach((el: PPTElement) => {
|
||||||
|
if(el.groupId === element.groupId) groupMembersId.push(el.elId)
|
||||||
|
})
|
||||||
|
newActiveIdList = activeElementIdList.value.filter(elId => !groupMembersId.includes(elId))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
newActiveIdList = activeElementIdList.value.filter(elId => elId !== element.elId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newActiveIdList.length > 0) {
|
||||||
|
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, newActiveIdList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果被点击的元素已激活,且没有按下多选按钮,且该元素不是当前操作元素,则将其设置为当前操作元素
|
||||||
|
else if(handleElementId.value !== element.elId) {
|
||||||
|
store.commit(MutationTypes.SET_HANDLE_ELEMENT_ID, element.elId)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(activeGroupElementId.value !== element.elId && element.groupId) {
|
||||||
|
const startPageX = e.pageX
|
||||||
|
const startPageY = e.pageY
|
||||||
|
|
||||||
|
;(e.target as HTMLElement).onmouseup = (e: MouseEvent) => {
|
||||||
|
const currentPageX = e.pageX
|
||||||
|
const currentPageY = e.pageY
|
||||||
|
|
||||||
|
if(startPageX === currentPageX && startPageY === currentPageY) {
|
||||||
|
activeGroupElementId.value = element.elId
|
||||||
|
;(e.target as HTMLElement).onmouseup = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(canMove) moveElement(e, element)
|
||||||
}
|
}
|
||||||
const rotateElement = () => {
|
const rotateElement = () => {
|
||||||
console.log('rotateElement')
|
console.log('rotateElement')
|
||||||
@ -204,6 +352,9 @@ export default defineComponent({
|
|||||||
const scaleElement = () => {
|
const scaleElement = () => {
|
||||||
console.log('scaleElement')
|
console.log('scaleElement')
|
||||||
}
|
}
|
||||||
|
const scaleMultiElement = () => {
|
||||||
|
console.log('scaleMultiElement')
|
||||||
|
}
|
||||||
const orderElement = () => {
|
const orderElement = () => {
|
||||||
console.log('orderElement')
|
console.log('orderElement')
|
||||||
}
|
}
|
||||||
@ -248,6 +399,7 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
elementList,
|
elementList,
|
||||||
activeElementIdList,
|
activeElementIdList,
|
||||||
|
activeElementList,
|
||||||
handleElementId,
|
handleElementId,
|
||||||
activeGroupElementId,
|
activeGroupElementId,
|
||||||
canvasRef,
|
canvasRef,
|
||||||
@ -263,6 +415,7 @@ export default defineComponent({
|
|||||||
selectElement,
|
selectElement,
|
||||||
rotateElement,
|
rotateElement,
|
||||||
scaleElement,
|
scaleElement,
|
||||||
|
scaleMultiElement,
|
||||||
orderElement,
|
orderElement,
|
||||||
combineElements,
|
combineElements,
|
||||||
uncombineElements,
|
uncombineElements,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, defineComponent, onMounted, onUnmounted } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { State } from '@/store/state'
|
import { State } from '@/store/state'
|
||||||
import { KEYCODE } from '@/configs/keyCode'
|
import { KEYCODE } from '@/configs/keyCode'
|
||||||
@ -27,6 +27,7 @@ import Canvas from './Canvas/index.vue'
|
|||||||
import CanvasTool from './CanvasTool/index.vue'
|
import CanvasTool from './CanvasTool/index.vue'
|
||||||
import Thumbnails from './Thumbnails/index.vue'
|
import Thumbnails from './Thumbnails/index.vue'
|
||||||
import Toolbar from './Toolbar/index.vue'
|
import Toolbar from './Toolbar/index.vue'
|
||||||
|
import { MutationTypes } from '@/store/constants'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editor',
|
name: 'editor',
|
||||||
@ -38,10 +39,11 @@ export default defineComponent({
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const ctrlKeyDown = ref(false)
|
|
||||||
const shiftKeyDown = ref(false)
|
|
||||||
|
|
||||||
const store = useStore<State>()
|
const store = useStore<State>()
|
||||||
|
|
||||||
|
const ctrlKeyActive = computed(() => store.state.ctrlKeyState)
|
||||||
|
const shiftKeyActive = computed(() => store.state.shiftKeyState)
|
||||||
|
|
||||||
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
||||||
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
|
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
|
||||||
const disableHotkeys = computed(() => store.state.disableHotkeys)
|
const disableHotkeys = computed(() => store.state.disableHotkeys)
|
||||||
@ -83,8 +85,8 @@ export default defineComponent({
|
|||||||
const keydownListener = (e: KeyboardEvent) => {
|
const keydownListener = (e: KeyboardEvent) => {
|
||||||
const { keyCode, ctrlKey, shiftKey } = e
|
const { keyCode, ctrlKey, shiftKey } = e
|
||||||
|
|
||||||
if(ctrlKey && !ctrlKeyDown.value) ctrlKeyDown.value = true
|
if(ctrlKey && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
|
||||||
if(shiftKey && !shiftKeyDown.value) shiftKeyDown.value = true
|
if(shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)
|
||||||
|
|
||||||
if(!editorAreaFocus.value && !thumbnailsFocus.value) return
|
if(!editorAreaFocus.value && !thumbnailsFocus.value) return
|
||||||
|
|
||||||
@ -147,8 +149,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const keyupListener = () => {
|
const keyupListener = () => {
|
||||||
if(ctrlKeyDown.value) ctrlKeyDown.value = false
|
if(ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, false)
|
||||||
if(shiftKeyDown.value) shiftKeyDown.value = false
|
if(shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteImageFile = (imageFile: File) => {
|
const pasteImageFile = (imageFile: File) => {
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<div
|
<div
|
||||||
class="operate"
|
class="operate"
|
||||||
:class="{
|
:class="{
|
||||||
'show': isActive,
|
'active': isActive,
|
||||||
'multi-select': isMultiSelect && isActive,
|
'multi-select': isMultiSelect && isActive,
|
||||||
'selected': isHandleEl
|
'selected': isHandleEl
|
||||||
}"
|
}"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user