- 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
50 lines
2.8 KiB
Vue
50 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { nextTick, ref, watch } from 'vue'
|
|
import { ElButton } from 'element-plus'
|
|
import { clozeSentence, type ReviewItem } from '../stores/review'
|
|
|
|
const props = defineProps<{ item: ReviewItem; position: number; total: number; revealed: boolean; busy: boolean; error: string }>()
|
|
const emit = defineEmits<{ reveal: []; grade: [grade: 'correct' | 'wrong' | 'again']; end: []; retry: [] }>()
|
|
// The word and its masked example are enough to answer; the definition stays hidden.
|
|
const prompt = () => clozeSentence(props.item)
|
|
const revealButton = ref<{ $el?: HTMLElement } | null>(null)
|
|
const correctButton = ref<{ $el?: HTMLElement } | null>(null)
|
|
// Keyboard users should reach the next action without tabbing through the page again.
|
|
watch(() => props.item.id, async () => {
|
|
await nextTick()
|
|
if (!props.revealed) revealButton.value?.$el?.focus()
|
|
}, { immediate: true })
|
|
watch(() => props.revealed, async value => {
|
|
if (!value) return
|
|
await nextTick()
|
|
correctButton.value?.$el?.focus()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<section class="review-card" aria-labelledby="review-word" data-testid="review-card">
|
|
<p class="subtle" data-testid="review-position">到期复习 · {{ position }} / {{ total }}</p>
|
|
<h2 id="review-word" tabindex="-1" class="review-word">{{ item.originalForm }}</h2>
|
|
<p v-if="prompt()" class="review-example" lang="en">{{ prompt() }}</p>
|
|
<p class="subtle">{{ item.status === 'learning' ? `学习中 · 等级 ${item.level}` : '新词 · 尚未复习' }}</p>
|
|
<p v-if="error" role="alert" class="notice">{{ error }}</p>
|
|
|
|
<template v-if="!revealed">
|
|
<ElButton ref="revealButton" type="primary" data-testid="review-reveal" :disabled="busy || !!error" @click="emit('reveal')">显示答案</ElButton>
|
|
</template>
|
|
<template v-else>
|
|
<div class="review-answer">
|
|
<p class="review-definition" data-testid="review-definition">{{ item.definition || '(未填写个人释义)' }}</p>
|
|
<blockquote v-for="(example, index) in item.examples" :key="index" lang="en">{{ example }}</blockquote>
|
|
</div>
|
|
<div class="review-grades">
|
|
<ElButton ref="correctButton" type="primary" data-testid="review-correct" :loading="busy" @click="emit('grade', 'correct')">认识 / 答对</ElButton>
|
|
<ElButton data-testid="review-wrong" :disabled="busy" @click="emit('grade', 'wrong')">不认识 / 答错</ElButton>
|
|
<ElButton data-testid="review-again" :disabled="busy" @click="emit('grade', 'again')">再学一次</ElButton>
|
|
</div>
|
|
</template>
|
|
<p v-if="error" class="review-actions"><ElButton data-testid="review-retry" @click="emit('retry')">重试提交</ElButton></p>
|
|
<p class="review-actions"><ElButton text data-testid="review-end" @click="emit('end')">结束本次复习</ElButton></p>
|
|
</section>
|
|
</template>
|