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
c218a4ba5a
commit
a7176f8be6
@ -1,24 +1,30 @@
|
|||||||
|
export enum ClipPathTypes {
|
||||||
|
RECT = 'rect',
|
||||||
|
ELLIPSE = 'ellipse',
|
||||||
|
POLYGON = 'polygon',
|
||||||
|
}
|
||||||
|
|
||||||
export const CLIPPATHS = {
|
export const CLIPPATHS = {
|
||||||
rect: {
|
rect: {
|
||||||
name: '矩形',
|
name: '矩形',
|
||||||
type: 'rect',
|
type: ClipPathTypes.RECT,
|
||||||
radius: '0',
|
radius: '0',
|
||||||
style: '',
|
style: '',
|
||||||
},
|
},
|
||||||
roundRect: {
|
roundRect: {
|
||||||
name: '圆角矩形',
|
name: '圆角矩形',
|
||||||
type: 'rect',
|
type: ClipPathTypes.RECT,
|
||||||
radius: '10%',
|
radius: '10%',
|
||||||
style: 'inset(0 0 0 0 round 10% 10% 10% 10%)',
|
style: 'inset(0 0 0 0 round 10% 10% 10% 10%)',
|
||||||
},
|
},
|
||||||
ellipse: {
|
ellipse: {
|
||||||
name: '圆形',
|
name: '圆形',
|
||||||
type: 'ellipse',
|
type: ClipPathTypes.ELLIPSE,
|
||||||
style: 'ellipse(50% 50% at 50% 50%)',
|
style: 'ellipse(50% 50% at 50% 50%)',
|
||||||
},
|
},
|
||||||
triangle: {
|
triangle: {
|
||||||
name: '三角形',
|
name: '三角形',
|
||||||
type: 'polygon',
|
type: ClipPathTypes.POLYGON,
|
||||||
style: 'polygon(50% 0%, 0% 100%, 100% 100%)',
|
style: 'polygon(50% 0%, 0% 100%, 100% 100%)',
|
||||||
createPath: (width: number, height: number) => {
|
createPath: (width: number, height: number) => {
|
||||||
return `M ${width / 2} 0 L 0 ${height} L ${width} ${height} Z`
|
return `M ${width / 2} 0 L 0 ${height} L ${width} ${height} Z`
|
||||||
@ -26,7 +32,7 @@ export const CLIPPATHS = {
|
|||||||
},
|
},
|
||||||
pentagon: {
|
pentagon: {
|
||||||
name: '五边形',
|
name: '五边形',
|
||||||
type: 'polygon',
|
type: ClipPathTypes.POLYGON,
|
||||||
style: 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)',
|
style: 'polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%)',
|
||||||
createPath: (width: number, height: number) => {
|
createPath: (width: number, height: number) => {
|
||||||
return `M ${width / 2} 0 L ${width} ${0.38 * height} L ${0.82 * width} ${height} L ${0.18 * width} ${height} L 0 ${0.38 * height} Z`
|
return `M ${width / 2} 0 L ${width} ${0.38 * height} L ${0.82 * width} ${height} L ${0.18 * width} ${height} L 0 ${0.38 * height} Z`
|
||||||
@ -34,7 +40,7 @@ export const CLIPPATHS = {
|
|||||||
},
|
},
|
||||||
rhombus: {
|
rhombus: {
|
||||||
name: '菱形',
|
name: '菱形',
|
||||||
type: 'polygon',
|
type: ClipPathTypes.POLYGON,
|
||||||
style: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)',
|
style: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)',
|
||||||
createPath: (width: number, height: number) => {
|
createPath: (width: number, height: number) => {
|
||||||
return `M ${width / 2} 0 L ${width} ${height / 2} L ${width / 2} ${height} L 0 ${height / 2} Z`
|
return `M ${width / 2} 0 L ${width} ${height / 2} L ${width / 2} ${height} L 0 ${height / 2} Z`
|
||||||
@ -42,7 +48,7 @@ export const CLIPPATHS = {
|
|||||||
},
|
},
|
||||||
star: {
|
star: {
|
||||||
name: '五角星',
|
name: '五角星',
|
||||||
type: 'polygon',
|
type: ClipPathTypes.POLYGON,
|
||||||
style: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
style: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
||||||
createPath: (width: number, height: number) => {
|
createPath: (width: number, height: number) => {
|
||||||
return `M ${width / 2} 0 L ${0.61 * width} ${0.35 * height} L ${0.98 * width} ${0.35 * height} L ${0.68 * width} ${0.57 * height} L ${0.79 * width} ${0.91 * height} L ${0.50 * width} ${0.70 * height} L ${0.21 * width} ${0.91 * height} L ${0.32 * width} ${0.57 * height} L ${0.02 * width} ${0.35 * height} L ${0.39 * width} ${0.35 * height} Z`
|
return `M ${width / 2} 0 L ${0.61 * width} ${0.35 * height} L ${0.98 * width} ${0.35 * height} L ${0.68 * width} ${0.57 * height} L ${0.79 * width} ${0.91 * height} L ${0.50 * width} ${0.70 * height} L ${0.21 * width} ${0.91 * height} L ${0.32 * width} ${0.57 * height} L ${0.02 * width} ${0.35 * height} L ${0.39 * width} ${0.35 * height} Z`
|
||||||
|
@ -25,4 +25,27 @@ export type ElementLockCommand = 'lock' | 'unlock'
|
|||||||
export enum ElementLockCommands {
|
export enum ElementLockCommands {
|
||||||
LOCK = 'lock',
|
LOCK = 'lock',
|
||||||
UNLOCK = 'unlock',
|
UNLOCK = 'unlock',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperateBorderLineType = 't' | 'b' | 'l' | 'r'
|
||||||
|
|
||||||
|
export enum OperateBorderLineTypes {
|
||||||
|
T = 't',
|
||||||
|
B = 'b',
|
||||||
|
L = 'l',
|
||||||
|
R = 'r',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OperateResizablePointType = 't-l' | 't-c' | 't-r' | 'm-l' | 'm-r' | 'b-l' | 'b-c' | 'b-r' | 'any'
|
||||||
|
|
||||||
|
export enum OperateResizablePointTypes {
|
||||||
|
TL = 't-l',
|
||||||
|
TC = 't-c',
|
||||||
|
TR = 't-r',
|
||||||
|
ML = 'm-l',
|
||||||
|
MR = 'm-r',
|
||||||
|
BL = 'b-l',
|
||||||
|
BC = 'b-c',
|
||||||
|
BR = 'b-r',
|
||||||
|
ANY = 'any',
|
||||||
}
|
}
|
@ -39,9 +39,9 @@ export interface PPTImageElement extends PPTElementBaseProps, PPTElementSizeProp
|
|||||||
filter?: string;
|
filter?: string;
|
||||||
clip?: {
|
clip?: {
|
||||||
range: [[number, number], [number, number]];
|
range: [[number, number], [number, number]];
|
||||||
shape: string;
|
shape: 'rect' | 'ellipse' | 'polygon';
|
||||||
};
|
};
|
||||||
flip?: string;
|
flip?: { x?: number, y?: number };
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="editable-element"
|
class="editable-element"
|
||||||
|
ref="elementRef"
|
||||||
:id="'editable-element-' + elementInfo.elId"
|
:id="'editable-element-' + elementInfo.elId"
|
||||||
:style="{ zIndex: elementIndex }"
|
:style="{ zIndex: elementIndex }"
|
||||||
>
|
>
|
||||||
@ -10,7 +11,9 @@
|
|||||||
:canvasScale="canvasScale"
|
:canvasScale="canvasScale"
|
||||||
:isActive="isActive"
|
:isActive="isActive"
|
||||||
:isHandleEl="isHandleEl"
|
:isHandleEl="isHandleEl"
|
||||||
|
:isActiveGroupElement="isActiveGroupElement"
|
||||||
:isMultiSelect="isMultiSelect"
|
:isMultiSelect="isMultiSelect"
|
||||||
|
:animationIndex="-1"
|
||||||
:selectElement="selectElement"
|
:selectElement="selectElement"
|
||||||
:rotateElement="rotateElement"
|
:rotateElement="rotateElement"
|
||||||
:scaleElement="scaleElement"
|
:scaleElement="scaleElement"
|
||||||
@ -59,12 +62,16 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
isActiveGroupElement: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
isMultiSelect: {
|
isMultiSelect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
selectElement: {
|
selectElement: {
|
||||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove: boolean) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
rotateElement: {
|
rotateElement: {
|
||||||
|
@ -58,6 +58,16 @@ interface ClipData {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClipedEmitData {
|
||||||
|
range: ClipDataRange;
|
||||||
|
position: {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
|
type ScaleClipRangeType = 't-l' | 't-r' | 'b-l' | 'b-r'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -193,10 +203,12 @@ export default defineComponent({
|
|||||||
width: (topImgWrapperPosition.width - 100) / 100 * props.width,
|
width: (topImgWrapperPosition.width - 100) / 100 * props.width,
|
||||||
height: (topImgWrapperPosition.height - 100) / 100 * props.height,
|
height: (topImgWrapperPosition.height - 100) / 100 * props.height,
|
||||||
}
|
}
|
||||||
emit('clip', {
|
|
||||||
|
const clipedEmitData: ClipedEmitData = {
|
||||||
range: currentRange.value,
|
range: currentRange.value,
|
||||||
position,
|
position,
|
||||||
})
|
}
|
||||||
|
emit('clip', clipedEmitData)
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboardClip = (e: KeyboardEvent) => {
|
const keyboardClip = (e: KeyboardEvent) => {
|
||||||
|
@ -0,0 +1,368 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="editable-element image"
|
||||||
|
:class="{'lock': elementInfo.isLock}"
|
||||||
|
:style="{
|
||||||
|
top: elementInfo.top + 'px',
|
||||||
|
left: elementInfo.left + 'px',
|
||||||
|
width: elementInfo.width + 'px',
|
||||||
|
height: elementInfo.height + 'px',
|
||||||
|
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||||
|
}"
|
||||||
|
@mousedown="handleSelectElement($event)"
|
||||||
|
>
|
||||||
|
<ImageClip
|
||||||
|
v-if="isCliping"
|
||||||
|
:imgUrl="elementInfo.imgUrl"
|
||||||
|
:clipData="elementInfo.clip"
|
||||||
|
:canvasScale="canvasScale"
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:top="elementInfo.top"
|
||||||
|
:left="elementInfo.left"
|
||||||
|
:clipPath="clipShape.style"
|
||||||
|
@clip="range => clip(range)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="element-content"
|
||||||
|
v-if="!isCliping"
|
||||||
|
v-contextmenu="contextmenus"
|
||||||
|
:style="{
|
||||||
|
filter: elementInfo.shadow ? `drop-shadow(${elementInfo.shadow})` : '',
|
||||||
|
transform: flip,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<RectBorder
|
||||||
|
v-if="clipShape.type === 'rect'"
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:radius="clipShape.radius"
|
||||||
|
:borderColor="elementInfo.borderColor"
|
||||||
|
:borderWidth="elementInfo.borderWidth"
|
||||||
|
:borderStyle="elementInfo.borderStyle"
|
||||||
|
/>
|
||||||
|
<EllipseBorder
|
||||||
|
v-else-if="clipShape.type === 'ellipse'"
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:borderColor="elementInfo.borderColor"
|
||||||
|
:borderWidth="elementInfo.borderWidth"
|
||||||
|
:borderStyle="elementInfo.borderStyle"
|
||||||
|
/>
|
||||||
|
<PolygonBorder
|
||||||
|
v-else-if="clipShape.type === 'polygon'"
|
||||||
|
:width="elementInfo.width"
|
||||||
|
:height="elementInfo.height"
|
||||||
|
:createPath="clipShape.createPath"
|
||||||
|
:borderColor="elementInfo.borderColor"
|
||||||
|
:borderWidth="elementInfo.borderWidth"
|
||||||
|
:borderStyle="elementInfo.borderStyle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="img-wrapper" :style="{clipPath: clipShape.style}">
|
||||||
|
<img
|
||||||
|
:src="elementInfo.imgUrl"
|
||||||
|
:draggable="false"
|
||||||
|
:style="{
|
||||||
|
top: imgPosition.top,
|
||||||
|
left: imgPosition.left,
|
||||||
|
width: imgPosition.width,
|
||||||
|
height: imgPosition.height,
|
||||||
|
filter: filter,
|
||||||
|
}"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="operate"
|
||||||
|
:class="{
|
||||||
|
'active': isActive,
|
||||||
|
'multi-select': isMultiSelect && isActive,
|
||||||
|
'selected': isHandleEl,
|
||||||
|
}"
|
||||||
|
:style="{transform: `scale(${1 / canvasScale})`}"
|
||||||
|
v-if="!isCliping"
|
||||||
|
>
|
||||||
|
<BorderLine
|
||||||
|
class="el-border-line"
|
||||||
|
v-for="line in borderLines"
|
||||||
|
:key="line.type"
|
||||||
|
:type="line.type"
|
||||||
|
:style="line.style"
|
||||||
|
/>
|
||||||
|
<template v-if="!elementInfo.isLock && (isActiveGroupElement || !isMultiSelect)">
|
||||||
|
<ResizablePoint
|
||||||
|
class="el-resizable-point"
|
||||||
|
v-for="point in resizablePoints"
|
||||||
|
:key="point.type"
|
||||||
|
:type="point.type"
|
||||||
|
:style="point.style"
|
||||||
|
@mousedown.stop="scaleElement($event, elementInfo, point.direction)"
|
||||||
|
/>
|
||||||
|
<RotateHandle
|
||||||
|
class="el-rotate-handle"
|
||||||
|
:style="{left: scaleWidth / 2 + 'px'}"
|
||||||
|
@mousedown.stop="rotateElement(elementInfo)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<AnimationIndex v-if="animationIndex !== -1" :animationIndex="animationIndex" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, ref, PropType } from 'vue'
|
||||||
|
|
||||||
|
import { PPTImageElement } from '@/types/slides'
|
||||||
|
import { ElementScaleHandler, OperateResizablePointTypes, OperateBorderLineTypes } from '@/types/edit'
|
||||||
|
|
||||||
|
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
|
||||||
|
import { OPERATE_KEYS } from '@/configs/element'
|
||||||
|
|
||||||
|
import RotateHandler from '@/views/_common/_operate/RotateHandler.vue'
|
||||||
|
import ResizablePoint from '@/views/_common/_operate/ResizablePoint.vue'
|
||||||
|
import BorderLine from '@/views/_common/_operate/BorderLine.vue'
|
||||||
|
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
|
||||||
|
|
||||||
|
import ImageClip, { ClipedEmitData } from './ImageClipHandler.vue'
|
||||||
|
import ImageRectBorder from './ImageRectBorder.vue'
|
||||||
|
import ImageEllipseBorder from './ImageEllipseBorder.vue'
|
||||||
|
import ImagePolygonBorder from './ImagePolygonBorder.vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'editable-element-image',
|
||||||
|
components: {
|
||||||
|
RotateHandler,
|
||||||
|
ResizablePoint,
|
||||||
|
BorderLine,
|
||||||
|
AnimationIndex,
|
||||||
|
ImageClip,
|
||||||
|
ImageRectBorder,
|
||||||
|
ImageEllipseBorder,
|
||||||
|
ImagePolygonBorder,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
elementInfo: {
|
||||||
|
type: Object as PropType<PPTImageElement>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
canvasScale: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isHandleEl: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isActiveGroupElement: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isMultiSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
animationIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectElement: {
|
||||||
|
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, canMove?: boolean) => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
rotateElement: {
|
||||||
|
type: Function as PropType<(element: PPTImageElement) => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
scaleElement: {
|
||||||
|
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: ElementScaleHandler) => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const clipingImageElId = ref('')
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo ? props.elementInfo.width * props.canvasScale : 0)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo ? props.elementInfo.height * props.canvasScale : 0)
|
||||||
|
const isCliping = computed(() => clipingImageElId.value === props.elementInfo.elId)
|
||||||
|
|
||||||
|
const imgPosition = computed(() => {
|
||||||
|
if(!props.elementInfo || !props.elementInfo.clip) {
|
||||||
|
return {
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [start, end] = props.elementInfo.clip.range
|
||||||
|
|
||||||
|
const widthScale = (end[0] - start[0]) / 100
|
||||||
|
const heightScale = (end[1] - start[1]) / 100
|
||||||
|
const left = start[0] / widthScale
|
||||||
|
const top = start[1] / heightScale
|
||||||
|
|
||||||
|
return {
|
||||||
|
left: -left + '%',
|
||||||
|
top: -top + '%',
|
||||||
|
width: 100 / widthScale + '%',
|
||||||
|
height: 100 / heightScale + '%',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const clipShape = computed(() => {
|
||||||
|
if(!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
|
||||||
|
const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
|
||||||
|
|
||||||
|
return CLIPPATHS[shape]
|
||||||
|
})
|
||||||
|
|
||||||
|
const resizablePoints = computed(() => {
|
||||||
|
return [
|
||||||
|
{ type: OperateResizablePointTypes.TL, direction: OPERATE_KEYS.LEFT_TOP, style: {} },
|
||||||
|
{ type: OperateResizablePointTypes.TC, direction: OPERATE_KEYS.TOP, style: {left: scaleWidth.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.TR, direction: OPERATE_KEYS.RIGHT_TOP, style: {left: scaleWidth.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.ML, direction: OPERATE_KEYS.LEFT, style: {top: scaleHeight.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.MR, direction: OPERATE_KEYS.RIGHT, style: {left: scaleWidth.value + 'px', top: scaleHeight.value / 2 + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BL, direction: OPERATE_KEYS.LEFT_BOTTOM, style: {top: scaleHeight.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BC, direction: OPERATE_KEYS.BOTTOM, style: {left: scaleWidth.value / 2 + 'px', top: scaleHeight.value + 'px'} },
|
||||||
|
{ type: OperateResizablePointTypes.BR, direction: OPERATE_KEYS.RIGHT_BOTTOM, style: {left: scaleWidth.value + 'px', top: scaleHeight.value + 'px'} },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const borderLines = computed(() => {
|
||||||
|
return [
|
||||||
|
{ type: OperateBorderLineTypes.T, style: {width: scaleWidth.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.B, style: {top: scaleHeight.value + 'px', width: scaleWidth.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.L, style: {height: scaleHeight.value + 'px'} },
|
||||||
|
{ type: OperateBorderLineTypes.R, style: {left: scaleWidth.value + 'px', height: scaleHeight.value + 'px'} },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const filter = computed(() => {
|
||||||
|
if(!props.elementInfo.filter) return ''
|
||||||
|
let filter = ''
|
||||||
|
for(const key of Object.keys(props.elementInfo.filter)) {
|
||||||
|
filter += `${key}(${props.elementInfo.filter[key]}) `
|
||||||
|
}
|
||||||
|
return filter
|
||||||
|
})
|
||||||
|
|
||||||
|
const flip = computed(() => {
|
||||||
|
if(!props.elementInfo.flip) return ''
|
||||||
|
const { x, y } = props.elementInfo.flip
|
||||||
|
if(x && y) return `rotateX(${x}deg) rotateY(${y}deg)`
|
||||||
|
else if(x) return `rotateX(${x}deg)`
|
||||||
|
else if(y) return `rotateY(${y}deg)`
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSelectElement = (e: MouseEvent) => {
|
||||||
|
if(isCliping.value || props.elementInfo.isLock) return
|
||||||
|
e.stopPropagation()
|
||||||
|
props.selectElement(e, props.elementInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clip = (data: ClipedEmitData) => {
|
||||||
|
clipingImageElId.value = ''
|
||||||
|
|
||||||
|
if(!data) return
|
||||||
|
|
||||||
|
const { range, position } = data
|
||||||
|
const originClip = props.elementInfo.clip || {}
|
||||||
|
|
||||||
|
const _props = {
|
||||||
|
clip: { ...originClip, range },
|
||||||
|
left: props.elementInfo.left + position.left,
|
||||||
|
top: props.elementInfo.top + position.top,
|
||||||
|
width: props.elementInfo.width + position.width,
|
||||||
|
height: props.elementInfo.height + position.height,
|
||||||
|
}
|
||||||
|
console.log(_props)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCliping,
|
||||||
|
imgPosition,
|
||||||
|
clipShape,
|
||||||
|
resizablePoints,
|
||||||
|
borderLines,
|
||||||
|
filter,
|
||||||
|
flip,
|
||||||
|
handleSelectElement,
|
||||||
|
clip,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.editable-element {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
&.lock .el-border-line {
|
||||||
|
border-color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .el-border-line {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lock .element-content {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.element-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
.img-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.operate {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.el-border-line,
|
||||||
|
.el-resizable-point,
|
||||||
|
.el-rotate-handle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.multi-select:not(.selected) .el-border-line {
|
||||||
|
border-color: rgba($color: $themeColor, $alpha: .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-border-line,
|
||||||
|
.el-resizable-point,
|
||||||
|
.el-rotate-handle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,8 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="animation-index">
|
<div class="animation-index">
|
||||||
<div class="index" v-for="(item, index) in animations" :key="index">
|
{{animationIndex}}
|
||||||
{{index}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -13,8 +11,8 @@ import { PPTAnimation } from '@/types/slides'
|
|||||||
export default {
|
export default {
|
||||||
name: 'animation-index',
|
name: 'animation-index',
|
||||||
props: {
|
props: {
|
||||||
animations: {
|
animationIndex: {
|
||||||
type: Array as PropType<PPTAnimation[]>,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -27,8 +25,6 @@ export default {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: -22px;
|
left: -22px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
|
||||||
.index {
|
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
|
import { OperateBorderLineType } from '@/types/edit'
|
||||||
type BorderLineType = 't' | 'b' | 'l' | 'r'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'border-line',
|
name: 'border-line',
|
||||||
props: {
|
props: {
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<BorderLineType>,
|
type: String as PropType<OperateBorderLineType>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isWide: {
|
isWide: {
|
||||||
|
@ -4,14 +4,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
|
import { OperateResizablePointType } from '@/types/edit'
|
||||||
type ResizablePointType = 't-l' | 't-c' | 't-r' | 'm-l' | 'm-r' | 'b-l' | 'b-c' | 'b-r' | 'any'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'resizable-point',
|
name: 'resizable-point',
|
||||||
props: {
|
props: {
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<ResizablePointType>,
|
type: String as PropType<OperateResizablePointType>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user