feat: 添加手势缩放功能至MarkingImageViewerNew组件
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
a92743dda1
commit
bcacba59a1
|
|
@ -3,7 +3,6 @@ import type { KonvaMarkingData } from '../../composables/renderer/useMarkingKonv
|
||||||
import type { ExamStudentMarkingQuestionResponse } from '@/api'
|
import type { ExamStudentMarkingQuestionResponse } from '@/api'
|
||||||
import { markingSettings } from '../../composables/useMarkingSettings'
|
import { markingSettings } from '../../composables/useMarkingSettings'
|
||||||
import { useSmartScale } from '../../composables/useSmartScale'
|
import { useSmartScale } from '../../composables/useSmartScale'
|
||||||
import ScaleControlPanel from '../ScaleControlPanel.vue'
|
|
||||||
import QuestionRenderer from './QuestionRenderer.vue'
|
import QuestionRenderer from './QuestionRenderer.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -21,6 +20,62 @@ const smartScale = useSmartScale(props.imageSize)
|
||||||
|
|
||||||
const questionData = defineModel<ExamStudentMarkingQuestionResponse[]>('questionData')
|
const questionData = defineModel<ExamStudentMarkingQuestionResponse[]>('questionData')
|
||||||
|
|
||||||
|
// 手势缩放相关
|
||||||
|
const containerRef = ref<HTMLElement>()
|
||||||
|
const initialDistance = ref(0)
|
||||||
|
const initialScale = ref(1)
|
||||||
|
const isGesturing = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两点之间的距离
|
||||||
|
*/
|
||||||
|
function getDistance(touch1: Touch, touch2: Touch): number {
|
||||||
|
const dx = touch2.clientX - touch1.clientX
|
||||||
|
const dy = touch2.clientY - touch1.clientY
|
||||||
|
return Math.sqrt(dx * dx + dy * dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理触摸开始
|
||||||
|
*/
|
||||||
|
function handleTouchStart(e: TouchEvent) {
|
||||||
|
if (e.touches.length === 2) {
|
||||||
|
// 双指触摸,开始缩放手势
|
||||||
|
isGesturing.value = true
|
||||||
|
initialDistance.value = getDistance(e.touches[0], e.touches[1])
|
||||||
|
initialScale.value = smartScale.userScaleFactor.value
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理触摸移动
|
||||||
|
*/
|
||||||
|
function handleTouchMove(e: TouchEvent) {
|
||||||
|
if (e.touches.length === 2 && isGesturing.value) {
|
||||||
|
// 计算新的距离
|
||||||
|
const currentDistance = getDistance(e.touches[0], e.touches[1])
|
||||||
|
|
||||||
|
// 计算缩放比例变化
|
||||||
|
const scaleChange = currentDistance / initialDistance.value
|
||||||
|
|
||||||
|
// 应用新的缩放比例
|
||||||
|
const newScale = initialScale.value * scaleChange
|
||||||
|
smartScale.setScale(newScale)
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理触摸结束
|
||||||
|
*/
|
||||||
|
function handleTouchEnd(e: TouchEvent) {
|
||||||
|
if (e.touches.length < 2) {
|
||||||
|
isGesturing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算题目布局类
|
// 计算题目布局类
|
||||||
const questionsLayoutClass = computed(() => ({
|
const questionsLayoutClass = computed(() => ({
|
||||||
'questions-vertical': true, // 多题总是竖排
|
'questions-vertical': true, // 多题总是竖排
|
||||||
|
|
@ -51,17 +106,20 @@ function handleMarkingChange(questionIndex: number, imageIndex: number, data: Ko
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="multi-question-renderer">
|
<div
|
||||||
<!-- 缩放控制面板 -->
|
ref="containerRef"
|
||||||
<ScaleControlPanel
|
class="multi-question-renderer"
|
||||||
:scale-text="smartScale.scaleText.value"
|
@touchstart="handleTouchStart"
|
||||||
:can-zoom-in="smartScale.canZoomIn.value"
|
@touchmove="handleTouchMove"
|
||||||
:can-zoom-out="smartScale.canZoomOut.value"
|
@touchend="handleTouchEnd"
|
||||||
@zoom-in="smartScale.zoomIn"
|
>
|
||||||
@zoom-out="smartScale.zoomOut"
|
<!-- 缩放提示 -->
|
||||||
@reset-zoom="smartScale.resetZoom"
|
<div
|
||||||
@fit-to-screen="smartScale.fitToScreen"
|
v-if="isGesturing"
|
||||||
/>
|
class="fixed left-1/2 top-1/2 z-50 rounded-lg bg-black/70 px-4 py-2 text-white -translate-x-1/2 -translate-y-1/2"
|
||||||
|
>
|
||||||
|
{{ smartScale.scaleText.value }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col flex-nowrap" :class="questionsLayoutClass">
|
<div class="flex flex-col flex-nowrap" :class="questionsLayoutClass">
|
||||||
<QuestionRenderer
|
<QuestionRenderer
|
||||||
|
|
@ -85,6 +143,7 @@ function handleMarkingChange(questionIndex: number, imageIndex: number, data: Ko
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 8rpx 16rpx;
|
padding: 8rpx 16rpx;
|
||||||
padding-bottom: 96rpx;
|
padding-bottom: 96rpx;
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questions-container {
|
.questions-container {
|
||||||
|
|
|
||||||
|
|
@ -1,70 +1,55 @@
|
||||||
import { ref, computed, watch } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useScreenWidth } from './useScreenWidth'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 智能缩放控制
|
* 智能缩放控制 - 支持手势缩放
|
||||||
*/
|
*/
|
||||||
export function useSmartScale(imageSize: number = 100) {
|
export function useSmartScale(imageSize: number = 100) {
|
||||||
const { screenWidth } = useScreenWidth()
|
// 用户手动调节的缩放因子(1.0 = 默认100%,0.5 = 缩小50%,2.0 = 放大200%)
|
||||||
|
|
||||||
// 基础缩放比例
|
|
||||||
const baseScale = computed(() => imageSize / 100)
|
|
||||||
|
|
||||||
// 用户手动调节的缩放因子(1.0 = 默认,0.5 = 缩小50%,1.5 = 放大50%)
|
|
||||||
const userScaleFactor = ref(1.0)
|
const userScaleFactor = ref(1.0)
|
||||||
|
|
||||||
// 自动适应屏幕的缩放因子
|
// 最终的缩放比例 - 默认100%
|
||||||
const autoScaleFactor = computed(() => {
|
|
||||||
// 当屏幕宽度小于600px时,适当缩小以适应屏幕
|
|
||||||
if (screenWidth.value < 600) {
|
|
||||||
return Math.max(0.6, screenWidth.value / 1000) // 最小0.6倍
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 最终的缩放比例
|
|
||||||
const finalScale = computed(() => {
|
const finalScale = computed(() => {
|
||||||
return baseScale.value * userScaleFactor.value * autoScaleFactor.value
|
return userScaleFactor.value
|
||||||
})
|
})
|
||||||
|
|
||||||
// 缩放控制方法
|
// 设置缩放比例(用于手势缩放)
|
||||||
|
const setScale = (scale: number) => {
|
||||||
|
// 限制缩放范围:0.3倍到5倍
|
||||||
|
userScaleFactor.value = Math.max(0.3, Math.min(5.0, scale))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缩放控制方法(保留用于可能的按钮控制)
|
||||||
const zoomIn = () => {
|
const zoomIn = () => {
|
||||||
userScaleFactor.value = Math.min(userScaleFactor.value + 0.2, 3.0) // 最大3倍
|
userScaleFactor.value = Math.min(userScaleFactor.value + 0.2, 5.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomOut = () => {
|
const zoomOut = () => {
|
||||||
userScaleFactor.value = Math.max(userScaleFactor.value - 0.2, 0.3) // 最小0.3倍
|
userScaleFactor.value = Math.max(userScaleFactor.value - 0.2, 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetZoom = () => {
|
const resetZoom = () => {
|
||||||
userScaleFactor.value = 1.0
|
userScaleFactor.value = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动适应屏幕
|
|
||||||
const fitToScreen = () => {
|
|
||||||
userScaleFactor.value = 1.0 / autoScaleFactor.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缩放级别文本
|
// 缩放级别文本
|
||||||
const scaleText = computed(() => {
|
const scaleText = computed(() => {
|
||||||
const percentage = Math.round(userScaleFactor.value * 100)
|
const percentage = Math.round(userScaleFactor.value * 100)
|
||||||
return `${percentage}%`
|
return `${percentage}%`
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否可以放大/缩小
|
// 是否可以放大/缩小
|
||||||
const canZoomIn = computed(() => userScaleFactor.value < 3.0)
|
const canZoomIn = computed(() => userScaleFactor.value < 5.0)
|
||||||
const canZoomOut = computed(() => userScaleFactor.value > 0.3)
|
const canZoomOut = computed(() => userScaleFactor.value > 0.3)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
finalScale,
|
finalScale,
|
||||||
userScaleFactor,
|
userScaleFactor,
|
||||||
autoScaleFactor,
|
|
||||||
scaleText,
|
scaleText,
|
||||||
canZoomIn,
|
canZoomIn,
|
||||||
canZoomOut,
|
canZoomOut,
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
resetZoom,
|
resetZoom,
|
||||||
fitToScreen,
|
setScale,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue