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
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:
parent
97acaf1d6f
commit
0fd4d0d39c
@ -157,6 +157,59 @@ function onResizeStart(e: MouseEvent) {
|
|||||||
document.addEventListener('mouseup', onMouseUp)
|
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)
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
@ -172,6 +225,7 @@ function getTitleKey(item: any) {
|
|||||||
|
|
||||||
async function enterExplaining() {
|
async function enterExplaining() {
|
||||||
initLeftWidth()
|
initLeftWidth()
|
||||||
|
resetZoom()
|
||||||
phase.value = 'explaining'
|
phase.value = 'explaining'
|
||||||
currentIndex.value = 0
|
currentIndex.value = 0
|
||||||
await callExplainApi(1)
|
await callExplainApi(1)
|
||||||
@ -333,37 +387,48 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
|
|||||||
|
|
||||||
<!-- ==================== 全屏讲解阶段 ==================== -->
|
<!-- ==================== 全屏讲解阶段 ==================== -->
|
||||||
<div v-else-if="phase === 'explaining'" class="explain-fullscreen">
|
<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-left" :style="{ flex: `0 0 ${leftWidth}px` }">
|
||||||
<div class="explain-panel-header">
|
<div class="explain-panel-header">
|
||||||
<span class="panel-title">第 {{ currentIndex + 1 }} 题</span>
|
<span class="panel-title">第 {{ currentIndex + 1 }} 题</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="explain-panel-body">
|
<div class="explain-panel-body">
|
||||||
<template
|
<div class="explain-zoom-layer">
|
||||||
v-if="
|
<template
|
||||||
Array.isArray(currentQuestion.xkwTitle?.questionList) &&
|
v-if="
|
||||||
currentQuestion.xkwTitle.questionList.length
|
Array.isArray(currentQuestion.xkwTitle?.questionList) &&
|
||||||
"
|
currentQuestion.xkwTitle.questionList.length
|
||||||
>
|
"
|
||||||
<PaperQuestionItem
|
>
|
||||||
v-for="question in currentQuestion.xkwTitle.questionList"
|
<PaperQuestionItem
|
||||||
:key="question.id"
|
v-for="question in currentQuestion.xkwTitle.questionList"
|
||||||
:index="currentQuestion.sort ?? currentIndex + 1"
|
:key="question.id"
|
||||||
:question="{ ...question, score: currentQuestion.score }"
|
:index="currentQuestion.sort ?? currentIndex + 1"
|
||||||
:preview="true"
|
:question="{ ...question, score: currentQuestion.score }"
|
||||||
/>
|
:preview="true"
|
||||||
</template>
|
/>
|
||||||
<template v-else>
|
</template>
|
||||||
<PaperQuestionItem
|
<template v-else>
|
||||||
:index="currentQuestion.sort ?? currentIndex + 1"
|
<PaperQuestionItem
|
||||||
:question="{
|
:index="currentQuestion.sort ?? currentIndex + 1"
|
||||||
...currentQuestion.xkwTitle,
|
:question="{
|
||||||
score: currentQuestion.score,
|
...currentQuestion.xkwTitle,
|
||||||
}"
|
score: currentQuestion.score,
|
||||||
:preview="true"
|
}"
|
||||||
/>
|
:preview="true"
|
||||||
</template>
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -376,60 +441,62 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
|
|||||||
<span class="panel-title">答案与解析</span>
|
<span class="panel-title">答案与解析</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="explain-panel-body">
|
<div class="explain-panel-body">
|
||||||
<!-- 答案 -->
|
<div class="explain-zoom-layer">
|
||||||
<div class="info-section" v-if="currentQuestion.xkwTitle?.answer">
|
<!-- 答案 -->
|
||||||
<div class="info-label">【答案】</div>
|
<div class="info-section" v-if="currentQuestion.xkwTitle?.answer">
|
||||||
<div class="info-content">
|
<div class="info-label">【答案】</div>
|
||||||
<latex-text :text="currentQuestion.xkwTitle.answer" />
|
<div class="info-content">
|
||||||
|
<latex-text :text="currentQuestion.xkwTitle.answer" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 解析 -->
|
<!-- 解析 -->
|
||||||
<div
|
<div
|
||||||
class="info-section"
|
class="info-section"
|
||||||
v-if="currentQuestion.xkwTitle?.explanation"
|
v-if="currentQuestion.xkwTitle?.explanation"
|
||||||
>
|
>
|
||||||
<div class="info-label">【解析】</div>
|
<div class="info-label">【解析】</div>
|
||||||
<div class="info-content">
|
<div class="info-content">
|
||||||
<latex-text :text="currentQuestion.xkwTitle.explanation" />
|
<latex-text :text="currentQuestion.xkwTitle.explanation" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 统计数据 -->
|
<!-- 统计数据 -->
|
||||||
<div class="stats-section">
|
<div class="stats-section">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div
|
<div
|
||||||
class="stat-card stat-success"
|
class="stat-card stat-success"
|
||||||
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 1)"
|
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 1)"
|
||||||
>
|
>
|
||||||
<div class="stat-value">
|
<div class="stat-value">
|
||||||
{{ currentQuestion.answerStatus?.correctNum ?? 0 }}
|
{{ currentQuestion.answerStatus?.correctNum ?? 0 }}
|
||||||
|
</div>
|
||||||
|
<div class="stat-label">满分人数</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-label">满分人数</div>
|
<div
|
||||||
</div>
|
class="stat-card stat-danger"
|
||||||
<div
|
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 2)"
|
||||||
class="stat-card stat-danger"
|
>
|
||||||
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 2)"
|
<div class="stat-value">
|
||||||
>
|
{{ currentQuestion.answerStatus?.errorNum ?? 0 }}
|
||||||
<div class="stat-value">
|
</div>
|
||||||
{{ currentQuestion.answerStatus?.errorNum ?? 0 }}
|
<div class="stat-label">非满分人数</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-label">非满分人数</div>
|
<div class="stat-card stat-primary">
|
||||||
</div>
|
<div class="stat-value">
|
||||||
<div class="stat-card stat-primary">
|
{{ currentQuestion.answerStatus?.correctRateText ?? '0%' }}
|
||||||
<div class="stat-value">
|
</div>
|
||||||
{{ currentQuestion.answerStatus?.correctRateText ?? '0%' }}
|
<div class="stat-label">得分率</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-label">得分率</div>
|
<div
|
||||||
</div>
|
class="stat-card stat-warning"
|
||||||
<div
|
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 3)"
|
||||||
class="stat-card stat-warning"
|
>
|
||||||
@click="getTitleStudentInfo(getTitleKey(currentQuestion), 3)"
|
<div class="stat-value">
|
||||||
>
|
{{ currentQuestion.answerStatus?.unSubmitNum ?? 0 }}
|
||||||
<div class="stat-value">
|
</div>
|
||||||
{{ currentQuestion.answerStatus?.unSubmitNum ?? 0 }}
|
<div class="stat-label">未作答人数</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-label">未作答人数</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -464,6 +531,7 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-right">
|
<div class="footer-right">
|
||||||
|
<el-button @click="resetZoom">重置缩放 {{ zoomPercent }}%</el-button>
|
||||||
<el-button type="danger" @click="endExplaining">结束讲解</el-button>
|
<el-button type="danger" @click="endExplaining">结束讲解</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -591,6 +659,8 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.explain-left,
|
.explain-left,
|
||||||
@ -636,6 +706,18 @@ async function getTitleStudentInfo(titleId: string, markType: number) {
|
|||||||
overflow: hidden auto;
|
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 {
|
.info-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user