添加形状元素的渲染
This commit is contained in:
parent
665e26c833
commit
8fe7b266f3
src
|
@ -109,7 +109,7 @@ export default () => {
|
|||
})
|
||||
}
|
||||
|
||||
const createShapeElement = (position: CommonElementPosition, svgCode: string) => {
|
||||
const createShapeElement = (position: CommonElementPosition, path: string, viewBox: number) => {
|
||||
const { left, top, width, height } = position
|
||||
createElement({
|
||||
...DEFAULT_SHAPE,
|
||||
|
@ -119,7 +119,8 @@ export default () => {
|
|||
top,
|
||||
width,
|
||||
height,
|
||||
svgCode,
|
||||
viewBox,
|
||||
path,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,21 @@ export const slides: Slide[] = [
|
|||
lock: false,
|
||||
content: '<div>😀 😐 😶 😜 🔔 ⭐ ⚡ 🔥 👍 💡 🔰 🎀 🎁 🥇 🏅 🏆 🎈 🎉 💎 🚧 ⛔ 📢 ⌛ ⏰ 🕒 🧩 🎵 📎 🔒 🔑 ⛳ 📌 📍 💬 📅 📈 📋 📜 📁 📱 💻 💾 🌏 🚚 🚡 🚢💧 🌐 🧭 💰 💳 🛒</div>',
|
||||
},
|
||||
{
|
||||
id: 'xxx7',
|
||||
type: 'shape',
|
||||
left: 130,
|
||||
top: 50,
|
||||
width: 150,
|
||||
height: 150,
|
||||
rotate: 0,
|
||||
fill: '#eebc29',
|
||||
opacity: 0.9,
|
||||
fixedRatio: false,
|
||||
lock: false,
|
||||
viewBox: 1024,
|
||||
path: 'M721.35111111 475.59111111H302.64888889c-5.00622222 0-9.10222222 4.096-9.10222222 9.10222222v54.61333334c0 5.00622222 4.096 9.10222222 9.10222222 9.10222222h418.70222222c5.00622222 0 9.10222222-4.096 9.10222222-9.10222222v-54.61333334c0-5.00622222-4.096-9.10222222-9.10222222-9.10222222z M512 2.27555555C230.51377778 2.27555555 2.27555555 230.51377778 2.27555555 512s228.23822222 509.72444445 509.72444445 509.72444445 509.72444445-228.23822222 509.72444445-509.72444445S793.48622222 2.27555555 512 2.27555555z m0 932.97777778c-233.69955555 0-423.25333333-189.55377778-423.25333333-423.25333333s189.55377778-423.25333333 423.25333333-423.25333333 423.25333333 189.55377778 423.25333333 423.25333333-189.55377778 423.25333333-423.25333333 423.25333333z',
|
||||
}
|
||||
],
|
||||
animations: [
|
||||
{
|
||||
|
|
|
@ -70,7 +70,8 @@ export interface PPTShapeElement extends PPTElementBaseProps {
|
|||
type: 'shape';
|
||||
width: number;
|
||||
height: number;
|
||||
svgCode: string;
|
||||
viewBox: number;
|
||||
path: string;
|
||||
fixedRatio: boolean;
|
||||
fill: string;
|
||||
rotate?: number;
|
||||
|
|
|
@ -30,6 +30,7 @@ import { ElementOrderCommands, ElementAlignCommands } from '@/types/edit'
|
|||
|
||||
import ImageElement from '@/views/components/element/ImageElement/index.vue'
|
||||
import TextElement from '@/views/components/element/TextElement/index.vue'
|
||||
import ShapeElement from '@/views/components/element/ShapeElement/index.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element',
|
||||
|
@ -56,6 +57,7 @@ export default defineComponent({
|
|||
const elementTypeMap = {
|
||||
'image': ImageElement,
|
||||
'text': TextElement,
|
||||
'shape': ShapeElement,
|
||||
}
|
||||
return elementTypeMap[props.elementInfo.type] || null
|
||||
})
|
||||
|
|
|
@ -11,15 +11,7 @@
|
|||
:clipPath="clipShape.style"
|
||||
@clip="range => clip(range)"
|
||||
/>
|
||||
<div
|
||||
class="image-element-operate"
|
||||
v-else
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
>
|
||||
<div class="image-element-operate" v-else>
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
|
@ -72,14 +64,6 @@ export default defineComponent({
|
|||
type: Object as PropType<PPTImageElement>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -136,26 +120,4 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-element-operate {
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.active) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div class="text-element-operate">
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
:key="line.type"
|
||||
:type="line.type"
|
||||
:style="line.style"
|
||||
/>
|
||||
<template v-if="!elementInfo.lock && (isActiveGroupElement || !isMultiSelect)">
|
||||
<ResizeHandler
|
||||
class="operate-resize-handler"
|
||||
v-for="point in resizeHandlers"
|
||||
:key="point.direction"
|
||||
:type="point.direction"
|
||||
:style="point.style"
|
||||
@mousedown.stop="$event => scaleElement($event, elementInfo, point.direction)"
|
||||
/>
|
||||
<RotateHandler
|
||||
class="operate-rotate-handler"
|
||||
:style="{ left: scaleWidth / 2 + 'px' }"
|
||||
@mousedown.stop="rotateElement(elementInfo)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { State } from '@/store'
|
||||
|
||||
import { PPTShapeElement } from '@/types/slides'
|
||||
import { OperateResizeHandler } from '@/types/edit'
|
||||
import useCommonOperate from '../hooks/useCommonOperate'
|
||||
|
||||
import RotateHandler from './RotateHandler.vue'
|
||||
import ResizeHandler from './ResizeHandler.vue'
|
||||
import BorderLine from './BorderLine.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'text-element-operate',
|
||||
components: {
|
||||
RotateHandler,
|
||||
ResizeHandler,
|
||||
BorderLine,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTShapeElement>,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isMultiSelect: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
rotateElement: {
|
||||
type: Function as PropType<(element: PPTShapeElement) => void>,
|
||||
required: true,
|
||||
},
|
||||
scaleElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandler) => void>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore<State>()
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
|
||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||
|
||||
return {
|
||||
scaleWidth,
|
||||
resizeHandlers,
|
||||
borderLines,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
|
@ -1,12 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
class="text-element-operate"
|
||||
:class="{
|
||||
'selected': isSelected,
|
||||
'multi-select': isMultiSelect && isSelected,
|
||||
'active': isActive,
|
||||
}"
|
||||
>
|
||||
<div class="text-element-operate">
|
||||
<BorderLine
|
||||
class="operate-border-line"
|
||||
v-for="line in borderLines"
|
||||
|
@ -57,14 +50,6 @@ export default defineComponent({
|
|||
type: Object as PropType<PPTTextElement>,
|
||||
required: true,
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isActiveGroupElement: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -98,26 +83,4 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-element-operate {
|
||||
&.selected {
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.multi-select:not(.active) .operate-border-line {
|
||||
border-color: rgba($color: $themeColor, $alpha: .3);
|
||||
}
|
||||
|
||||
.operate-border-line,
|
||||
.operate-resize-handler,
|
||||
.operate-rotate-handler {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
|
@ -1,21 +1,20 @@
|
|||
<template>
|
||||
<div
|
||||
class="operate"
|
||||
:class="{ 'multi-select': isMultiSelect && !isActive }"
|
||||
v-if="isSelected"
|
||||
:style="{
|
||||
top: elementInfo.top * canvasScale + 'px',
|
||||
left: elementInfo.left * canvasScale + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
'transform-origin': `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
|
||||
transformOrigin: `${elementInfo.width * canvasScale / 2}px ${elementInfo.height * canvasScale / 2}px`,
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="currentOperateComponent"
|
||||
:elementInfo="elementInfo"
|
||||
:isSelected="isSelected"
|
||||
:isActive="isActive"
|
||||
:isActiveGroupElement="isActiveGroupElement"
|
||||
:isMultiSelect="isMultiSelect"
|
||||
:animationIndex="elementIndexInAnimation"
|
||||
:rotateElement="rotateElement"
|
||||
:scaleElement="scaleElement"
|
||||
></component>
|
||||
|
@ -38,6 +37,7 @@ import { OperateResizeHandler } from '@/types/edit'
|
|||
|
||||
import ImageElementOperate from './ImageElementOperate.vue'
|
||||
import TextElementOperate from './TextElementOperate.vue'
|
||||
import ShapeElementOperate from './ShapeElementOperate.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'operate',
|
||||
|
@ -81,6 +81,7 @@ export default defineComponent({
|
|||
const elementTypeMap = {
|
||||
'image': ImageElementOperate,
|
||||
'text': TextElementOperate,
|
||||
'shape': ShapeElementOperate,
|
||||
}
|
||||
return elementTypeMap[props.elementInfo.type] || null
|
||||
})
|
||||
|
@ -105,6 +106,10 @@ export default defineComponent({
|
|||
position: absolute;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
|
||||
&.multi-select {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
.animation-index {
|
||||
position: absolute;
|
||||
|
|
|
@ -16,6 +16,7 @@ import { PPTElement } from '@/types/slides'
|
|||
|
||||
import BaseImageElement from '@/views/components/element/ImageElement/BaseImageElement.vue'
|
||||
import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
|
||||
import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element',
|
||||
|
@ -34,6 +35,7 @@ export default defineComponent({
|
|||
const elementTypeMap = {
|
||||
'image': BaseImageElement,
|
||||
'text': BaseTextElement,
|
||||
'shape': BaseShapeElement,
|
||||
}
|
||||
return elementTypeMap[props.elementInfo.type] || null
|
||||
})
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div class="base-element-shape"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
width: elementInfo.width + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="element-content"
|
||||
:style="{
|
||||
opacity: elementInfo.opacity,
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
}"
|
||||
>
|
||||
<SvgWrapper overflow="visible"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
>
|
||||
<g
|
||||
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin=""
|
||||
:d="elementInfo.path"
|
||||
:fill="elementInfo.fill"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
|
||||
></path>
|
||||
</g>
|
||||
</SvgWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { PPTShapeElement } from '@/types/slides'
|
||||
import useElementOutline from '@/views/components/element/hooks/useElementOutline'
|
||||
import useElementShadow from '@/views/components/element/hooks/useElementShadow'
|
||||
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'base-element-shape',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTShapeElement>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const outline = computed(() => props.elementInfo.outline)
|
||||
const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
shadowStyle,
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.base-element-shape {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.element-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
transform-origin: 0 0;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div class="editable-element-shape"
|
||||
:class="{ 'lock': elementInfo.lock }"
|
||||
:style="{
|
||||
top: elementInfo.top + 'px',
|
||||
left: elementInfo.left + 'px',
|
||||
width: elementInfo.width + 'px',
|
||||
transform: `rotate(${elementInfo.rotate}deg)`,
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div
|
||||
class="element-content"
|
||||
v-contextmenu="contextmenus"
|
||||
:style="{
|
||||
opacity: elementInfo.opacity,
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
}"
|
||||
>
|
||||
<SvgWrapper overflow="visible"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
>
|
||||
<g
|
||||
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
|
||||
>
|
||||
<path
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-linecap="butt"
|
||||
stroke-miterlimit="8"
|
||||
stroke-linejoin=""
|
||||
:d="elementInfo.path"
|
||||
:fill="elementInfo.fill"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="outlineWidth"
|
||||
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
|
||||
></path>
|
||||
</g>
|
||||
</SvgWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { PPTShapeElement } from '@/types/slides'
|
||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import useElementOutline from '@/views/components/element/hooks/useElementOutline'
|
||||
import useElementShadow from '@/views/components/element/hooks/useElementShadow'
|
||||
|
||||
import SvgWrapper from '@/components/SvgWrapper.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editable-element-shape',
|
||||
components: {
|
||||
SvgWrapper,
|
||||
},
|
||||
props: {
|
||||
elementInfo: {
|
||||
type: Object as PropType<PPTShapeElement>,
|
||||
required: true,
|
||||
},
|
||||
selectElement: {
|
||||
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, canMove?: boolean) => void>,
|
||||
required: true,
|
||||
},
|
||||
contextmenus: {
|
||||
type: Function as PropType<() => ContextmenuItem[]>,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const handleSelectElement = (e: MouseEvent) => {
|
||||
if(props.elementInfo.lock) return
|
||||
e.stopPropagation()
|
||||
|
||||
props.selectElement(e, props.elementInfo)
|
||||
}
|
||||
|
||||
const outline = computed(() => props.elementInfo.outline)
|
||||
const { outlineWidth, outlineStyle, outlineColor } = useElementOutline(outline)
|
||||
|
||||
const shadow = computed(() => props.elementInfo.shadow)
|
||||
const { shadowStyle } = useElementShadow(shadow)
|
||||
|
||||
return {
|
||||
handleSelectElement,
|
||||
shadowStyle,
|
||||
outlineWidth,
|
||||
outlineStyle,
|
||||
outlineColor,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-shape {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.element-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
transform-origin: 0 0;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue