Compare commits

..

9 Commits

7 changed files with 69 additions and 123 deletions

View File

@ -72,7 +72,7 @@ const weakTone = ['danger', 'warning', 'primary']
description="继续保持每日刷题节奏,稳步提升正确率。"
/>
<div v-else class="weak-list">
<div v-for="(w, i) in data.weakPoints" :key="`${w.module}-${w.point}`" class="weak-item">
<AppStack direction="row" gap="space-3" class="weak-item" v-for="(w, i) in data.weakPoints" :key="`${w.module}-${w.point}`">
<span class="weak-item__rank" :class="`is-${weakTone[i % weakTone.length]}`">{{ i + 1 }}</span>
<div class="weak-item__body">
<p class="weak-item__title">{{ w.module }} · {{ w.point }}</p>
@ -82,7 +82,7 @@ const weakTone = ['danger', 'warning', 'primary']
</div>
</div>
<span class="weak-item__tag">{{ w.reasons[0] || '待改进' }}</span>
</div>
</AppStack>
</div>
</AppCard>
@ -150,9 +150,7 @@ const weakTone = ['danger', 'warning', 'primary']
gap: var(--space-4);
}
.weak-item {
display: flex;
align-items: flex-start;
gap: var(--space-3);
--stack-align: flex-start;
}
.weak-item__rank {
display: grid;

View File

@ -147,7 +147,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
/>
<!-- ============ 移动端今日计划 ============ -->
<div v-else-if="isMobile && todayData" class="plan-mobile">
<template v-else-if="isMobile && todayData">
<header class="plan-mobile__head">
<h1>今日计划</h1>
<p>{{ planMessage }}</p>
@ -191,10 +191,10 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
<!-- 本周计划 -->
<AppCard class="plan-week">
<div class="plan-week__head">
<AppStack direction="row" class="plan-week__head">
<h3>本周计划</h3>
<span class="plan-week__tag"> {{ reviewData?.weekNumber ?? 1 }} </span>
</div>
</AppStack>
<div class="plan-week__meta">
<span>完成 {{ weekMetric.done }} / 目标 {{ weekMetric.total }} </span>
</div>
@ -212,13 +212,13 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
<!-- 明日预告 -->
<AppCard v-if="tomorrowTasks.length > 0" title="明日预告">
<div class="plan-tomorrow__row" v-for="task in tomorrowTasks" :key="task.id">
<AppStack direction="row" gap="space-3" class="plan-tomorrow__row" v-for="task in tomorrowTasks" :key="task.id">
<span class="plan-tomorrow__dot" />
<div class="plan-tomorrow__body">
<p class="plan-tomorrow__title">{{ task.title }}</p>
<p class="plan-tomorrow__meta">{{ task.type }}<template v-if="task.module"> · {{ task.module }}</template> · {{ task.target }}</p>
</div>
</div>
</AppStack>
</AppCard>
</template>
@ -231,10 +231,10 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
>
<AppButton variant="primary" @click="showCreate = true">创建今日任务</AppButton>
</AppEmpty>
</div>
</template>
<!-- ============ 桌面端备考计划 ============ -->
<div v-else-if="todayData" class="plan-desktop">
<template v-else-if="todayData">
<AppPageHeader title="备考计划" :subtitle="`安排阶段任务,稳步推进备考进度 · 备考第 ${profileDays} 天`" sticky-mobile>
<RouterLink class="btn-link" to="/plan/review">
<AppIcon name="chart" :size="16" />周报复盘
@ -317,7 +317,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
</div>
</AppCard>
</div>
</div>
</template>
<!-- 新建任务弹层 -->
<AppModal v-if="showCreate" title="新建计划任务" :max-width="480" @close="showCreate = false">
@ -365,12 +365,8 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
<style scoped>
/* 指标卡 */
.plan-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--card-gap-desktop);
}
.plan-metric {
flex: 1;
background: var(--bg-card);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-lg);
@ -459,7 +455,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
.plan-side__list {
display: flex;
flex-direction: column;
gap: var(--space-4);
gap: var(--space-2);
}
.plan-side__date {
margin: 0 0 var(--space-1);
@ -567,9 +563,8 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
gap: var(--space-3);
}
.plan-week__head {
display: flex;
align-items: center;
justify-content: space-between;
--stack-align: center;
--stack-justify: space-between;
}
.plan-week__head h3 {
margin: 0;
@ -615,9 +610,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
text-decoration: none;
}
.plan-tomorrow__row {
display: flex;
align-items: center;
gap: var(--space-3);
--stack-align: center;
padding: var(--space-2) 0;
}
.plan-tomorrow__dot {

View File

@ -164,7 +164,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
<p>本卷共 {{ finishResult.total }} · 作答 {{ finishResult.answered }} </p>
</header>
<div class="result__hero">
<AppStack direction="row" gap="space-6" class="result__hero">
<div class="result__score">
<strong class="num">{{ finishResult.correctCount }}<em>/{{ finishResult.total }}</em></strong>
<span>答对题数</span>
@ -178,18 +178,18 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
<strong class="num">{{ formatDuration(finishResult.durationSeconds) }}</strong>
<span>用时</span>
</div>
</div>
</AppStack>
<AppCard v-if="wrongItems.length > 0" title="错题回顾" icon="alert">
<div class="wrong-list">
<div v-for="(q, i) in wrongItems" :key="q.questionId" class="wrong-list__item">
<span class="wrong-list__idx">{{ i + 1 }}</span>
<div class="wrong-list__body">
<div class="wrong-list__ans">
<AppStack direction="row" gap="space-2" class="wrong-list__ans">
<span>你的答案 <b class="is-wrong">{{ q.userAnswer }}</b></span>
<span class="wrong-list__arrow"><AppIcon name="chevron-right" :size="14" /></span>
<span>正确答案 <b class="is-right">{{ q.correctAnswer }}</b></span>
</div>
</AppStack>
<p class="wrong-list__analysis">{{ q.analysis }}</p>
<AppButton
variant="ghost"
@ -267,7 +267,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
<!-- ===== 单题结果态 ===== -->
<div v-if="answered && result" class="single-result">
<div class="single-result__banner" :class="result.correct ? 'is-correct' : 'is-wrong'">
<AppStack direction="row" gap="space-3" class="single-result__banner" :class="result.correct ? 'is-correct' : 'is-wrong'">
<span class="single-result__icon">
<AppIcon :name="result.correct ? 'check' : 'close'" :size="18" />
</span>
@ -276,7 +276,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
<span v-if="!result.correct">正确答案是 {{ result.correctAnswer }} · 本题已自动收录到错题本</span>
<span v-else>回答正确继续加油</span>
</div>
</div>
</AppStack>
<AppCard v-if="result.analysis" title="解析" icon="book-open">
<p class="single-result__analysis">{{ result.analysis }}</p>
@ -465,9 +465,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
gap: var(--space-4);
}
.single-result__banner {
display: flex;
align-items: center;
gap: var(--space-3);
--stack-align: center;
padding: var(--space-4);
border-radius: var(--radius-lg);
color: var(--on-accent);
@ -553,9 +551,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
color: var(--text-secondary);
}
.result__hero {
display: flex;
align-items: center;
gap: var(--space-6);
--stack-align: center;
padding: var(--card-padding-desktop);
background: var(--bg-card);
border: 1px solid var(--border-subtle);
@ -623,9 +619,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
flex: 1;
}
.wrong-list__ans {
display: flex;
align-items: center;
gap: var(--space-2);
--stack-align: center;
font-size: var(--fs-subtitle);
}
.wrong-list__ans .is-wrong {

View File

@ -22,10 +22,10 @@ const QUESTION_TYPES = [
备考通当前聚焦行测闭环申论功能正在规划中
</p>
<div class="essay__types">
<div v-for="t in QUESTION_TYPES" :key="t.name" class="essay__type">
<AppStack direction="row" gap="space-3" class="essay__type" v-for="t in QUESTION_TYPES" :key="t.name">
<AppBadge variant="primary">{{ t.name }}</AppBadge>
<span class="essay__type-desc">{{ t.desc }}</span>
</div>
</AppStack>
</div>
</AppCard>
@ -49,10 +49,10 @@ const QUESTION_TYPES = [
<!-- 说明提示 -->
<AppCard :padded="true">
<div class="essay__notice">
<AppStack direction="row" gap="space-3" class="essay__notice">
<AppIcon name="alert" :size="18" />
<p>当前为占位页面不保存任何写作内容建议先完成行测刷题 错题复习 模考分析闭环</p>
</div>
</AppStack>
</AppCard>
</AppStack>
</template>
@ -70,9 +70,7 @@ const QUESTION_TYPES = [
gap: var(--space-3);
}
.essay__type {
display: flex;
align-items: center;
gap: var(--space-3);
--stack-align: center;
padding: var(--space-3);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
@ -104,9 +102,7 @@ const QUESTION_TYPES = [
flex: none;
}
.essay__notice {
display: flex;
align-items: flex-start;
gap: var(--space-3);
--stack-align: flex-start;
}
.essay__notice svg {
color: var(--warning);

View File

@ -172,7 +172,7 @@ const subtitle = computed(() => {
</AppPageHeader>
<!-- tab -->
<div class="practice-tabs">
<div class="practice-tabs" v-if="isMobile">
<RouterLink
v-for="t in tabs"
:key="t.key"
@ -209,13 +209,13 @@ const subtitle = computed(() => {
:class="{ 'module-card--active': activeModule === m.name }"
@click="selectModule = m.name"
>
<div class="module-card__row">
<AppStack direction="row" class="module-card__row">
<span class="module-card__name">{{ m.name }}</span>
<span v-if="activeModule === m.name" class="module-card__check">
<AppIcon name="check" :size="14" />
</span>
</div>
<div class="module-card__meta">
</AppStack>
<AppStack direction="row" gap="space-1" class="module-card__meta">
<span class="module-card__count">{{ m.total }} </span>
<span class="module-card__dot">·</span>
<span
@ -224,7 +224,7 @@ const subtitle = computed(() => {
>
掌握度 {{ formatPercent(masteryByModule.get(m.name) ?? 0) }}
</span>
</div>
</AppStack>
</button>
</div>
<AppEmpty v-if="(modules.data.value?.modules.length ?? 0) === 0" title="还没有题目" description="请先到题库管理导入题目,再开始刷题。">
@ -260,10 +260,10 @@ const subtitle = computed(() => {
<AppEmpty v-if="reasons.length === 0" icon="chart" title="暂无错因数据" description="完成刷题后根据错题统计错因分布。" />
<div v-else class="reason-bars">
<div v-for="r in reasons" :key="r.reason" class="reason-bar">
<div class="reason-bar__row">
<AppStack direction="row" class="reason-bar__row">
<span class="reason-bar__label">{{ r.reason }}</span>
<span class="reason-bar__pct">{{ formatPercent(r.percent) }}</span>
</div>
</AppStack>
<div class="reason-bar__track">
<div class="reason-bar__fill" :style="{ width: `${r.percent}%` }" />
</div>
@ -282,15 +282,15 @@ const subtitle = computed(() => {
class="pending-item"
:to="{ name: 'practice-wrong' }"
>
<div class="pending-item__meta">
<AppStack direction="row" gap="space-2" class="pending-item__meta">
<span class="pending-item__module">{{ item.module }}</span>
<span class="pending-item__sub">{{ item.subModule }}</span>
</div>
</AppStack>
<div class="pending-item__text">{{ item.stem }}</div>
<div class="pending-item__foot">
<AppStack direction="row" class="pending-item__foot">
<span class="pending-item__reason">{{ item.wrongReason }}</span>
<span class="pending-item__count">已复习 {{ item.reviewCount }} </span>
</div>
</AppStack>
</RouterLink>
</div>
</AppCard>
@ -372,13 +372,13 @@ const subtitle = computed(() => {
class="wrong-book-item"
:to="{ name: 'practice-wrong-detail', params: { id: item.id } }"
>
<div class="wrong-book-item__head">
<AppStack direction="row" gap="space-2" class="wrong-book-item__head">
<div class="wrong-book-item__tags">
<AppBadge variant="primary">{{ item.module }}</AppBadge>
<AppBadge v-if="item.subModule" variant="soft">{{ item.subModule }}</AppBadge>
</div>
<AppBadge :variant="wrongStatusVariant(item.status)">{{ wrongStatusLabel(item.status) }}</AppBadge>
</div>
</AppStack>
<p class="wrong-book-item__stem">{{ item.stem }}</p>
<div class="wrong-book-item__foot">
<span v-if="item.wrongReason" class="wrong-book-item__reason">错因 · {{ item.wrongReason }}</span>
@ -462,9 +462,8 @@ const subtitle = computed(() => {
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 14%, transparent);
}
.module-card__row {
display: flex;
align-items: center;
justify-content: space-between;
--stack-align: center;
--stack-justify: space-between;
}
.module-card__name {
font-size: var(--fs-card-title);
@ -481,9 +480,7 @@ const subtitle = computed(() => {
color: var(--on-accent);
}
.module-card__meta {
display: flex;
align-items: center;
gap: var(--space-1);
--stack-align: center;
font-size: var(--fs-label);
color: var(--text-secondary);
}
@ -531,9 +528,8 @@ const subtitle = computed(() => {
gap: var(--space-3);
}
.reason-bar__row {
display: flex;
align-items: center;
justify-content: space-between;
--stack-align: center;
--stack-justify: space-between;
margin-bottom: var(--space-1);
font-size: var(--fs-subtitle);
}
@ -579,9 +575,7 @@ const subtitle = computed(() => {
padding-block: var(--space-1);
}
.pending-item__meta {
display: flex;
align-items: center;
gap: var(--space-2);
--stack-align: center;
font-size: var(--fs-label);
}
.pending-item__module {
@ -601,9 +595,8 @@ const subtitle = computed(() => {
overflow: hidden;
}
.pending-item__foot {
display: flex;
align-items: center;
justify-content: space-between;
--stack-align: center;
--stack-justify: space-between;
font-size: var(--fs-label);
color: var(--text-secondary);
}
@ -679,10 +672,8 @@ const subtitle = computed(() => {
box-shadow: var(--shadow-level-1);
}
.wrong-book-item__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
--stack-align: center;
--stack-justify: space-between;
}
.wrong-book-item__tags {
display: flex;

View File

@ -9,7 +9,8 @@ import AppPageHeader from '../../components/base/AppPageHeader.vue'
import AppStack from '../../components/base/AppStack.vue'
import AppLoading from '../../components/feedback/AppLoading.vue'
import AppError from '../../components/feedback/AppError.vue'
import { formatDateShort, formatDateWeekday } from '../../utils/format'
import { formatDateWeekday } from '../../utils/format'
import { useResponsive } from '../../composables/useResponsive'
const route = useRoute()
const router = useRouter()
@ -19,6 +20,8 @@ const loading = ref(true)
const error = ref('')
const detail = ref<ReviewDetail | null>(null)
const { isMobile } = useResponsive()
//
const submitting = ref(false)
const assessed = ref(false)
@ -151,26 +154,26 @@ function goAiExplain() {
</div>
<!-- 操作区重做此题 / AI 讲解 / 标记掌握 / 自评 -->
<div v-if="!assessed" class="wd__actions">
<AppStack v-if="!assessed" :direction="isMobile ? 'column' : 'row'" gap="var(--space-3)">
<AppButton block size="lg" icon="refresh" @click="router.push(`/practice/session`)" disabled>
重做此题
</AppButton>
<AppButton block size="lg" variant="secondary" icon="sparkles" @click="goAiExplain">
AI 讲解
</AppButton>
</div>
</AppStack>
<!-- 自评区 -->
<section v-if="!assessed && detail.status !== 'mastered'" class="wd__assess">
<p class="wd__assess-title">这次复习结果如何用于安排下次复习</p>
<div class="wd__assess-btns">
<AppStack :direction="isMobile ? 'column' : 'row'" gap="var(--space-3)" class="wd__assess-btns">
<AppButton block size="lg" variant="secondary" :loading="submitting" @click="assess(true)">
记对了
</AppButton>
<AppButton block size="lg" variant="danger" :loading="submitting" @click="assess(false)">
记错了
</AppButton>
</div>
</AppStack>
<div v-if="chosenReason" class="wd__reason-hint">记错原因{{ chosenReason }}</div>
<div class="wd__reason-tags">
<button
@ -343,15 +346,6 @@ function goAiExplain() {
color: var(--text-primary);
}
/* 操作区 */
.wd__actions {
display: flex;
gap: var(--space-3);
}
.wd__actions .btn {
flex: 1;
}
/* 自评区 */
.wd__assess {
background: var(--bg-soft);
@ -368,10 +362,6 @@ function goAiExplain() {
font-weight: var(--fw-button);
color: var(--text-primary);
}
.wd__assess-btns {
display: flex;
gap: var(--space-3);
}
.wd__reason-hint {
font-size: var(--fs-label);
color: var(--warning);
@ -489,13 +479,4 @@ function goAiExplain() {
color: var(--text-secondary);
margin-left: auto;
}
@media (max-width: 767px) {
.wd__actions {
flex-direction: column;
}
.wd__assess-btns {
flex-direction: column;
}
}
</style>

View File

@ -86,7 +86,7 @@ function editProfile() {
/>
<!-- ============ 移动端我的 ============ -->
<div v-else-if="isMobile && d" class="profile-mobile">
<template v-else-if="isMobile && d">
<header class="profile-mobile__head">
<h1>我的</h1>
</header>
@ -165,10 +165,10 @@ function editProfile() {
<p class="profile-tip__text">{{ weakTip }}</p>
</div>
</AppCard>
</div>
</template>
<!-- ============ 桌面端我的 ============ -->
<div v-else-if="d" class="profile-desktop">
<template v-else-if="d">
<AppPageHeader title="我的" subtitle="管理个人资料与备考偏好" sticky-mobile>
<AppButton variant="secondary" @click="editProfile">
<AppIcon name="user" :size="16" />编辑资料
@ -235,15 +235,15 @@ function editProfile() {
</AppCard>
<!-- 本周学习建议 -->
<div class="tip-banner">
<AppStack direction="row" gap="space-3" class="tip-banner">
<span class="tip-banner__icon"><AppIcon name="target" :size="20" /></span>
<div class="tip-banner__body">
<p class="tip-banner__title">本周学习建议</p>
<p class="tip-banner__text">{{ weakTip }}</p>
</div>
<RouterLink class="tip-banner__action" to="/plan/review">去加强</RouterLink>
</div>
</div>
</AppStack>
</template>
</AppStack>
</template>
@ -391,9 +391,7 @@ function editProfile() {
/* 学习建议 */
.tip-banner {
display: flex;
align-items: center;
gap: var(--space-3);
--stack-align: center;
margin-top: var(--space-2);
padding: var(--space-4) var(--space-5);
border-radius: var(--radius-lg);
@ -435,11 +433,6 @@ function editProfile() {
}
/* 移动端 */
.profile-mobile {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.profile-mobile__head h1 {
margin: 0;
font-size: 22px;