perf: 代码优化&注释

This commit is contained in:
pipipi-pikachu 2021-02-11 14:13:49 +08:00
parent 1d25d85621
commit d4a8d41394
23 changed files with 307 additions and 226 deletions

View File

@ -54,6 +54,10 @@ export interface ImageElementFilters {
'hue-rotate'?: string; 'hue-rotate'?: string;
'opacity'?: string; 'opacity'?: string;
} }
export interface ImageElementClip {
range: [[number, number], [number, number]];
shape: string;
}
export interface PPTImageElement { export interface PPTImageElement {
type: 'image'; type: 'image';
id: string; id: string;
@ -68,10 +72,7 @@ export interface PPTImageElement {
rotate?: number; rotate?: number;
outline?: PPTElementOutline; outline?: PPTElementOutline;
filters?: ImageElementFilters; filters?: ImageElementFilters;
clip?: { clip?: ImageElementClip;
range: [[number, number], [number, number]];
shape: 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'pentagon' | 'rhombus' | 'star';
};
flip?: ImageOrShapeFlip; flip?: ImageOrShapeFlip;
shadow?: PPTElementShadow; shadow?: PPTElementShadow;
} }

View File

@ -13,7 +13,7 @@
transform: `scale(${scale})`, transform: `scale(${scale})`,
}" }"
> >
<div class="background" :style="{ ...backgroundStyle }"></div> <div class="background" :style="backgroundStyle"></div>
<ThumbnailElement <ThumbnailElement
v-for="(element, index) in slide.elements" v-for="(element, index) in slide.elements"
:key="element.id" :key="element.id"

View File

@ -101,6 +101,7 @@ export default defineComponent({
onMounted(renderChart) onMounted(renderChart)
//
const updateTheme = () => { const updateTheme = () => {
if (!chartRef.value) return if (!chartRef.value) return
@ -116,15 +117,19 @@ export default defineComponent({
} }
chartRef.value.style.setProperty(`--theme-color-${i + 1}`, tinycolor(_hsla).toRgbString()) chartRef.value.style.setProperty(`--theme-color-${i + 1}`, tinycolor(_hsla).toRgbString())
} }
}
watch(() => props.themeColor, updateTheme)
onMounted(updateTheme)
//
const updateGridColor = () => {
if (!chartRef.value) return
if (props.gridColor) chartRef.value.style.setProperty(`--grid-color`, props.gridColor) if (props.gridColor) chartRef.value.style.setProperty(`--grid-color`, props.gridColor)
} }
watch([ watch(() => props.gridColor, updateGridColor)
() => props.themeColor, onMounted(updateGridColor)
() => props.gridColor,
], updateTheme)
onMounted(updateTheme)
return { return {
slideScale, slideScale,

View File

@ -15,7 +15,7 @@
:d="`M0,0 L${width},0 L${width},${height} L0,${height} Z`" :d="`M0,0 L${width},0 L${width},${height} L0,${height} Z`"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 6' : '0 0'"
></path> ></path>
</SvgWrapper> </SvgWrapper>
</template> </template>

View File

@ -16,26 +16,7 @@
transform: flipStyle, transform: flipStyle,
}" }"
> >
<ImageRectOutline <ImageOutline :elementInfo="elementInfo" />
v-if="clipShape.type === 'rect'"
:width="elementInfo.width"
:height="elementInfo.height"
:radius="clipShape.radius"
:outline="elementInfo.outline"
/>
<ImageEllipseOutline
v-else-if="clipShape.type === 'ellipse'"
:width="elementInfo.width"
:height="elementInfo.height"
:outline="elementInfo.outline"
/>
<ImagePolygonOutline
v-else-if="clipShape.type === 'polygon'"
:width="elementInfo.width"
:height="elementInfo.height"
:createPath="clipShape.createPath"
:outline="elementInfo.outline"
/>
<div class="image-content" :style="{ clipPath: clipShape.style }"> <div class="image-content" :style="{ clipPath: clipShape.style }">
<img <img
@ -57,23 +38,18 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType } from 'vue' import { computed, defineComponent, PropType } from 'vue'
import { PPTImageElement } from '@/types/slides' import { PPTImageElement } from '@/types/slides'
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
import ImageRectOutline from './ImageRectOutline.vue'
import ImageEllipseOutline from './ImageEllipseOutline.vue'
import ImagePolygonOutline from './ImagePolygonOutline.vue'
import useElementShadow from '@/views/components/element/hooks/useElementShadow' import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import useElementFlip from '@/views/components/element/hooks/useElementFlip' import useElementFlip from '@/views/components/element/hooks/useElementFlip'
import useClipImage from './useClipImage'
import useFilter from './useFilter'
import ImageOutline from './ImageOutline/index.vue'
export default defineComponent({ export default defineComponent({
name: 'base-element-image', name: 'base-element-image',
components: { components: {
ImageRectOutline, ImageOutline,
ImageEllipseOutline,
ImagePolygonOutline,
}, },
props: { props: {
elementInfo: { elementInfo: {
@ -82,59 +58,24 @@ export default defineComponent({
}, },
}, },
setup(props) { 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.filters) return ''
let filter = ''
for (const key of Object.keys(props.elementInfo.filters)) {
filter += `${key}(${props.elementInfo.filters[key]}) `
}
return filter
})
const shadow = computed(() => props.elementInfo.shadow) const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow) const { shadowStyle } = useElementShadow(shadow)
const flip = computed(() => props.elementInfo.flip) const flip = computed(() => props.elementInfo.flip)
const { flipStyle } = useElementFlip(flip) const { flipStyle } = useElementFlip(flip)
const clip = computed(() => props.elementInfo.clip)
const { clipShape, imgPosition } = useClipImage(clip)
const filters = computed(() => props.elementInfo.filters)
const { filter } = useFilter(filters)
return { return {
imgPosition, imgPosition,
clipShape,
filter, filter,
flipStyle, flipStyle,
shadowStyle, shadowStyle,
clipShape,
} }
}, },
}) })

