| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- <template>
- <div
- class="editable-element-text"
- :class="{ 'lock': elementInfo.lock }"
- :style="{
- top: elementInfo.top + 'px',
- left: elementInfo.left + 'px',
- width: elementInfo.width + 'px',
- height: elementInfo.height + 'px',
- }"
- >
- <div
- class="rotate-wrapper"
- :style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
- >
- <div
- class="element-content"
- ref="elementRef"
- :style="{
- width: elementInfo.vertical ? 'auto' : elementInfo.width + 'px',
- height: elementInfo.vertical ? elementInfo.height + 'px' : elementInfo.height + 'px',
- backgroundColor: elementInfo.fill,
- opacity: elementInfo.opacity,
- textShadow: shadowStyle,
- lineHeight: elementInfo.lineHeight,
- letterSpacing: (elementInfo.wordSpace || 0) + 'px',
- color: elementInfo.defaultColor,
- fontFamily: elementInfo.defaultFontName,
- writingMode: elementInfo.vertical ? 'vertical-rl' : 'horizontal-tb',
- display: 'flex',
- alignItems: 'center',
- overflow: hidden,
- }"
- v-contextmenu="contextmenus"
- @mousedown="$event => handleSelectElement($event)"
- @touchstart="$event => handleSelectElement($event)"
- >
- <ElementOutline
- :width="elementInfo.width"
- :height="elementInfo.height"
- :outline="elementInfo.outline"
- />
- <ProsemirrorEditor
- class="text"
- :elementId="elementInfo.id"
- :defaultColor="elementInfo.defaultColor"
- :defaultFontName="elementInfo.defaultFontName"
- :editable="!elementInfo.lock"
- :value="elementInfo.content"
- :style="{
- '--paragraphSpace': `${elementInfo.paragraphSpace === undefined ? 5 : elementInfo.paragraphSpace}px`,
- }"
- @update="({ value, ignore }) => updateContent(value, ignore)"
- @mousedown="$event => handleSelectElement($event, false)"
- />
- <!-- 当字号过大且行高较小时,会出现文字高度溢出的情况,导致拖拽区域无法被选中,因此添加了以下节点避免该情况 -->
- <div class="drag-handler top"></div>
- <div class="drag-handler bottom"></div>
- </div>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import { computed, onMounted, onUnmounted, ref, watch, useTemplateRef } from 'vue'
- import { storeToRefs } from 'pinia'
- import { debounce } from 'lodash'
- import { useMainStore, useSlidesStore } from '@/store'
- import type { PPTTextElement } from '@/types/slides'
- import type { ContextmenuItem } from '@/components/Contextmenu/types'
- import useElementShadow from '@/views/components/element/hooks/useElementShadow'
- import useHistorySnapshot from '@/hooks/useHistorySnapshot'
- import ElementOutline from '@/views/components/element/ElementOutline.vue'
- import ProsemirrorEditor from '@/views/components/element/ProsemirrorEditor.vue'
- const props = defineProps<{
- elementInfo: PPTTextElement
- selectElement: (e: MouseEvent | TouchEvent, element: PPTTextElement, canMove?: boolean) => void
- contextmenus: () => ContextmenuItem[] | null
- }>()
- const mainStore = useMainStore()
- const slidesStore = useSlidesStore()
- const { handleElementId, isScaling } = storeToRefs(mainStore)
- const { addHistorySnapshot } = useHistorySnapshot()
- const elementRef = useTemplateRef<HTMLElement>('elementRef')
- const shadow = computed(() => props.elementInfo.shadow)
- const { shadowStyle } = useElementShadow(shadow)
- const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
- if (props.elementInfo.lock) return
- e.stopPropagation()
- props.selectElement(e, props.elementInfo, canMove)
- }
- // 监听文本元素的尺寸变化,当高度变化时,更新高度到vuex
- // 如果高度变化时正处在缩放操作中,则等待缩放操作结束后再更新
- const realHeightCache = ref(-1)
- const realWidthCache = ref(-1)
- watch(isScaling, () => {
- if (handleElementId.value !== props.elementInfo.id) return
- if (!isScaling.value) {
- if (!props.elementInfo.vertical && realHeightCache.value !== -1) {
- slidesStore.updateElement({
- id: props.elementInfo.id,
- props: { height: realHeightCache.value },
- })
- realHeightCache.value = -1
- }
- if (props.elementInfo.vertical && realWidthCache.value !== -1) {
- slidesStore.updateElement({
- id: props.elementInfo.id,
- props: { width: realWidthCache.value },
- })
- realWidthCache.value = -1
- }
- }
- })
- const updateTextElementHeight = (entries: ResizeObserverEntry[]) => {
- const contentRect = entries[0].contentRect
- if (!elementRef.value) return
- const realHeight = contentRect.height + 20
- const realWidth = contentRect.width + 20
- if (!props.elementInfo.vertical && props.elementInfo.height !== realHeight) {
- if (!isScaling.value) {
- slidesStore.updateElement({
- id: props.elementInfo.id,
- props: { height: realHeight },
- })
- }
- else realHeightCache.value = realHeight
- }
- if (props.elementInfo.vertical && props.elementInfo.width !== realWidth) {
- if (!isScaling.value) {
- slidesStore.updateElement({
- id: props.elementInfo.id,
- props: { width: realWidth },
- })
- }
- else realWidthCache.value = realWidth
- }
- }
- const resizeObserver = new ResizeObserver(updateTextElementHeight)
- onMounted(() => {
- if (elementRef.value) resizeObserver.observe(elementRef.value)
- })
- onUnmounted(() => {
- if (elementRef.value) resizeObserver.unobserve(elementRef.value)
- })
- const updateContent = (content: string, ignore = false) => {
- slidesStore.updateElement({
- id: props.elementInfo.id,
- props: { content },
- })
-
- if (!ignore) addHistorySnapshot()
- }
- const checkEmptyText = debounce(function() {
- const pureText = props.elementInfo.content.replace(/<[^>]+>/g, '')
- if (!pureText) slidesStore.deleteElement(props.elementInfo.id)
- }, 300, { trailing: true })
- const isHandleElement = computed(() => handleElementId.value === props.elementInfo.id)
- watch(isHandleElement, () => {
- if (!isHandleElement.value) checkEmptyText()
- })
- </script>
- <style lang="scss" scoped>
- .editable-element-text {
- position: absolute;
- &.lock .element-content {
- cursor: default;
- }
- }
- .rotate-wrapper {
- width: 100%;
- height: 100%;
- }
- .element-content {
- position: relative;
- padding: 10px;
- line-height: 1.5;
- word-break: break-word;
- cursor: move;
- .text {
- position: relative;
- }
- ::v-deep(a) {
- cursor: text;
- }
- }
- .drag-handler {
- height: 10px;
- position: absolute;
- left: 0;
- right: 0;
- &.top {
- top: 0;
- }
- &.bottom {
- bottom: 0;
- }
- }
- </style>
|