Files
lexgo/learner/src/views/ReviewView.vue
T
ila 0328505b77 feat: 完成到期单词复习与幂等答题 (#8)
- schema v6 新增 lexgo_term_reviews 与 lexgo_review_answers:语句都是可重试的加法迁移,
  既有已保存词汇按 created_at 立即进入队列;旧二进制回到 v5 仍可写入个人词条
- 固定间隔表 1/2/4/7/15/30/60 天:答对升级封顶 7、答错降级最低 1、再学一次不改等级,
  答错与再学立即回到本轮;已知与忽略不入队
- GET /api/v1/reviews/queue 与 POST /api/v1/reviews/:termId/answers:按 answerId 去重、
  按 expectedDueAt 判定过期标签页,重复提交与并发都不重复更新次数和间隔
- 学习端新增 /review 路由与到期复习入口,正面挖空例句、答案面评分、完成页与空队列页
- 同步 Architecture-and-Code-Map、Business-Rules-and-Glossary、
  Local-Development-and-Verification、Product-Requirements-Overview 与 Home
2026-09-11 20:54:17 +08:00

77 lines
3.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
import { ElButton } from 'element-plus'
import ReviewCard from '../components/ReviewCard.vue'
import { useReviewStore, type ReviewItem } from '../stores/review'
import { useSessionStore } from '../stores/session'
const session = useSessionStore()
const review = useReviewStore()
const router = useRouter()
function endReview() { void router.push('/') }
// Retrying reuses the grade of the failed attempt; the store keeps the answer id.
let lastGrade: 'correct' | 'wrong' | 'again' = 'correct'
function grade(value: 'correct' | 'wrong' | 'again') { lastGrade = value; void review.answer(value) }
function retry() { void review.answer(lastGrade) }
onMounted(() => { void review.load() })
async function logout() {
try { await session.logout() }
catch { session.notice = '已退出此设备。服务器暂时无法连接,请稍后重试。' }
finally { await router.replace('/login') }
}
// The template guards for a card before rendering it; this keeps the prop type strict.
const requireItem = (value: ReviewItem | null): ReviewItem => value as ReviewItem
</script>
<template>
<div v-if="session.user">
<header class="site-header">
<RouterLink to="/" class="brand">LexGo<span class="brand-dot">.</span></RouterLink>
<nav aria-label="学习导航"><RouterLink to="/">我的书库</RouterLink></nav>
<div class="account">
<span class="account-name">{{ session.user.username }}</span>
<ElButton text @click="logout">退出登录</ElButton>
</div>
</header>
<main class="page review-page">
<h1>到期复习</h1>
<p v-if="review.loading" role="status" class="loading">正在加载…</p>
<div v-else-if="review.error && !review.current" class="notice">
<p role="alert">{{ review.error }}</p>
<ElButton data-testid="review-reload" @click="review.load()">重试</ElButton>
</div>
<section v-else-if="review.finished" class="review-summary" data-testid="review-summary">
<h2>本次复习完成</h2>
<p>复习了 {{ review.wordsReviewed }} 个词条 · 共 {{ review.answered }} 次作答</p>
<p class="subtle">答对 {{ review.correctCount }} · 答错或再学 {{ review.wrongCount }} · 已更新复习计划</p>
<p v-if="review.pending" role="status" class="notice">还有 {{ review.pending }} 个词条到期。</p>
<div class="review-grades">
<ElButton v-if="review.pending" type="primary" data-testid="review-more" @click="review.continueRound()">继续复习</ElButton>
<ElButton data-testid="review-finish" @click="endReview">继续阅读</ElButton>
</div>
</section>
<section v-else-if="review.empty" class="review-summary" data-testid="review-empty">
<h2>今天没有到期词条</h2>
<p class="subtle">读一篇文章,积累下一次的词汇。</p>
<ElButton data-testid="review-finish" @click="endReview">返回书库</ElButton>
</section>
<ReviewCard
v-else-if="review.current"
:item="requireItem(review.current)"
:position="review.answered + 1"
:total="review.answered + review.queue.length"
:revealed="review.revealed"
:busy="review.busy"
:error="review.error"
@reveal="review.reveal()"
@grade="grade"
@end="endReview"
@retry="retry"
/>
</main>
</div>
</template>