View File

@ -95,12 +95,6 @@ export default defineComponent({
const canvasScale = computed(() => store.state.canvasScale) const canvasScale = computed(() => store.state.canvasScale)
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive) const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)
const topImgWrapperPosition = reactive({
top: 0,
left: 0,
width: 0,
height: 0,
})
const clipWrapperPositionStyle = reactive({ const clipWrapperPositionStyle = reactive({
top: '0', top: '0',
left: '0', left: '0',
@ -108,6 +102,7 @@ export default defineComponent({
const isSettingClipRange = ref(false) const isSettingClipRange = ref(false)
const currentRange = ref<ImageClipDataRange | null>(null) const currentRange = ref<ImageClipDataRange | null>(null)
//
const getClipDataTransformInfo = () => { const getClipDataTransformInfo = () => {
const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]] const [start, end] = props.clipData ? props.clipData.range : [[0, 0], [100, 100]]
@ -119,6 +114,7 @@ export default defineComponent({
return { widthScale, heightScale, left, top } return { widthScale, heightScale, left, top }
} }
//
const imgPosition = computed(() => { const imgPosition = computed(() => {
const { widthScale, heightScale, left, top } = getClipDataTransformInfo() const { widthScale, heightScale, left, top } = getClipDataTransformInfo()
return { return {
@ -129,6 +125,7 @@ export default defineComponent({
} }
}) })
//
const bottomImgPositionStyle = computed(() => { const bottomImgPositionStyle = computed(() => {
return { return {
top: imgPosition.value.top + '%', top: imgPosition.value.top + '%',
@ -138,6 +135,15 @@ export default defineComponent({
} }
}) })
//
const topImgWrapperPosition = reactive({
top: 0,
left: 0,
width: 0,
height: 0,
})
//
const topImgWrapperPositionStyle = computed(() => { const topImgWrapperPositionStyle = computed(() => {
return { return {
top: topImgWrapperPosition.top + '%', top: topImgWrapperPosition.top + '%',
@ -147,6 +153,7 @@ export default defineComponent({
} }
}) })
//
const topImgPositionStyle = computed(() => { const topImgPositionStyle = computed(() => {
const bottomWidth = imgPosition.value.width const bottomWidth = imgPosition.value.width
const bottomHeight = imgPosition.value.height const bottomHeight = imgPosition.value.height
@ -164,6 +171,7 @@ export default defineComponent({
} }
}) })
//
const initClipPosition = () => { const initClipPosition = () => {
const { left, top } = getClipDataTransformInfo() const { left, top } = getClipDataTransformInfo()
topImgWrapperPosition.left = left topImgWrapperPosition.left = left
@ -175,6 +183,7 @@ export default defineComponent({
clipWrapperPositionStyle.left = -left + '%' clipWrapperPositionStyle.left = -left + '%'
} }
//
const handleClip = () => { const handleClip = () => {
if (isSettingClipRange.value) return if (isSettingClipRange.value) return
@ -199,6 +208,7 @@ export default defineComponent({
emit('clip', clipedEmitData) emit('clip', clipedEmitData)
} }
//
const keyboardListener = (e: KeyboardEvent) => { const keyboardListener = (e: KeyboardEvent) => {
const key = e.key.toUpperCase() const key = e.key.toUpperCase()
if (key === KEYS.ENTER) handleClip() if (key === KEYS.ENTER) handleClip()
@ -212,7 +222,8 @@ export default defineComponent({
document.removeEventListener('keydown', keyboardListener) document.removeEventListener('keydown', keyboardListener)
}) })
const getRange = () => { //
const updateRange = () => {
const retPosition = { const retPosition = {
left: parseInt(topImgPositionStyle.value.left), left: parseInt(topImgPositionStyle.value.left),
top: parseInt(topImgPositionStyle.value.top), top: parseInt(topImgPositionStyle.value.top),
@ -235,6 +246,7 @@ export default defineComponent({
currentRange.value = [start, end] currentRange.value = [start, end]
} }
//
const moveClipRange = (e: MouseEvent) => { const moveClipRange = (e: MouseEvent) => {
isSettingClipRange.value = true isSettingClipRange.value = true
let isMouseDown = true let isMouseDown = true
@ -261,7 +273,6 @@ export default defineComponent({
let targetLeft = originPositopn.left + moveX let targetLeft = originPositopn.left + moveX
let targetTop = originPositopn.top + moveY let targetTop = originPositopn.top + moveY
//
if (targetLeft < 0) targetLeft = 0 if (targetLeft < 0) targetLeft = 0
else if (targetLeft + originPositopn.width > bottomPosition.width) { else if (targetLeft + originPositopn.width > bottomPosition.width) {
targetLeft = bottomPosition.width - originPositopn.width targetLeft = bottomPosition.width - originPositopn.width
@ -280,7 +291,7 @@ export default defineComponent({
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
getRange() updateRange()
setTimeout(() => { setTimeout(() => {
isSettingClipRange.value = false isSettingClipRange.value = false
@ -288,6 +299,7 @@ export default defineComponent({
} }
} }
//
const scaleClipRange = (e: MouseEvent, type: ScaleClipRangeType) => { const scaleClipRange = (e: MouseEvent, type: ScaleClipRangeType) => {
isSettingClipRange.value = true isSettingClipRange.value = true
let isMouseDown = true let isMouseDown = true
@ -323,7 +335,6 @@ export default defineComponent({
let targetLeft, targetTop, targetWidth, targetHeight let targetLeft, targetTop, targetWidth, targetHeight
//
if (type === 't-l') { if (type === 't-l') {
if (originPositopn.left + moveX < 0) { if (originPositopn.left + moveX < 0) {
moveX = -originPositopn.left moveX = -originPositopn.left
@ -408,7 +419,7 @@ export default defineComponent({
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
getRange() updateRange()
setTimeout(() => isSettingClipRange.value = false, 0) setTimeout(() => isSettingClipRange.value = false, 0)
} }

View File

@ -18,7 +18,7 @@
:ry="height / 2" :ry="height / 2"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 6' : '0 0'"
></ellipse> ></ellipse>
</SvgWrapper> </SvgWrapper>
</template> </template>

View File

@ -15,7 +15,7 @@
:d="createPath(width, height)" :d="createPath(width, height)"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 6' : '0 0'"
></path> ></path>
</SvgWrapper> </SvgWrapper>
</template> </template>

View File

@ -18,7 +18,7 @@
:height="height" :height="height"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '12 9' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 6' : '0 0'"
></rect> ></rect>
</SvgWrapper> </SvgWrapper>
</template> </template>

View File

@ -0,0 +1,57 @@
<template>
<div class="image-outline">
<ImageRectOutline
v-if="clipShape.type === 'rect'"
:width="elementInfo.width"
:height="elementInfo.height"
:radius="clipShape.radius"
:outline="elementInfo.outline"
/>
<ImageEllipseOutline
v-else-if="clipShape.type === 'ellipse'"
:width="elementInfo.width"
:height="elementInfo.height"
:outline="elementInfo.outline"
/>
<ImagePolygonOutline
v-else-if="clipShape.type === 'polygon'"
:width="elementInfo.width"
:height="elementInfo.height"
:outline="elementInfo.outline"
:createPath="clipShape.createPath"
/>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { PPTImageElement } from '@/types/slides'
import useClipImage from '../useClipImage'
import ImageRectOutline from './ImageRectOutline.vue'
import ImageEllipseOutline from './ImageEllipseOutline.vue'
import ImagePolygonOutline from './ImagePolygonOutline.vue'
export default defineComponent({
name: 'image-outline',
components: {
ImageRectOutline,
ImageEllipseOutline,
ImagePolygonOutline,
},
props: {
elementInfo: {
type: Object as PropType<PPTImageElement>,
required: true,
},
},
setup(props) {
const clip = computed(() => props.elementInfo.clip)
const { clipShape } = useClipImage(clip)
return {
clipShape,
}
},
})
</script>

View File

@ -20,7 +20,7 @@
:top="elementInfo.top" :top="elementInfo.top"
:left="elementInfo.left" :left="elementInfo.left"
:clipPath="clipShape.style" :clipPath="clipShape.style"
@clip="range => clip(range)" @clip="range => handleClip(range)"
/> />
<div <div
class="element-content" class="element-content"
@ -31,28 +31,9 @@
transform: flipStyle, transform: flipStyle,
}" }"
> >
<ImageRectOutline <ImageOutline :elementInfo="elementInfo" />
v-if="clipShape.type === 'rect'"
:width="elementInfo.width"
:height="elementInfo.height"
:radius="clipShape.radius"
:outline="elementInfo.outline"
/>
<ImageEllipseOutline
v-else-if="clipShape.type === 'ellipse'"
:width="elementInfo.width"
:height="elementInfo.height"
:outline="elementInfo.outline"
/>
<ImagePolygonOutline
v-else-if="clipShape.type === 'polygon'"
:width="elementInfo.width"
:height="elementInfo.height"
:outline="elementInfo.outline"
:createPath="clipShape.createPath"
/>
<div class="image-content" :style="{clipPath: clipShape.style}"> <div class="image-content" :style="{ clipPath: clipShape.style }">
<img <img
:src="elementInfo.src" :src="elementInfo.src"
:draggable="false" :draggable="false"
@ -74,23 +55,20 @@
import { computed, defineComponent, PropType } from 'vue' import { computed, defineComponent, PropType } from 'vue'
import { MutationTypes, useStore } from '@/store' import { MutationTypes, useStore } from '@/store'
import { PPTImageElement } from '@/types/slides' import { PPTImageElement } from '@/types/slides'
import { ImageClipedEmitData } from '@/types/edit'
import { ContextmenuItem } from '@/components/Contextmenu/types' import { ContextmenuItem } from '@/components/Contextmenu/types'
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
import useElementShadow from '@/views/components/element/hooks/useElementShadow' import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import useElementFlip from '@/views/components/element/hooks/useElementFlip' import useElementFlip from '@/views/components/element/hooks/useElementFlip'
import useClipImage from './useClipImage'
import useFilter from './useFilter'
import ImageRectOutline from './ImageRectOutline.vue' import ImageOutline from './ImageOutline/index.vue'
import ImageEllipseOutline from './ImageEllipseOutline.vue'
import ImagePolygonOutline from './ImagePolygonOutline.vue'
import ImageClipHandler from './ImageClipHandler.vue' import ImageClipHandler from './ImageClipHandler.vue'
import { ImageClipedEmitData } from '@/types/edit'
export default defineComponent({ export default defineComponent({
name: 'editable-element-image', name: 'editable-element-image',
components: { components: {
ImageRectOutline, ImageOutline,
ImageEllipseOutline,
ImagePolygonOutline,
ImageClipHandler, ImageClipHandler,
}, },
props: { props: {
@ -117,53 +95,19 @@ export default defineComponent({
const flip = computed(() => props.elementInfo.flip) const flip = computed(() => props.elementInfo.flip)
const { flipStyle } = useElementFlip(flip) const { flipStyle } = useElementFlip(flip)
const clip = computed(() => props.elementInfo.clip)
const { clipShape, imgPosition } = useClipImage(clip)
const filters = computed(() => props.elementInfo.filters)
const { filter } = useFilter(filters)
const handleSelectElement = (e: MouseEvent) => { const handleSelectElement = (e: MouseEvent) => {
if (props.elementInfo.lock) return if (props.elementInfo.lock) return
e.stopPropagation() e.stopPropagation()
props.selectElement(e, props.elementInfo) props.selectElement(e, props.elementInfo)
} }
const clipShape = computed(() => {
if (!props.elementInfo || !props.elementInfo.clip) return CLIPPATHS.rect
const shape = props.elementInfo.clip.shape || ClipPathTypes.RECT
return CLIPPATHS[shape] const handleClip = (data: ImageClipedEmitData) => {
})
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 filter = computed(() => {
if (!props.elementInfo.filters) return ''
let filter = ''
for (const key of Object.keys(props.elementInfo.filters)) {
filter += `${key}(${props.elementInfo.filters[key]}) `
}
return filter
})
const clip = (data: ImageClipedEmitData) => {
store.commit(MutationTypes.SET_CLIPING_IMAGE_ELEMENT_ID, '') store.commit(MutationTypes.SET_CLIPING_IMAGE_ELEMENT_ID, '')
if (!data) return if (!data) return
@ -183,7 +127,7 @@ export default defineComponent({
return { return {
isCliping, isCliping,
clip, handleClip,
clipingImageElementId, clipingImageElementId,
shadowStyle, shadowStyle,
handleSelectElement, handleSelectElement,

View File

@ -0,0 +1,42 @@
import { computed, Ref } from 'vue'
import { CLIPPATHS, ClipPathTypes } from '@/configs/imageClip'
import { ImageElementClip } from '@/types/slides'
export default (clip: Ref<ImageElementClip | undefined>) => {
const clipShape = computed(() => {
if (!clip.value) return CLIPPATHS.rect
const shape = clip.value.shape || ClipPathTypes.RECT
return CLIPPATHS[shape]
})
const imgPosition = computed(() => {
if (!clip.value) {
return {
top: '0',
left: '0',
width: '100%',
height: '100%',
}
}
const [start, end] = clip.value.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 + '%',
}
})
return {
clipShape,
imgPosition,
}
}

View File

@ -0,0 +1,17 @@
import { computed, Ref } from 'vue'
import { ImageElementFilters } from '@/types/slides'
export default (filters: Ref<ImageElementFilters | undefined>) => {
const filter = computed(() => {
if (!filters.value) return ''
let filter = ''
for (const key of Object.keys(filters.value)) {
filter += `${key}(${filters.value[key]}) `
}
return filter
})
return {
filter,
}
}

View File

@ -82,6 +82,7 @@ export default defineComponent({
}) })
const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10, 5' : '0, 0') const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10, 5' : '0, 0')
const path = computed(() => { const path = computed(() => {
const start = props.elementInfo.start.join(',') const start = props.elementInfo.start.join(',')
const end = props.elementInfo.end.join(',') const end = props.elementInfo.end.join(',')

View File

@ -106,7 +106,8 @@ export default defineComponent({
return height < 24 ? 24 : height return height < 24 ? 24 : height
}) })
const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10, 5' : '0, 0') const lineDashArray = computed(() => props.elementInfo.style === 'dashed' ? '10 6' : '0 0')
const path = computed(() => { const path = computed(() => {
const start = props.elementInfo.start.join(',') const start = props.elementInfo.start.join(',')
const end = props.elementInfo.end.join(',') const end = props.elementInfo.end.join(',')

View File

@ -46,7 +46,7 @@
:fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill" :fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 6' : '0 0'"
></path> ></path>
</g> </g>
</SvgWrapper> </SvgWrapper>

View File

@ -30,6 +30,8 @@ export default defineComponent({
const text = ref('') const text = ref('')
const isFocus = ref(false) const isFocus = ref(false)
// v-modal
//
watch(() => props.modelValue, () => { watch(() => props.modelValue, () => {
if (isFocus.value) return if (isFocus.value) return
text.value = props.modelValue text.value = props.modelValue
@ -42,6 +44,7 @@ export default defineComponent({
emit('update:modelValue', text) emit('update:modelValue', text)
} }
//
const handleFocus = () => { const handleFocus = () => {
isFocus.value = true isFocus.value = true
@ -58,11 +61,13 @@ export default defineComponent({
} }
} }
//
const handleBlur = () => { const handleBlur = () => {
isFocus.value = false isFocus.value = false
if (textareaRef.value) textareaRef.value.onpaste = null if (textareaRef.value) textareaRef.value.onpaste = null
} }
//
onUnmounted(() => { onUnmounted(() => {
if (textareaRef.value) textareaRef.value.onpaste = null if (textareaRef.value) textareaRef.value.onpaste = null
}) })

View File

@ -114,6 +114,20 @@ export default defineComponent({
const store = useStore() const store = useStore()
const canvasScale = computed(() => store.state.canvasScale) const canvasScale = computed(() => store.state.canvasScale)
const isStartSelect = ref(false)
const startCell = ref<number[]>([])
const endCell = ref<number[]>([])
const tableCells = computed<TableCell[][]>({
get() {
return props.data
},
set(newData) {
emit('change', newData)
},
})
//
const subThemeColor = ref(['', '']) const subThemeColor = ref(['', ''])
watch(() => props.theme, () => { watch(() => props.theme, () => {
if (props.theme) { if (props.theme) {
@ -127,15 +141,7 @@ export default defineComponent({
} }
}, { immediate: true }) }, { immediate: true })
const tableCells = computed<TableCell[][]>({ //
get() {
return props.data
},
set(newData) {
emit('change', newData)
},
})
const colSizeList = ref<number[]>([]) const colSizeList = ref<number[]>([])
const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b)) const totalWidth = computed(() => colSizeList.value.reduce((a, b) => a + b))
watch([ watch([
@ -145,10 +151,8 @@ export default defineComponent({
colSizeList.value = props.colWidths.map(item => item * props.width) colSizeList.value = props.colWidths.map(item => item * props.width)
}, { immediate: true }) }, { immediate: true })
const isStartSelect = ref(false) //
const startCell = ref<number[]>([]) //
const endCell = ref<number[]>([])
const removeSelectedCells = () => { const removeSelectedCells = () => {
startCell.value = [] startCell.value = []
endCell.value = [] endCell.value = []
@ -158,6 +162,7 @@ export default defineComponent({
if (!props.editable) removeSelectedCells() if (!props.editable) removeSelectedCells()
}) })
//
const dragLinePosition = computed(() => { const dragLinePosition = computed(() => {
const dragLinePosition: number[] = [] const dragLinePosition: number[] = []
for (let i = 1; i < colSizeList.value.length + 1; i++) { for (let i = 1; i < colSizeList.value.length + 1; i++) {
@ -167,6 +172,7 @@ export default defineComponent({
return dragLinePosition return dragLinePosition
}) })
//
const hideCells = computed(() => { const hideCells = computed(() => {
const hideCells = [] const hideCells = []
@ -188,6 +194,7 @@ export default defineComponent({
return hideCells return hideCells
}) })
//
const selectedCells = computed(() => { const selectedCells = computed(() => {
if (!startCell.value.length) return [] if (!startCell.value.length) return []
const [startX, startY] = startCell.value const [startX, startY] = startCell.value
@ -217,11 +224,13 @@ export default defineComponent({
emit('changeSelectedCells', selectedCells.value) emit('changeSelectedCells', selectedCells.value)
}) })
//
const activedCell = computed(() => { const activedCell = computed(() => {
if (selectedCells.value.length > 1) return null if (selectedCells.value.length > 1) return null
return selectedCells.value[0] return selectedCells.value[0]
}) })
//
const selectedRange = computed(() => { const selectedRange = computed(() => {
if (!startCell.value.length) return null if (!startCell.value.length) return null
const [startX, startY] = startCell.value const [startX, startY] = startCell.value
@ -242,6 +251,7 @@ export default defineComponent({
} }
}) })
//
const handleMouseup = () => isStartSelect.value = false const handleMouseup = () => isStartSelect.value = false
const handleCellMousedown = (e: MouseEvent, rowIndex: number, colIndex: number) => { const handleCellMousedown = (e: MouseEvent, rowIndex: number, colIndex: number) => {
@ -264,20 +274,24 @@ export default defineComponent({
document.removeEventListener('mouseup', handleMouseup) document.removeEventListener('mouseup', handleMouseup)
}) })
//
const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`) const isHideCell = (rowIndex: number, colIndex: number) => hideCells.value.includes(`${rowIndex}_${colIndex}`)
//
const selectCol = (index: number) => { const selectCol = (index: number) => {
const maxRow = tableCells.value.length - 1 const maxRow = tableCells.value.length - 1
startCell.value = [0, index] startCell.value = [0, index]
endCell.value = [maxRow, index] endCell.value = [maxRow, index]
} }
//
const selectRow = (index: number) => { const selectRow = (index: number) => {
const maxCol = tableCells.value[index].length - 1 const maxCol = tableCells.value[index].length - 1
startCell.value = [index, 0] startCell.value = [index, 0]
endCell.value = [index, maxCol] endCell.value = [index, maxCol]
} }
//
const selectAll = () => { const selectAll = () => {
const maxRow = tableCells.value.length - 1 const maxRow = tableCells.value.length - 1
const maxCol = tableCells.value[maxRow].length - 1 const maxCol = tableCells.value[maxRow].length - 1
@ -285,6 +299,7 @@ export default defineComponent({
endCell.value = [maxRow, maxCol] endCell.value = [maxRow, maxCol]
} }
//
const deleteRow = (rowIndex: number) => { const deleteRow = (rowIndex: number) => {
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value)) const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
@ -307,6 +322,7 @@ export default defineComponent({
tableCells.value = _tableCells tableCells.value = _tableCells
} }
//
const deleteCol = (colIndex: number) => { const deleteCol = (colIndex: number) => {
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value)) const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
@ -332,6 +348,7 @@ export default defineComponent({
emit('changeColWidths', colSizeList.value) emit('changeColWidths', colSizeList.value)
} }
//
const insertRow = (rowIndex: number) => { const insertRow = (rowIndex: number) => {
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value)) const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
@ -349,6 +366,7 @@ export default defineComponent({
tableCells.value = _tableCells tableCells.value = _tableCells
} }
//
const insertCol = (colIndex: number) => { const insertCol = (colIndex: number) => {
tableCells.value = tableCells.value.map(item => { tableCells.value = tableCells.value.map(item => {
const cell = { const cell = {
@ -364,6 +382,7 @@ export default defineComponent({
emit('changeColWidths', colSizeList.value) emit('changeColWidths', colSizeList.value)
} }
//
const mergeCells = () => { const mergeCells = () => {
const [startX, startY] = startCell.value const [startX, startY] = startCell.value
const [endX, endY] = endCell.value const [endX, endY] = endCell.value
@ -382,6 +401,7 @@ export default defineComponent({
removeSelectedCells() removeSelectedCells()
} }
//
const splitCells = (rowIndex: number, colIndex: number) => { const splitCells = (rowIndex: number, colIndex: number) => {
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value)) const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
_tableCells[rowIndex][colIndex].rowspan = 1 _tableCells[rowIndex][colIndex].rowspan = 1
@ -391,6 +411,7 @@ export default defineComponent({
removeSelectedCells() removeSelectedCells()
} }
//
const handleMousedownColHandler = (e: MouseEvent, colIndex: number) => { const handleMousedownColHandler = (e: MouseEvent, colIndex: number) => {
removeSelectedCells() removeSelectedCells()
let isMouseDown = true let isMouseDown = true
@ -417,6 +438,7 @@ export default defineComponent({
} }
} }
//
const clearSelectedCellText = () => { const clearSelectedCellText = () => {
const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value)) const _tableCells: TableCell[][] = JSON.parse(JSON.stringify(tableCells.value))
@ -430,6 +452,10 @@ export default defineComponent({
tableCells.value = _tableCells tableCells.value = _tableCells
} }
//
//
//
//
const tabActiveCell = () => { const tabActiveCell = () => {
const getNextCell = (i: number, j: number): [number, number] | null => { const getNextCell = (i: number, j: number): [number, number] | null => {
if (!tableCells.value[i]) return null if (!tableCells.value[i]) return null
@ -450,12 +476,14 @@ export default defineComponent({
} }
else startCell.value = nextCell else startCell.value = nextCell
//
nextTick(() => { nextTick(() => {
const textRef = document.querySelector('.cell-text.active') as HTMLInputElement const textRef = document.querySelector('.cell-text.active') as HTMLInputElement
if (textRef) textRef.focus() if (textRef) textRef.focus()
}) })
} }
//
const keydownListener = (e: KeyboardEvent) => { const keydownListener = (e: KeyboardEvent) => {
if (!props.editable || !selectedCells.value.length) return if (!props.editable || !selectedCells.value.length) return
@ -498,6 +526,7 @@ export default defineComponent({
document.removeEventListener('keydown', keydownListener) document.removeEventListener('keydown', keydownListener)
}) })
//
const getTextStyle = (style?: TableCellStyle) => { const getTextStyle = (style?: TableCellStyle) => {
if (!style) return {} if (!style) return {}
const { const {
@ -524,10 +553,12 @@ export default defineComponent({
} }
} }
//
const handleInput = debounce(function() { const handleInput = debounce(function() {
emit('change', tableCells.value) emit('change', tableCells.value)
}, 300, { trailing: true }) }, 300, { trailing: true })
//
const getEffectiveTableCells = () => { const getEffectiveTableCells = () => {
const effectiveTableCells = [] const effectiveTableCells = []
@ -543,6 +574,7 @@ export default defineComponent({
return effectiveTableCells return effectiveTableCells
} }
// /1
const checkCanDeleteRowOrCol = () => { const checkCanDeleteRowOrCol = () => {
const effectiveTableCells = getEffectiveTableCells() const effectiveTableCells = getEffectiveTableCells()
const canDeleteRow = effectiveTableCells.length > 1 const canDeleteRow = effectiveTableCells.length > 1
@ -551,6 +583,9 @@ export default defineComponent({
return { canDeleteRow, canDeleteCol } return { canDeleteRow, canDeleteCol }
} }
//
//
//
const checkCanMergeOrSplit = (rowIndex: number, colIndex: number) => { const checkCanMergeOrSplit = (rowIndex: number, colIndex: number) => {
const isMultiSelected = selectedCells.value.length > 1 const isMultiSelected = selectedCells.value.length > 1
const targetCell = tableCells.value[rowIndex][colIndex] const targetCell = tableCells.value[rowIndex][colIndex]
@ -717,7 +752,7 @@ table {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
background-color: rgba($color: $themeColor, $alpha: .3); background-color: rgba($color: #666, $alpha: .4);
} }
} }

View File

@ -69,6 +69,9 @@ export default defineComponent({
setup(props) { setup(props) {
const store = useStore() const store = useStore()
const canvasScale = computed(() => store.state.canvasScale) const canvasScale = computed(() => store.state.canvasScale)
const handleElementId = computed(() => store.state.handleElementId)
const elementRef = ref<HTMLElement>()
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
@ -78,8 +81,9 @@ export default defineComponent({
props.selectElement(e, props.elementInfo) props.selectElement(e, props.elementInfo)
} }
//
const editable = ref(false) const editable = ref(false)
const handleElementId = computed(() => store.state.handleElementId)
watch(handleElementId, () => { watch(handleElementId, () => {
if (handleElementId.value !== props.elementInfo.id) editable.value = false if (handleElementId.value !== props.elementInfo.id) editable.value = false
@ -89,8 +93,12 @@ export default defineComponent({
store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, editable.value) store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, editable.value)
}) })
const elementRef = ref<HTMLElement>() const startEdit = () => {
if (!props.elementInfo.lock) editable.value = true
}
// vuex
//
const isScaling = ref(false) const isScaling = ref(false)
const realHeightCache = ref(-1) const realHeightCache = ref(-1)
@ -139,6 +147,7 @@ export default defineComponent({
if (elementRef.value) resizeObserver.unobserve(elementRef.value) if (elementRef.value) resizeObserver.unobserve(elementRef.value)
}) })
//
const updateTableCells = (data: TableCell[][]) => { const updateTableCells = (data: TableCell[][]) => {
store.commit(MutationTypes.UPDATE_ELEMENT, { store.commit(MutationTypes.UPDATE_ELEMENT, {
id: props.elementInfo.id, id: props.elementInfo.id,
@ -146,6 +155,8 @@ export default defineComponent({
}) })
addHistorySnapshot() addHistorySnapshot()
} }
//
const updateColWidths = (widths: number[]) => { const updateColWidths = (widths: number[]) => {
const width = widths.reduce((a, b) => a + b) const width = widths.reduce((a, b) => a + b)
const colWidths = widths.map(item => item / width) const colWidths = widths.map(item => item / width)
@ -157,14 +168,11 @@ export default defineComponent({
addHistorySnapshot() addHistorySnapshot()
} }
//
const updateSelectedCells = (cells: string[]) => { const updateSelectedCells = (cells: string[]) => {
nextTick(() => emitter.emit(EmitterEvents.UPDATE_TABLE_SELECTED_CELL, cells)) nextTick(() => emitter.emit(EmitterEvents.UPDATE_TABLE_SELECTED_CELL, cells))
} }
const startEdit = () => {
if (!props.elementInfo.lock) editable.value = true
}
return { return {
elementRef, elementRef,
canvasScale, canvasScale,

View File

@ -86,6 +86,23 @@ export default defineComponent({
const isScaling = ref(false) const isScaling = ref(false)
const realHeightCache = ref(-1) const realHeightCache = ref(-1)
const editorViewRef = ref<HTMLElement>()
let editorView: EditorView
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
const handleElementId = computed(() => store.state.handleElementId)
const handleSelectElement = (e: MouseEvent, canMove = true) => {
if (props.elementInfo.lock) return
e.stopPropagation()
props.selectElement(e, props.elementInfo, canMove)
}
// vuex
//
const scaleElementStateListener = (state: boolean) => { const scaleElementStateListener = (state: boolean) => {
isScaling.value = state isScaling.value = state
@ -128,9 +145,10 @@ export default defineComponent({
if (elementRef.value) resizeObserver.unobserve(elementRef.value) if (elementRef.value) resizeObserver.unobserve(elementRef.value)
}) })
const editorViewRef = ref<HTMLElement>() //
let editorView: EditorView //
// vuex
//
const handleFocus = () => { const handleFocus = () => {
store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, true) store.commit(MutationTypes.SET_DISABLE_HOTKEYS_STATE, true)
} }
@ -155,6 +173,7 @@ export default defineComponent({
handleClick() handleClick()
} }
// DOM
const textContent = computed(() => props.elementInfo.content) const textContent = computed(() => props.elementInfo.content)
watch(textContent, () => { watch(textContent, () => {
if (!editorView) return if (!editorView) return
@ -162,11 +181,13 @@ export default defineComponent({
editorView.dom.innerHTML = textContent.value editorView.dom.innerHTML = textContent.value
}) })
// /
const editable = computed(() => !props.elementInfo.lock) const editable = computed(() => !props.elementInfo.lock)
watch(editable, () => { watch(editable, () => {
editorView.setProps({ editable: () => editable.value }) editorView.setProps({ editable: () => editable.value })
}) })
// Prosemirror
onMounted(() => { onMounted(() => {
editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, { editorView = initProsemirrorEditor((editorViewRef.value as Element), textContent.value, {
handleDOMEvents: { handleDOMEvents: {
@ -182,18 +203,8 @@ export default defineComponent({
editorView && editorView.destroy() editorView && editorView.destroy()
}) })
const handleSelectElement = (e: MouseEvent, canMove = true) => { //
if (props.elementInfo.lock) return //
e.stopPropagation()
props.selectElement(e, props.elementInfo, canMove)
}
const shadow = computed(() => props.elementInfo.shadow)
const { shadowStyle } = useElementShadow(shadow)
const handleElementId = computed(() => store.state.handleElementId)
const execCommand = (payload: CommandPayload | CommandPayload[]) => { const execCommand = (payload: CommandPayload | CommandPayload[]) => {
if (handleElementId.value !== props.elementInfo.id) return if (handleElementId.value !== props.elementInfo.id) return

View File

@ -1,18 +1,20 @@
import { ref, Ref, watchEffect } from 'vue' import { computed, Ref } from 'vue'
import { ImageOrShapeFlip } from '@/types/slides' import { ImageOrShapeFlip } from '@/types/slides'
// 计算元素的翻转样式
export default (flip: Ref<ImageOrShapeFlip | undefined>) => { export default (flip: Ref<ImageOrShapeFlip | undefined>) => {
const flipStyle = ref('') const flipStyle = computed(() => {
watchEffect(() => {
if (flip.value) { if (flip.value) {
let style = ''
const { x, y } = flip.value const { x, y } = flip.value
if (x && y) flipStyle.value = `rotateX(${x}deg) rotateY(${y}deg)` if (x && y) style = `rotateX(${x}deg) rotateY(${y}deg)`
else if (x) flipStyle.value = `rotateX(${x}deg)` else if (x) style = `rotateX(${x}deg)`
else if (y) flipStyle.value = `rotateY(${y}deg)` else if (y) style = `rotateY(${y}deg)`
else flipStyle.value = ''
return style
} }
else flipStyle.value = '' return ''
}) })
return { return {

View File

@ -1,10 +1,11 @@
import { computed, Ref } from 'vue' import { computed, Ref } from 'vue'
import { PPTElementOutline } from '@/types/slides' import { PPTElementOutline } from '@/types/slides'
// 计算边框相关属性值,主要是对默认值的处理
export default (outline: Ref<PPTElementOutline | undefined>) => { export default (outline: Ref<PPTElementOutline | undefined>) => {
const outlineWidth = computed(() => (outline.value && outline.value.width !== undefined) ? outline.value.width : 0) const outlineWidth = computed(() => outline.value?.width ?? 0)
const outlineStyle = computed(() => (outline.value && outline.value.style !== undefined) ? outline.value.style : 'solid') const outlineStyle = computed(() => outline.value?.style || 'solid')
const outlineColor = computed(() => (outline.value && outline.value.color !== undefined) ? outline.value.color : '#d14424') const outlineColor = computed(() => outline.value?.color || '#d14424')
return { return {
outlineWidth, outlineWidth,

View File

@ -1,15 +1,14 @@
import { ref, Ref, watchEffect } from 'vue' import { computed, Ref } from 'vue'
import { PPTElementShadow } from '@/types/slides' import { PPTElementShadow } from '@/types/slides'
// 计算元素的阴影样式
export default (shadow: Ref<PPTElementShadow | undefined>) => { export default (shadow: Ref<PPTElementShadow | undefined>) => {
const shadowStyle = ref('') const shadowStyle = computed(() => {
watchEffect(() => {
if (shadow.value) { if (shadow.value) {
const { h, v, blur, color } = shadow.value const { h, v, blur, color } = shadow.value
shadowStyle.value = `${h}px ${v}px ${blur}px ${color}` return `${h}px ${v}px ${blur}px ${color}`
} }
else shadowStyle.value = '' return ''
}) })
return { return {