feat: 讲解模式新增缩放
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled

This commit is contained in:
shawko 2026-05-10 09:56:17 +08:00
parent 97acaf1d6f
commit 0fd4d0d39c

View File

@ -157,6 +157,59 @@ function onResizeStart(e: MouseEvent) {
document.addEventListener('mouseup', onMouseUp)
}
// ======================== ========================
const MIN_ZOOM_SCALE = 0.7
const MAX_ZOOM_SCALE = 2.5
const zoomScale = ref(1)
const zoomPercent = computed(() => Math.round(zoomScale.value * 100))
let pinchStartDistance = 0
let pinchStartScale = 1
function clampZoomScale(scale: number) {
return Math.min(MAX_ZOOM_SCALE, Math.max(MIN_ZOOM_SCALE, scale))
}
function getTouchDistance(touches: TouchList) {
const first = touches[0]
const second = touches[1]
if (!first || !second) return 0
return Math.hypot(
first.clientX - second.clientX,
first.clientY - second.clientY,
)
}
function onZoomTouchStart(e: TouchEvent) {
if (e.touches.length !== 2) return
pinchStartDistance = getTouchDistance(e.touches)
pinchStartScale = zoomScale.value
}
function onZoomTouchMove(e: TouchEvent) {
if (e.touches.length !== 2 || !pinchStartDistance) return
e.preventDefault()
const distance = getTouchDistance(e.touches)
zoomScale.value = clampZoomScale(
pinchStartScale * (distance / pinchStartDistance),
)
}
function onZoomTouchEnd(e: TouchEvent) {
if (e.touches.length >= 2) return
pinchStartDistance = 0
}
function onZoomWheel(e: WheelEvent) {
if (!e.ctrlKey && !e.metaKey) return
e.preventDefault()
const delta = e.deltaY > 0 ? -0.08 : 0.08
zoomScale.value = clampZoomScale(zoomScale.value + delta)
}
function resetZoom() {
zoomScale.value = 1
}
// ======================== ========================
const currentIndex = ref(0)
@ -172,6 +225,7 @@ function getTitleKey(item: any) {
async function enterExplaining() {
initLeftWidth()
resetZoom()
phase.value = 'explaining'
currentIndex.value = 0
await callExplainApi(1)
@ -333,13 +387,23 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
<!-- ==================== 全屏讲解阶段 ==================== -->
<div v-else-if="phase === 'explaining'" class="explain-fullscreen">
<div class="explain-main" v-if="currentQuestion">
<div
v-if="currentQuestion"
class="explain-main"
:style="{ '--explain-zoom-scale': zoomScale }"
@touchstart="onZoomTouchStart"
@touchmove="onZoomTouchMove"
@touchend="onZoomTouchEnd"
@touchcancel="onZoomTouchEnd"
@wheel="onZoomWheel"
>
<!-- 左侧题目 -->
<div class="explain-left" :style="{ flex: `0 0 ${leftWidth}px` }">
<div class="explain-panel-header">
<span class="panel-title"> {{ currentIndex + 1 }} </span>
</div>
<div class="explain-panel-body">
<div class="explain-zoom-layer">
<template
v-if="
Array.isArray(currentQuestion.xkwTitle?.questionList) &&
@ -366,6 +430,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
</template>
</div>
</div>
</div>
<!-- 拖动分隔条 -->
<div class="resize-handle" @mousedown="onResizeStart"></div>
@ -376,6 +441,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
<span class="panel-title">答案与解析</span>
</div>
<div class="explain-panel-body">
<div class="explain-zoom-layer">
<!-- 答案 -->
<div class="info-section" v-if="currentQuestion.xkwTitle?.answer">
<div class="info-label">答案</div>
@ -436,6 +502,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
</div>
</div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="explain-footer">
@ -464,6 +531,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
</div>
</div>
<div class="footer-right">
<el-button @click="resetZoom">重置缩放 {{ zoomPercent }}%</el-button>
<el-button type="danger" @click="endExplaining">结束讲解</el-button>
</div>
</div>
@ -591,6 +659,8 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
flex: 1;
padding: 16px;
overflow: hidden;
touch-action: pan-x pan-y;
user-select: none;
}
.explain-left,
@ -636,6 +706,18 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
overflow: hidden auto;
}
.explain-zoom-layer {
zoom: var(--explain-zoom-scale);
}
@supports not (zoom: 1) {
.explain-zoom-layer {
width: calc(100% / var(--explain-zoom-scale));
transform: scale(var(--explain-zoom-scale));
transform-origin: left top;
}
}
.info-section {
display: flex;
align-items: flex-start;