feat: 移动端支持旋转(#214)

This commit is contained in:
pipipi-pikachu 2023-07-30 21:15:24 +08:00
parent 35ef7976c5
commit 9061ae544b
10 changed files with 68 additions and 27 deletions

View File

@ -21,7 +21,7 @@
class="operate-rotate-handler" class="operate-rotate-handler"
v-if="!cannotRotate" v-if="!cannotRotate"
:style="{ left: scaleWidth / 2 + 'px' }" :style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)" @mousedown.stop="$event => rotateElement($event, elementInfo)"
/> />
</template> </template>
</div> </div>
@ -37,7 +37,7 @@ export default {
import { computed } from 'vue' import { computed } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store' import { useMainStore } from '@/store'
import type { PPTShapeElement, PPTVideoElement, PPTLatexElement, PPTAudioElement } from '@/types/slides' import type { PPTVideoElement, PPTLatexElement, PPTAudioElement, PPTChartElement } from '@/types/slides'
import type { OperateResizeHandlers } from '@/types/edit' import type { OperateResizeHandlers } from '@/types/edit'
import useCommonOperate from '../hooks/useCommonOperate' import useCommonOperate from '../hooks/useCommonOperate'
@ -45,12 +45,12 @@ import RotateHandler from './RotateHandler.vue'
import ResizeHandler from './ResizeHandler.vue' import ResizeHandler from './ResizeHandler.vue'
import BorderLine from './BorderLine.vue' import BorderLine from './BorderLine.vue'
type PPTElement = PPTShapeElement | PPTVideoElement | PPTLatexElement | PPTAudioElement type PPTElement = PPTVideoElement | PPTLatexElement | PPTAudioElement | PPTChartElement
const props = defineProps<{ const props = defineProps<{
elementInfo: PPTElement elementInfo: PPTElement
handlerVisible: boolean handlerVisible: boolean
rotateElement: (element: PPTElement) => void rotateElement: (e: MouseEvent, element: PPTElement) => void
scaleElement: (e: MouseEvent, element: PPTElement, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: PPTElement, command: OperateResizeHandlers) => void
}>() }>()

View File

@ -20,7 +20,7 @@
<RotateHandler <RotateHandler
class="operate-rotate-handler" class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }" :style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)" @mousedown.stop="$event => rotateElement($event, elementInfo)"
/> />
</template> </template>
</div> </div>
@ -47,7 +47,7 @@ import BorderLine from './BorderLine.vue'
const props = defineProps<{ const props = defineProps<{
elementInfo: PPTImageElement elementInfo: PPTImageElement
handlerVisible: boolean handlerVisible: boolean
rotateElement: (element: PPTImageElement) => void rotateElement: (e: MouseEvent, element: PPTImageElement) => void
scaleElement: (e: MouseEvent, element: PPTImageElement, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: PPTImageElement, command: OperateResizeHandlers) => void
}>() }>()

View File

@ -20,7 +20,7 @@
<RotateHandler <RotateHandler
class="operate-rotate-handler" class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }" :style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)" @mousedown.stop="$event => rotateElement($event, elementInfo)"
/> />
<div <div
class="operate-keypoint-handler" class="operate-keypoint-handler"
@ -54,7 +54,7 @@ import BorderLine from './BorderLine.vue'
const props = defineProps<{ const props = defineProps<{
elementInfo: PPTShapeElement elementInfo: PPTShapeElement
handlerVisible: boolean handlerVisible: boolean
rotateElement: (element: PPTShapeElement) => void rotateElement: (e: MouseEvent, element: PPTShapeElement) => void
scaleElement: (e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void
moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement) => void moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement) => void
}>() }>()

View File

@ -20,7 +20,7 @@
<RotateHandler <RotateHandler
class="operate-rotate-handler" class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }" :style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)" @mousedown.stop="$event => rotateElement($event, elementInfo)"
/> />
</template> </template>
</div> </div>
@ -47,7 +47,7 @@ import BorderLine from './BorderLine.vue'
const props = defineProps<{ const props = defineProps<{
elementInfo: PPTTableElement elementInfo: PPTTableElement
handlerVisible: boolean handlerVisible: boolean
rotateElement: (element: PPTTableElement) => void rotateElement: (e: MouseEvent, element: PPTTableElement) => void
scaleElement: (e: MouseEvent, element: PPTTableElement, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: PPTTableElement, command: OperateResizeHandlers) => void
}>() }>()

View File

