644 lines
16 KiB
Vue
644 lines
16 KiB
Vue
<template>
|
||
<div class="wrong-retrain-page">
|
||
<SubPageHeader class="retrain-header titled-header">
|
||
<template #left>
|
||
<button class="detail-back" type="button" @click="handleBack">
|
||
<svg class="detail-back-icon" viewBox="0 0 32 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path
|
||
d="M24 4L6 26L24 48"
|
||
stroke="#111827"
|
||
stroke-width="4.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
/>
|
||
</svg>
|
||
<span>{{ pageTitle }}</span>
|
||
</button>
|
||
</template>
|
||
<template #center>
|
||
<QuestionNumberNav
|
||
:total="questionList.length"
|
||
:current-index="currentQuestionIndex"
|
||
@update:current-index="currentQuestionIndex = $event"
|
||
/>
|
||
</template>
|
||
</SubPageHeader>
|
||
|
||
<div class="workspace-card">
|
||
<div class="workspace-header">
|
||
<div class="question-base-info">
|
||
<span class="question-progress">
|
||
{{ currentQuestionIndex + 1 }}/{{ questionList.length }}
|
||
</span>
|
||
<span class="question-score">[{{ currentQuestion.score }}分]</span>
|
||
<span class="small-tag blue">{{ currentQuestion.subject }}</span>
|
||
<span class="small-tag blue-light">{{ currentQuestion.questionType }}</span>
|
||
<span class="small-tag orange">{{ currentQuestion.difficulty }}</span>
|
||
</div>
|
||
<div class="timer-chip">用时: <span class="time">{{ currentQuestion.duration }}s</span></div>
|
||
<div class="header-actions">
|
||
<template v-if="isDetailMode">
|
||
<button class="lx-btn lx-btn--sm lx-btn--secondary" type="button">错题反馈</button>
|
||
<button class="lx-btn lx-btn--sm lx-btn--secondary" type="button">举一反三</button>
|
||
</template>
|
||
<button
|
||
v-else
|
||
class="lx-btn lx-btn--md lx-btn--short lx-btn--primary"
|
||
type="button"
|
||
@click="goNextQuestion"
|
||
>
|
||
下一题
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="page-content" :class="{ 'answer-collapsed': isAnswerCollapsed }">
|
||
<QuestionStemPanel
|
||
:question="currentQuestion"
|
||
:current-index="currentQuestionIndex"
|
||
:total="questionList.length"
|
||
/>
|
||
|
||
<button
|
||
class="collapse-answer-btn"
|
||
:class="{ collapsed: isAnswerCollapsed }"
|
||
type="button"
|
||
:aria-label="isAnswerCollapsed ? '展开答题区域' : '折叠答题区域'"
|
||
@click="isAnswerCollapsed = !isAnswerCollapsed"
|
||
>
|
||
<span class="collapse-arrow">{{ isAnswerCollapsed ? "‹" : "›" }}</span>
|
||
</button>
|
||
|
||
<div v-if="!isAnswerCollapsed" class="right-panel">
|
||
<template v-if="!isDetailMode">
|
||
<ChoiceAnswerPanel
|
||
v-if="currentQuestion.answerType === 'choice'"
|
||
:model-value="choiceAnswers[currentQuestion.id]"
|
||
:options="currentQuestion.options ?? []"
|
||
@update:model-value="choiceAnswers[currentQuestion.id] = $event"
|
||
/>
|
||
<UploadAnswerPanel
|
||
v-else
|
||
:model-value="uploadAnswers[currentQuestion.id]"
|
||
@update:model-value="uploadAnswers[currentQuestion.id] = $event"
|
||
/>
|
||
</template>
|
||
|
||
<div v-else class="detail-answer-card">
|
||
<div class="answer-title-row">
|
||
<h3 class="answer-title">我的答案</h3>
|
||
<div
|
||
class="score-result"
|
||
:class="currentQuestion.isCorrect ? 'correct' : 'wrong'"
|
||
>
|
||
答{{ currentQuestion.isCorrect ? "对啦" : "错啦" }}!得{{ currentQuestion.isCorrect ? currentQuestion.score : 0 }}分
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="currentQuestion.answerType === 'choice'" class="review-option-list">
|
||
<div
|
||
v-for="option in currentQuestion.options ?? []"
|
||
:key="option.value"
|
||
class="review-option-item"
|
||
:class="getReviewOptionClass(option.value)"
|
||
>
|
||
<span class="review-option-key">{{ option.value }}</span>
|
||
<span class="review-option-text">{{ option.label }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="image-answer-preview">
|
||
<div class="paper-answer">
|
||
<div class="paper-text">
|
||
There is no <span>point</span> trying<br />
|
||
to persuade him.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="answer-section">
|
||
<h4>答案</h4>
|
||
<p>{{ currentQuestion.answerText }}</p>
|
||
</div>
|
||
|
||
<div class="answer-section">
|
||
<h4>解析</h4>
|
||
<p>{{ currentQuestion.explanation }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, ref } from "vue";
|
||
import { useRoute, useRouter } from "vue-router";
|
||
import SubPageHeader from "@/components/layout/SubPageHeader.vue";
|
||
import QuestionNumberNav from "@/views/AnswerQuestions/components/QuestionNumberNav.vue";
|
||
import QuestionStemPanel from "@/views/AnswerQuestions/components/QuestionStemPanel.vue";
|
||
import ChoiceAnswerPanel from "@/views/AnswerQuestions/components/ChoiceAnswerPanel.vue";
|
||
import UploadAnswerPanel from "@/views/AnswerQuestions/components/UploadAnswerPanel.vue";
|
||
import type {
|
||
AnswerQuestion,
|
||
UploadAnswerValue,
|
||
} from "@/views/AnswerQuestions/components/types";
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const isDetailMode = computed(() => route.query.mode === "detail");
|
||
const pageTitle = computed(() => (isDetailMode.value ? "查看详情" : "错题重练"));
|
||
const requestedCount = Number(route.query.count ?? 10);
|
||
const answerQuestionCount = Number.isNaN(requestedCount)
|
||
? 10
|
||
: Math.min(50, Math.max(1, requestedCount));
|
||
const questionCount = computed(() => answerQuestionCount);
|
||
const detailQuestionId = Number(route.query.questionId ?? 1);
|
||
|
||
// 页面题目列表,后续可替换成接口返回数据
|
||
const questionList = computed<AnswerQuestion[]>(() =>
|
||
Array.from({ length: questionCount.value }, (_, index) => {
|
||
const detailIsUpload = isDetailMode.value && detailQuestionId % 2 === 0;
|
||
const isChoice = index % 2 === 0;
|
||
const answerType: AnswerQuestion["answerType"] =
|
||
detailIsUpload || !isChoice ? "upload" : "choice";
|
||
const isCorrect = detailIsUpload;
|
||
|
||
return {
|
||
id: index + 1,
|
||
subject: "英语",
|
||
questionType: answerType === "choice" ? "单选题" : "填空题",
|
||
difficulty: "中等",
|
||
score: 10,
|
||
duration: 30,
|
||
typeLabel: answerType === "choice" ? "单选题" : "填空题",
|
||
answerType,
|
||
stem: answerType === "choice"
|
||
? "下列全是元音字母的一组是 ______ 。"
|
||
: "There is no ______ trying to persuade him.",
|
||
options: answerType === "choice"
|
||
? [
|
||
{ value: "A", label: "b;f;k" },
|
||
{ value: "B", label: "a;e;o" },
|
||
{ value: "C", label: "h;0;m" },
|
||
{ value: "D", label: "a;u;f" },
|
||
]
|
||
: undefined,
|
||
userAnswer: answerType === "choice" ? "C" : undefined,
|
||
correctAnswer: answerType === "choice" ? "B" : "There is no point trying to persuade him.",
|
||
isCorrect,
|
||
answerText:
|
||
answerType === "choice"
|
||
? "正确答案 B。"
|
||
: "正确答案:There is no point trying to persuade him.",
|
||
explanation:
|
||
answerType === "choice"
|
||
? "元音字母为 a、e、i、o、u,本题中只有 B 选项全部为元音字母。"
|
||
: "There is no point doing sth. 表示“做某事没有意义”,后面接动名词 trying。",
|
||
};
|
||
}),
|
||
);
|
||
|
||
const currentQuestionIndex = ref(0);
|
||
// 以题目 id 为 key 存储答案,便于题号切换后保留作答状态
|
||
const choiceAnswers = ref<Record<number, string>>({});
|
||
const uploadAnswers = ref<Record<number, UploadAnswerValue[]>>({});
|
||
const isAnswerCollapsed = ref(false);
|
||
|
||
const currentQuestion = computed(() => questionList.value[currentQuestionIndex.value]);
|
||
|
||
function getReviewOptionClass(value: string) {
|
||
return {
|
||
correct: value === currentQuestion.value.correctAnswer,
|
||
wrong:
|
||
value === currentQuestion.value.userAnswer &&
|
||
value !== currentQuestion.value.correctAnswer,
|
||
};
|
||
}
|
||
|
||
function goNextQuestion() {
|
||
currentQuestionIndex.value =
|
||
(currentQuestionIndex.value + 1) % questionList.value.length;
|
||
}
|
||
|
||
function handleBack() {
|
||
router.back();
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.wrong-retrain-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
min-height: 0;
|
||
background: #dce6f7;
|
||
padding-top: 12px;
|
||
}
|
||
|
||
:deep(.retrain-header.sub-page-header) {
|
||
grid-template-columns: 120px 1fr 0;
|
||
padding: 14px 36px;
|
||
min-height: 74px;
|
||
}
|
||
|
||
:deep(.retrain-header.titled-header.sub-page-header) {
|
||
grid-template-columns: max-content minmax(0, 1fr) 0;
|
||
}
|
||
|
||
:deep(.retrain-header.titled-header .question-nav) {
|
||
max-width: 100%;
|
||
}
|
||
|
||
:deep(.retrain-header.titled-header .sph-center) {
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.retrain-header .sph-center) {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
:deep(.retrain-header .sph-right) {
|
||
display: none;
|
||
}
|
||
|
||
.detail-back {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 18px;
|
||
border: none;
|
||
background: transparent;
|
||
color: #151821;
|
||
cursor: pointer;
|
||
font-size: 28px;
|
||
line-height: 1;
|
||
padding: 8px 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.detail-back-icon {
|
||
width: 24px;
|
||
height: 38px;
|
||
}
|
||
|
||
.workspace-card {
|
||
flex: 1;
|
||
min-height: 0;
|
||
margin: 0 34px 22px;
|
||
background: rgba(255, 255, 255, 0.30);
|
||
border-radius: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
margin-top: 12px;
|
||
border: 2px solid rgba(255, 255, 255);
|
||
}
|
||
|
||
.workspace-header {
|
||
// min-height: 76px;
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
// align-items: start;
|
||
gap: 14px;
|
||
padding: 26px 32px;
|
||
position: relative;
|
||
// background: #eef3fc;
|
||
}
|
||
|
||
.question-base-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
grid-column: 1;
|
||
justify-self: start;
|
||
}
|
||
|
||
.question-progress,
|
||
.question-score {
|
||
font-size: 32px;
|
||
color: #151821;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.small-tag {
|
||
font-size: 24px;
|
||
line-height: 1;
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
font-weight: 500;
|
||
|
||
&.blue {
|
||
color: #3193F5;
|
||
background: rgba(49, 147, 245, 0.10);
|
||
}
|
||
|
||
&.blue-light {
|
||
color: #4e97d1;
|
||
background: #edf6ff;
|
||
}
|
||
|
||
&.orange {
|
||
color: #EB5E00;
|
||
background: rgba(255, 117, 26, 0.10);
|
||
}
|
||
}
|
||
|
||
.timer-chip {
|
||
position: absolute;
|
||
height: 72px;
|
||
min-width: 300px;
|
||
padding: 18px 0;
|
||
background: url("@/assets/AnswerQuestions/time.png") center / 100% 100% no-repeat;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #2e3442;
|
||
font-size: 24px;
|
||
// font-weight: 600;
|
||
// line-height: 1;
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
|
||
|
||
.time{
|
||
font-weight: 600;
|
||
margin-left: 12px;
|
||
font-size: 32px;
|
||
}
|
||
}
|
||
|
||
.header-actions {
|
||
grid-column: 3;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: flex-start;
|
||
gap: 28px;
|
||
}
|
||
|
||
.page-content {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.05fr) 0 minmax(0, 1fr);
|
||
background: #fff;
|
||
border-radius: 32px;
|
||
padding: 32px 0px;
|
||
position: relative;
|
||
transition: grid-template-columns 0.24s ease;
|
||
|
||
&.answer-collapsed {
|
||
grid-template-columns: minmax(0, 1fr) 0 0;
|
||
|
||
:deep(.left-panel) {
|
||
border-right: none;
|
||
}
|
||
}
|
||
}
|
||
|
||
.right-panel {
|
||
grid-column: 3;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
// padding: 12px 14px 16px;
|
||
}
|
||
|
||
.collapse-answer-btn {
|
||
grid-column: 2;
|
||
position: relative;
|
||
z-index: 2;
|
||
align-self: center;
|
||
justify-self: center;
|
||
width: 34px;
|
||
height: 64px;
|
||
border: 1px solid rgba(170, 202, 238, 0.72);
|
||
border-radius: 999px;
|
||
background: linear-gradient(180deg, #f8fbff 0%, #ddecff 100%);
|
||
color: var(--color-text-brand);
|
||
cursor: pointer;
|
||
|
||
box-shadow:
|
||
0 8px 20px rgba(45, 111, 224, 0.16),
|
||
0 1px 4px rgba(45, 111, 224, 0.12);
|
||
transform: translateX(0);
|
||
transition:
|
||
background 0.2s,
|
||
border-color 0.2s,
|
||
box-shadow 0.2s,
|
||
transform 0.24s ease;
|
||
|
||
&::before,
|
||
&::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
width: 4px;
|
||
height: 4px;
|
||
border-radius: 50%;
|
||
background: rgba(45, 111, 224, 0.22);
|
||
transform: translateX(-50%);
|
||
|
||
}
|
||
|
||
&::before {
|
||
top: 12px;
|
||
}
|
||
|
||
&::after {
|
||
bottom: 12px;
|
||
}
|
||
|
||
&.collapsed {
|
||
justify-self: end;
|
||
transform: translateX(-12px);
|
||
}
|
||
|
||
&:hover {
|
||
border-color: rgba(45, 111, 224, 0.42);
|
||
background: linear-gradient(180deg, #fff 0%, #d7e9ff 100%);
|
||
box-shadow:
|
||
0 10px 24px rgba(45, 111, 224, 0.22),
|
||
0 2px 6px rgba(45, 111, 224, 0.16);
|
||
}
|
||
}
|
||
|
||
.collapse-arrow {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 100%;
|
||
font-size: 34px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.page-content.answer-collapsed {
|
||
.collapse-arrow {
|
||
transform: translateY(-1px);
|
||
}
|
||
}
|
||
|
||
.detail-answer-card {
|
||
height: 100%;
|
||
overflow-y: auto;
|
||
padding: 0 32px;
|
||
background: #fff;
|
||
scrollbar-width: none;
|
||
|
||
&::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.answer-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
min-height: 42px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.answer-title {
|
||
color: #151821;
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
.score-result {
|
||
min-width: 180px;
|
||
border-radius: 6px;
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
line-height: 1;
|
||
padding: 8px 14px;
|
||
text-align: right;
|
||
|
||
&.correct {
|
||
background: linear-gradient(90deg, rgba(44, 180, 132, 0), rgba(44, 180, 132, 0.14));
|
||
color: #16936b;
|
||
}
|
||
|
||
&.wrong {
|
||
background: linear-gradient(90deg, rgba(226, 68, 68, 0), rgba(226, 68, 68, 0.12));
|
||
color: #c72535;
|
||
}
|
||
}
|
||
|
||
.review-option-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24px;
|
||
}
|
||
|
||
.review-option-item {
|
||
display: flex;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
min-height: 92px;
|
||
border: 2px solid #dbe7f5;
|
||
border-radius: 10px;
|
||
background: #fff;
|
||
color: #171b24;
|
||
|
||
&.correct {
|
||
border-color: #31aa85;
|
||
background: rgba(49, 170, 133, 0.07);
|
||
color: #18936d;
|
||
|
||
.review-option-key {
|
||
background: rgba(49, 170, 133, 0.08);
|
||
border-right-color: rgba(49, 170, 133, 0.45);
|
||
}
|
||
}
|
||
|
||
&.wrong {
|
||
border-color: #d92535;
|
||
background: rgba(217, 37, 53, 0.06);
|
||
color: #c72535;
|
||
|
||
.review-option-key {
|
||
background: rgba(217, 37, 53, 0.06);
|
||
border-right-color: rgba(217, 37, 53, 0.45);
|
||
}
|
||
}
|
||
}
|
||
|
||
.review-option-key {
|
||
width: 88px;
|
||
min-height: 92px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-right: 1px solid #dce8f6;
|
||
background: #edf4fd;
|
||
font-size: 40px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.review-option-text {
|
||
padding: 0 24px;
|
||
font-size: 28px;
|
||
}
|
||
|
||
.image-answer-preview {
|
||
overflow: hidden;
|
||
border-radius: 10px;
|
||
border: 1px solid #dbe7f5;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.paper-answer {
|
||
position: relative;
|
||
height: 220px;
|
||
background:
|
||
repeating-linear-gradient(
|
||
to bottom,
|
||
#f7f2e8 0,
|
||
#f7f2e8 42px,
|
||
#9bb8d2 44px,
|
||
#f7f2e8 46px
|
||
);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.paper-text {
|
||
color: #111;
|
||
font-family: "Comic Sans MS", "Segoe Print", cursive;
|
||
font-size: 46px;
|
||
font-style: italic;
|
||
line-height: 1.25;
|
||
text-align: center;
|
||
transform: rotate(-1deg);
|
||
|
||
span {
|
||
color: var(--color-text-brand);
|
||
}
|
||
}
|
||
|
||
.answer-section {
|
||
margin-top: 34px;
|
||
color: #151821;
|
||
|
||
h4 {
|
||
margin-bottom: 18px;
|
||
font-size: 26px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
p {
|
||
color: #2e3138;
|
||
font-size: 22px;
|
||
line-height: 1.7;
|
||
}
|
||
}
|
||
</style>
|