This commit is contained in:
pipipi-pikachu 2020-12-23 22:48:35 +08:00
parent f575fa56b3
commit d040a92b18
8 changed files with 413 additions and 14 deletions

View File

@ -0,0 +1,17 @@
import { Ref, computed } from 'vue'
export default (background: Ref<[string, string] | undefined>) => {
const backgroundStyle = computed(() => {
if(!background.value) return { backgroundColor: '#fff' }
const [type, value] = background.value
if(type === 'solid') return { backgroundColor: value }
else if(type === 'image') return { backgroundImage: `url(${value}` }
return { backgroundColor: '#fff' }
})
return {
backgroundStyle,
}
}

View File

@ -14,8 +14,8 @@
import { Ref, computed, defineComponent } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store'
import { Slide } from '@/types/slides'
import GridLines from './GridLines.vue'
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
export default defineComponent({
name: 'slide-background',
@ -25,17 +25,9 @@ export default defineComponent({
setup() {
const store = useStore<State>()
const showGridLines = computed(() => store.state.showGridLines)
const currentSlide: Ref<Slide> = computed(() => store.getters.currentSlide)
const background: Ref<[string, string] | undefined> = computed(() => store.getters.currentSlide.background)
const backgroundStyle = computed(() => {
if(!currentSlide.value.background) return { backgroundColor: '#fff' }
const [type, value] = currentSlide.value.background
if(type === 'solid') return { backgroundColor: value }
else if(type === 'image') return { backgroundImage: `url(${value}` }
return { backgroundColor: '#fff' }
})
const { backgroundStyle } = useSlideBackgroundStyle(background)
return {
showGridLines,

View File

@ -16,7 +16,7 @@
@end="handleDragEnd"
itemKey="id"
>
<template #item="{ index }">
<template #item="{ element, index }">
<div
class="thumbnail-wrapper"
:class="{ 'active': slideIndex === index }"
@ -24,7 +24,9 @@
v-contextmenu="contextmenus"
>
<div class="slide-index">{{ fillDigit(index + 1, 2) }}</div>
<div class="thumbnail"></div>
<div class="thumbnail">
<ThumbnailSlide :slide="element" :size="120" />
</div>
</div>
</template>
</draggable>
@ -40,10 +42,13 @@ import { fillDigit } from '@/utils/common'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import useSlideHandler from '@/hooks/useSlideHandler'
import ThumbnailSlide from '@/views/_common/ThumbnailSlide.vue'
export default defineComponent({
name: 'thumbnails',
components: {
draggable,
ThumbnailSlide,
},
setup() {
const store = useStore<State>()

View File

@ -0,0 +1,77 @@
<template>
<div class="thumbnail-slide"
:style="{
width: size + 'px',
height: size * VIEWPORT_ASPECT_RATIO + 'px',
}"
>
<div
class="elements-wrapper"
:style="{
width: VIEWPORT_SIZE + 'px',
height: VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO + 'px',
transform: `scale(${size / VIEWPORT_SIZE})`,
}"
>
<div class="background" :style="{ ...backgroundStyle }"></div>
<template v-for="(element, index) in slide.elements" :key="element.elId">
<BaseElement
:elementInfo="element"
:elementIndex="index + 1"
/>
</template>
</div>
</div>
</template>
<script lang="ts">
import { computed, PropType, defineComponent } from 'vue'
import { Slide } from '@/types/slides'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
import BaseElement from '@/views/_common/_element/BaseElement.vue'
export default defineComponent({
name: 'thumbnail-slide',
components: {
BaseElement,
},
props: {
slide: {
type: Object as PropType<Slide>,
required: true,
},
size: {
type: Number,
required: true,
},
},
setup(props) {
const background = computed(() => props.slide.background)
const { backgroundStyle } = useSlideBackgroundStyle(background)
return {
backgroundStyle,
VIEWPORT_SIZE,
VIEWPORT_ASPECT_RATIO,
}
},
})
</script>
<style lang="scss" scoped>
.thumbnail-slide {
background-color: #fff;
overflow: hidden;
}
.elements-wrapper {
transform-origin: 0 0;
}
.background {
background-position: center;
background-size: cover;
position: absolute;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div
class="base-element"
:style="{ zIndex: elementIndex }"
>
<component
:is="currentElementComponent"
:elementInfo="elementInfo"
></component>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTElement } from '@/types/slides'
import BaseImageElement from './ImageElement/BaseImageElement.vue'
import BaseTextElement from './TextElement/BaseTextElement.vue'
export default defineComponent({
name: 'editable-element',
props: {
elementInfo: {
type: Object as PropType<PPTElement>,
required: true,
},
elementIndex: {
type: Number,
required: true,
},
},
setup(props) {
const currentElementComponent = computed(() => {
const elementTypeMap = {
'image': BaseImageElement,
'text': BaseTextElement,
}
return elementTypeMap[props.elementInfo.type] || null
})
return {
currentElementComponent,
}
},
})
</script>

View File

@ -0,0 +1,169 @@
<template>
<div
class="base-element image"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
height: elementInfo.height + 'px',
transform: `rotate(${elementInfo.rotate}deg)`,
}"
>
<div
class="element-content"
:style="{
filter: elementInfo.shadow ? `drop-shadow(${elementInfo.shadow})` : '',
transform: flip,
}"
>
<ImageRectBorder
v-if="clipShape.type === 'rect'"
:width="elementInfo.width"
:height="elementInfo.height"
:radius="clipShape.radius"
:borderColor="elementInfo.borderColor"
:borderWidth="elementInfo.borderWidth"
:borderStyle="elementInfo.borderStyle"
/>
<ImageEllipseBorder
v-else-if="clipShape.type === 'ellipse'"
:width="elementInfo.width"
:height="elementInfo.height"
:borderColor="elementInfo.borderColor"
:borderWidth="elementInfo.borderWidth"
:borderStyle="elementInfo.borderStyle"
/>
<ImagePolygonBorder
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>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTImageElement } from '@/types/slides'
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
import ImageRectBorder from './ImageRectBorder.vue'
import ImageEllipseBorder from './ImageEllipseBorder.vue'
import ImagePolygonBorder from './ImagePolygonBorder.vue'
export default defineComponent({
name: 'base-element-image',
components: {
ImageRectBorder,
ImageEllipseBorder,
ImagePolygonBorder,
},
props: {
elementInfo: {
type: Object as PropType<PPTImageElement>,
required: true,
},
},
setup(props) {
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 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 ''
})
return {
imgPosition,
clipShape,
filter,
flip,
}
},
})
</script>
<style lang="scss" scoped>
.base-element {
position: absolute;
}
.element-content {
width: 100%;
height: 100%;
position: relative;
.img-wrapper {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
img {
position: absolute;
}
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div
class="base-element text"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
transform: `rotate(${elementInfo.rotate}deg)`,
}"
>
<div class="element-content"
:style="{
backgroundColor: elementInfo.fill,
opacity: elementInfo.opacity,
textShadow: elementInfo.shadow,
lineHeight: elementInfo.lineHeight,
letterSpacing: (elementInfo.letterSpacing || 0) + 'px',
}"
>
<ElementBorder
:width="elementInfo.width"
:height="elementInfo.height"
:borderColor="elementInfo.borderColor"
:borderWidth="elementInfo.borderWidth"
:borderStyle="elementInfo.borderStyle"
/>
<div class="text-content"
v-html="elementInfo.content"
></div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { PPTTextElement } from '@/types/slides'
import ElementBorder from '@/views/_common/_element/ElementBorder.vue'
export default defineComponent({
name: 'base-element-text',
components: {
ElementBorder,
},
props: {
elementInfo: {
type: Object as PropType<PPTTextElement>,
required: true,
},
},
})
</script>
<style lang="scss" scoped>
.base-element {
position: absolute;
}
.element-content {
position: relative;
padding: 10px;
.text-content {
position: relative;
}
}
::v-deep(.text-content) {
word-break: break-word;
font-family: '微软雅黑';
outline: 0;
::selection {
background-color: rgba(27, 110, 232, 0.3);
color: inherit;
}
ul {
list-style-type: disc;
padding-inline-start: 30px;
li {
list-style-type: disc;
}
}
ol {
list-style-type: decimal;
padding-inline-start: 30px;
li {
list-style-type: decimal;
}
}
}
</style>

View File

@ -86,7 +86,7 @@ import BorderLine from '@/views/_common/_operate/BorderLine.vue'
import AnimationIndex from '@/views/_common/_operate/AnimationIndex.vue'
export default defineComponent({
name: 'slide-element-text',
name: 'editable-element-text',
components: {
ElementBorder,
RotateHandler,