@ -20,7 +20,7 @@
<RotateHandler <RotateHandler
class="operate-rotate-handler" class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }" :style="{ left: scaleWidth / 2 + 'px' }"
@mousedown.stop="rotateElement(elementInfo)" @mousedown.stop="$event => rotateElement($event, elementInfo)"
/> />
</template> </template>
</div> </div>
@ -47,7 +47,7 @@ import BorderLine from './BorderLine.vue'
const props = defineProps<{ const props = defineProps<{
elementInfo: PPTTextElement elementInfo: PPTTextElement
handlerVisible: boolean handlerVisible: boolean
rotateElement: (element: PPTTextElement) => void rotateElement: (e: MouseEvent, element: PPTTextElement) => void
scaleElement: (e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void
}>() }>()

View File

@ -41,7 +41,15 @@
import { computed } from 'vue' import { computed } from 'vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store' import { useMainStore, useSlidesStore } from '@/store'
import { ElementTypes, type PPTElement, type PPTLineElement, type PPTVideoElement, type PPTAudioElement, type PPTShapeElement } from '@/types/slides' import {
ElementTypes,
type PPTElement,
type PPTLineElement,
type PPTVideoElement,
type PPTAudioElement,
type PPTShapeElement,
type PPTChartElement,
} from '@/types/slides'
import type { OperateLineHandlers, OperateResizeHandlers } from '@/types/edit' import type { OperateLineHandlers, OperateResizeHandlers } from '@/types/edit'
import ImageElementOperate from './ImageElementOperate.vue' import ImageElementOperate from './ImageElementOperate.vue'
@ -58,7 +66,7 @@ const props = defineProps<{
isActive: boolean isActive: boolean
isActiveGroupElement: boolean isActiveGroupElement: boolean
isMultiSelect: boolean isMultiSelect: boolean
rotateElement: (element: Exclude<PPTElement, PPTLineElement | PPTVideoElement | PPTAudioElement>) => void rotateElement: (e: MouseEvent, element: Exclude<PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement>) => void
scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void
dragLineElement: (e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void dragLineElement: (e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void
moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement) => void moveShapeKeypoint: (e: MouseEvent, element: PPTShapeElement) => void

View File

@ -1,7 +1,6 @@
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { storeToRefs } from 'pinia' import { useSlidesStore } from '@/store'
import { useMainStore, useSlidesStore } from '@/store' import type { PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement, PPTChartElement } from '@/types/slides'
import type { PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
/** /**
@ -15,14 +14,20 @@ const getAngleFromCoordinate = (x: number, y: number) => {
return angle return angle
} }
export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | undefined>) => { export default (
elementList: Ref<PPTElement[]>,
viewportRef: Ref<HTMLElement | undefined>,
canvasScale: Ref<number>,
) => {
const slidesStore = useSlidesStore() const slidesStore = useSlidesStore()
const { canvasScale } = storeToRefs(useMainStore())
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
// 旋转元素 // 旋转元素
const rotateElement = (element: Exclude<PPTElement, PPTLineElement | PPTVideoElement | PPTAudioElement>) => { const rotateElement = (e: MouseEvent | TouchEvent, element: Exclude<PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement>) => {
const isTouchEvent = !(e instanceof MouseEvent)
if (isTouchEvent && (!e.changedTouches || !e.changedTouches[0])) return
let isMouseDown = true let isMouseDown = true
let angle = 0 let angle = 0
const elOriginRotate = element.rotate || 0 const elOriginRotate = element.rotate || 0
@ -39,12 +44,15 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | u
if (!viewportRef.value) return if (!viewportRef.value) return
const viewportRect = viewportRef.value.getBoundingClientRect() const viewportRect = viewportRef.value.getBoundingClientRect()
document.onmousemove = e => { const handleMousemove = (e: MouseEvent | TouchEvent) => {
if (!isMouseDown) return if (!isMouseDown) return
const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX
const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY
// 计算当前鼠标位置相对元素中心点连线的角度(弧度) // 计算当前鼠标位置相对元素中心点连线的角度(弧度)
const mouseX = (e.pageX - viewportRect.left) / canvasScale.value const mouseX = (currentPageX - viewportRect.left) / canvasScale.value
const mouseY = (e.pageY - viewportRect.top) / canvasScale.value const mouseY = (currentPageY - viewportRect.top) / canvasScale.value
const x = mouseX - centerX const x = mouseX - centerX
const y = centerY - mouseY const y = centerY - mouseY
@ -65,7 +73,7 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | u
elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, rotate: angle } : el) elementList.value = elementList.value.map(el => element.id === el.id ? { ...el, rotate: angle } : el)
} }
document.onmouseup = () => { const handleMouseup = () => {
isMouseDown = false isMouseDown = false
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
@ -75,6 +83,15 @@ export default (elementList: Ref<PPTElement[]>, viewportRef: Ref<HTMLElement | u
slidesStore.updateSlide({ elements: elementList.value }) slidesStore.updateSlide({ elements: elementList.value })
addHistorySnapshot() addHistorySnapshot()
} }
if (isTouchEvent) {
document.ontouchmove = handleMousemove
document.ontouchend = handleMouseup
}
else {
document.onmousemove = handleMousemove
document.onmouseup = handleMouseup
}
} }
return { return {

View File

@ -180,7 +180,7 @@ const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
const { dragLineElement } = useDragLineElement(elementList) const { dragLineElement } = useDragLineElement(elementList)
const { selectElement } = useSelectElement(elementList, dragElement) const { selectElement } = useSelectElement(elementList, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale) const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
const { rotateElement } = useRotateElement(elementList, viewportRef) const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale) const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale)
const { selectAllElement } = useSelectAllElement() const { selectAllElement } = useSelectAllElement()

View File

@ -25,24 +25,34 @@
:style="point.style" :style="point.style"
@touchstart.stop="$event => scaleElement($event, elementInfo, point.direction)" @touchstart.stop="$event => scaleElement($event, elementInfo, point.direction)"
/> />
<RotateHandler
class="operate-rotate-handler"
:style="{ left: scaleWidth / 2 + 'px' }"
v-if="!cannotRotate"
@touchstart.stop="$event => rotateElement($event, elementInfo as CanRotatePPTElement)"
/>
</template> </template>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import type { PPTElement, PPTLineElement } from '@/types/slides' import type { PPTElement, PPTLineElement, PPTChartElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'
import useCommonOperate from '@/views/Editor/Canvas/hooks/useCommonOperate' import useCommonOperate from '@/views/Editor/Canvas/hooks/useCommonOperate'
import type { OperateResizeHandlers } from '@/types/edit' import type { OperateResizeHandlers } from '@/types/edit'
import BorderLine from '@/views/Editor/Canvas/Operate/BorderLine.vue' import BorderLine from '@/views/Editor/Canvas/Operate/BorderLine.vue'
import ResizeHandler from '@/views/Editor/Canvas/Operate/ResizeHandler.vue' import ResizeHandler from '@/views/Editor/Canvas/Operate/ResizeHandler.vue'
import RotateHandler from '@/views/Editor/Canvas/Operate/RotateHandler.vue'
type CanRotatePPTElement = Exclude<PPTElement, PPTChartElement | PPTLineElement | PPTVideoElement | PPTAudioElement>
const props = defineProps<{ const props = defineProps<{
elementInfo: Exclude<PPTElement, PPTLineElement> elementInfo: Exclude<PPTElement, PPTLineElement>
isSelected: boolean isSelected: boolean
canvasScale: number canvasScale: number
scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void scaleElement: (e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void
rotateElement: (e: MouseEvent, element: CanRotatePPTElement) => void
}>() }>()
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0) const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
@ -56,6 +66,8 @@ const {
} = useCommonOperate(scaleWidth, scaleHeight) } = useCommonOperate(scaleWidth, scaleHeight)
const resizeHandlers = props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : _resizeHandlers const resizeHandlers = props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : _resizeHandlers
const cannotRotate = computed(() => ['chart', 'video', 'audio'].includes(props.elementInfo.type))
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -20,9 +20,10 @@
:isSelected="activeElementIdList.includes(element.id)" :isSelected="activeElementIdList.includes(element.id)"
:canvasScale="canvasScale" :canvasScale="canvasScale"
:scaleElement="scaleElement" :scaleElement="scaleElement"
:rotateElement="rotateElement"
/> />
</template> </template>
<div class="viewport" :style="{ transform: `scale(${canvasScale})` }"> <div class="viewport" ref="viewportRef" :style="{ transform: `scale(${canvasScale})` }">
<MobileEditableElement <MobileEditableElement
v-for="(element, index) in elementList" v-for="(element, index) in elementList"
:key="element.id" :key="element.id"
@ -50,6 +51,7 @@ import { VIEWPORT_SIZE } from '@/configs/canvas'
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle' import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
import useDragElement from '@/views/Editor/Canvas/hooks/useDragElement' import useDragElement from '@/views/Editor/Canvas/hooks/useDragElement'
import useScaleElement from '@/views/Editor/Canvas/hooks/useScaleElement' import useScaleElement from '@/views/Editor/Canvas/hooks/useScaleElement'
import useRotateElement from '@/views/Editor/Canvas/hooks/useRotateElement'
import AlignmentLine from '@/views/Editor/Canvas/AlignmentLine.vue' import AlignmentLine from '@/views/Editor/Canvas/AlignmentLine.vue'
import MobileEditableElement from './MobileEditableElement.vue' import MobileEditableElement from './MobileEditableElement.vue'
@ -68,6 +70,7 @@ const { slideIndex, currentSlide, viewportRatio } = storeToRefs(slidesStore)
const { activeElementIdList, handleElement } = storeToRefs(mainStore) const { activeElementIdList, handleElement } = storeToRefs(mainStore)
const contentRef = ref<HTMLElement>() const contentRef = ref<HTMLElement>()
const viewportRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([]) const alignmentLines = ref<AlignmentLineProps[]>([])
@ -102,6 +105,7 @@ watchEffect(setLocalElementList)
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale) const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
const { scaleElement } = useScaleElement(elementList, alignmentLines, canvasScale) const { scaleElement } = useScaleElement(elementList, alignmentLines, canvasScale)
const { rotateElement } = useRotateElement(elementList, viewportRef, canvasScale)
const selectElement = (e: TouchEvent, element: PPTElement, startMove = true) => { const selectElement = (e: TouchEvent, element: PPTElement, startMove = true) => {
if (!activeElementIdList.value.includes(element.id)) { if (!activeElementIdList.value.includes(element.id)) {