From 0fd4d0d39c888eeafd668ecce3c433e558d34cfe Mon Sep 17 00:00:00 2001 From: shawko Date: Sun, 10 May 2026 09:56:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=B2=E8=A7=A3=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BC=A9=E6=94=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../question-bank/task-mgr/paper-explain.vue | 224 ++++++++++++------ 1 file changed, 153 insertions(+), 71 deletions(-) diff --git a/playground/src/views/question-bank/task-mgr/paper-explain.vue b/playground/src/views/question-bank/task-mgr/paper-explain.vue index 5d60813..22a2c81 100644 --- a/playground/src/views/question-bank/task-mgr/paper-explain.vue +++ b/playground/src/views/question-bank/task-mgr/paper-explain.vue @@ -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,37 +387,48 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
-
+
第 {{ currentIndex + 1 }} 题
- - +
+ + +
@@ -376,60 +441,62 @@ async function getTitleStudentInfo(titleId: string, markType: number) { 答案与解析
- -
-
【答案】
-
- +
+ +
+
【答案】
+
+ +
-
- -
-
【解析】
-
- + +
+
【解析】
+
+ +
-
- -
-
-
-
- {{ currentQuestion.answerStatus?.correctNum ?? 0 }} + +
+
+
+
+ {{ currentQuestion.answerStatus?.correctNum ?? 0 }} +
+
满分人数
-
满分人数
-
-
-
- {{ currentQuestion.answerStatus?.errorNum ?? 0 }} +
+
+ {{ currentQuestion.answerStatus?.errorNum ?? 0 }} +
+
非满分人数
-
非满分人数
-
-
-
- {{ currentQuestion.answerStatus?.correctRateText ?? '0%' }} +
+
+ {{ currentQuestion.answerStatus?.correctRateText ?? '0%' }} +
+
得分率
-
得分率
-
-
-
- {{ currentQuestion.answerStatus?.unSubmitNum ?? 0 }} +
+
+ {{ currentQuestion.answerStatus?.unSubmitNum ?? 0 }} +
+
未作答人数
-
未作答人数
@@ -464,6 +531,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
@@ -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;