修复填空复习功能

This commit is contained in:
taiyi 2026-04-27 12:37:15 +08:00
parent b981e17599
commit af99db67cb
9 changed files with 161 additions and 22 deletions

View File

@ -10,6 +10,7 @@ Page({
activeMode: 0,
onlyReview: true,
shuffle: false,
reviewOrder: 'sequential',
questionCount: 30,
loading: false,
error: '',
@ -21,7 +22,8 @@ Page({
activeContentType: plan.contentTypeIndex,
activeMode: plan.modeIndex,
onlyReview: plan.onlyReview,
shuffle: plan.shuffle,
shuffle: plan.reviewOrder === 'shuffle' || plan.shuffle,
reviewOrder: plan.reviewOrder || (plan.shuffle ? 'shuffle' : 'sequential'),
questionCount: plan.questionCount,
})
},
@ -50,8 +52,8 @@ Page({
},
toggleShuffle() {
const shuffle = !this.data.shuffle
this.setData({ shuffle })
this.persistPatch({ shuffle })
this.setData({ shuffle, reviewOrder: shuffle ? 'shuffle' : 'sequential' })
this.persistPatch({ shuffle, reviewOrder: shuffle ? 'shuffle' : 'sequential' })
},
onQuestionCountInput(e) {
const questionCount = Number(e.detail.value || 0)
@ -77,7 +79,7 @@ Page({
content_type,
question_mode,
review_strategy: this.data.onlyReview ? 'review_only' : 'all',
sort_mode: this.data.shuffle ? 'shuffle' : 'sequential',
sort_mode: this.data.reviewOrder === 'shuffle' ? 'shuffle' : 'sequential',
total_count: this.data.questionCount,
},
success: (res) => {

View File

@ -11,6 +11,7 @@ Page({
regressed: 0,
loading: false,
error: '',
clozeResults: [],
},
onLoad() {
this.loadResult()
@ -36,6 +37,24 @@ Page({
const correct = Number(data.correct || 0)
const wrong = Number(data.wrong || Math.max(0, total - correct))
const accuracy = total ? Math.round((correct / total) * 100) : 0
const clozeResults = Array.isArray(data.cloze_results)
? data.cloze_results.map((item) => ({
record_id: item.record_id,
entry_id: item.entry_id,
question_mode: item.question_mode,
user_answer: item.user_answer || '',
prompt_text: item.cloze_question?.prompt_text || '',
blanks: Array.isArray(item.blanks)
? item.blanks.map((blank) => ({
index: blank.index,
user_answer: blank.user_answer || '',
correct_answers: Array.isArray(blank.correct_answers) ? blank.correct_answers : [],
is_correct: !!blank.is_correct,
}))
: [],
is_correct: !!item.blanks?.every?.((blank) => blank.is_correct),
}))
: []
this.setData({
total,
accuracy,
@ -44,6 +63,7 @@ Page({
improved: Number(data.improved ?? 0),
regressed: Number(data.regressed ?? 0),
timeUsed: data.time_used || '--:--',
clozeResults,
})
},
fail: () => this.setData({ error: '结果加载失败' }),

View File

@ -65,6 +65,23 @@
</view>
</view>
<view wx:if="{{clozeResults.length}}" class="cloze-panel">
<text class="panel-title">填空逐空反馈</text>
<view wx:for="{{clozeResults}}" wx:for-item="item" wx:for-index="itemIndex" wx:key="record_id" class="cloze-result-card">
<view class="cloze-result-head">
<text class="cloze-result-index">题目 {{itemIndex + 1}}</text>
<text class="cloze-result-status {{item.is_correct ? 'ok' : 'bad'}}">{{item.is_correct ? '正确' : '错误'}}</text>
</view>
<view wx:if="{{item.prompt_text}}" class="cloze-result-prompt">{{item.prompt_text}}</view>
<view class="cloze-result-body">
<view wx:for="{{item.blanks}}" wx:for-item="blank" wx:for-index="blankIndex" wx:key="index" class="cloze-blank-line">
<text class="cloze-result-answer">空 {{blankIndex + 1}}{{blank.user_answer || '(空)'}}</text>
<text class="cloze-result-answer">答案:{{blank.correct_answers.join(' / ') || '—'}}</text>
</view>
</view>
</view>
</view>
</view>
<view class="actions">

View File

@ -1 +1 @@
page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;padding:48rpx 32rpx 60rpx;box-sizing:border-box;background:#0c0e12;position:relative;overflow:hidden}.bg-orb{position:absolute;border-radius:9999rpx;pointer-events:none;filter:blur(120rpx);opacity:.2;z-index:0}.orb1{top:18%;left:50%;width:520rpx;height:520rpx;transform:translate(-50%,-50%);background:#ff6c95}.orb2{bottom:18%;left:50%;width:380rpx;height:380rpx;transform:translate(-50%,50%);background:#00e5ff}.hero,.grid,.actions{position:relative;z-index:1}.hero{display:flex;flex-direction:column;align-items:center;gap:22rpx;margin:26rpx 0 44rpx}.badge{width:144rpx;height:144rpx;border-radius:9999rpx;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.06);box-shadow:0 0 30rpx rgba(255,108,149,.2)}.badge-inner{width:100%;height:100%;border-radius:9999rpx;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,rgba(255,108,149,.2),rgba(0,229,255,.2));font-size:56rpx}.headline{display:block;font-size:54rpx;font-weight:800;text-align:center}.subtitle{display:block;margin-top:8rpx;color:#00e5ff;letter-spacing:4rpx;text-transform:uppercase;text-align:center}.grid{display:grid;grid-template-columns:1fr 1fr;gap:18rpx}.hero-card{grid-column:1/-1;background:rgba(29,32,37,.7);backdrop-filter:blur(20rpx);border:1rpx solid rgba(255,255,255,.1);border-radius:28rpx;padding:32rpx;box-sizing:border-box;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);position:relative;overflow:hidden}.hero-card:after{content:'';position:absolute;top:-40rpx;right:-40rpx;width:160rpx;height:160rpx;border-radius:9999rpx;background:rgba(193,156,255,.1);filter:blur(48rpx)}.hero-card-head{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}.metric-label{display:block;color:#aaabb0;font-size:20rpx;letter-spacing:4rpx;text-transform:uppercase}.metric-row{display:flex;align-items:baseline;gap:10rpx}.metric-big{font-size:88rpx;font-weight:800;color:#fff;line-height:1}.metric-unit{font-size:32rpx;color:#aaabb0;font-weight:700}.ring{width:124rpx;height:124rpx;border-radius:9999rpx;border:4rpx solid #23262c;display:flex;align-items:center;justify-content:center;color:#c19cff;font-size:52rpx;position:relative}.mini-card{background:rgba(29,32,37,.7);backdrop-filter:blur(20rpx);border:1rpx solid rgba(255,255,255,.1);border-radius:28rpx;padding:24rpx;box-sizing:border-box;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);display:flex;flex-direction:column;gap:18rpx}.mini-head{display:flex;align-items:center;gap:14rpx}.mini-ico{width:56rpx;height:56rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center;font-size:28rpx}.mini-label{color:#aaabb0;font-size:20rpx;letter-spacing:4rpx;text-transform:uppercase}.mini-value{font-size:36rpx;font-weight:800}.breakdown{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:0;border-radius:28rpx;overflow:hidden;border:1rpx solid rgba(255,255,255,.1)}.break-item{background:#1d2025;padding:30rpx;text-align:center}.break-num{display:block;font-size:54rpx;font-weight:800}.cyan{color:#00e5ff}.pink{color:#ff6c95}.break-label{display:block;margin-top:10rpx;color:#aaabb0;font-size:24rpx}.achievement{grid-column:1/-1;display:flex;align-items:center;gap:22rpx;padding:26rpx 24rpx;border-radius:28rpx;background:linear-gradient(90deg,#1d2025,#171a1f);border:1rpx solid rgba(255,255,255,.08);box-shadow:0 20rpx 40rpx rgba(0,0,0,.4)}.ach-ico{width:96rpx;height:96rpx;border-radius:9999rpx;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#ff6c95,#00e5ff);font-size:44rpx;flex-shrink:0}.ach-sub{display:block;color:#ff6c95;font-size:20rpx;letter-spacing:4rpx;text-transform:uppercase;font-weight:700}.ach-title{display:block;margin-top:6rpx;font-size:32rpx;font-weight:700}.actions{display:flex;gap:18rpx;margin-top:28rpx;position:relative;z-index:1}.primary-btn,.secondary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:800}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.secondary-btn{background:#23262c;color:#f6f6fc}
page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;padding:48rpx 32rpx 60rpx;box-sizing:border-box;background:#0c0e12;position:relative;overflow:hidden}.bg-orb{position:absolute;border-radius:9999rpx;pointer-events:none;filter:blur(120rpx);opacity:.2;z-index:0}.orb1{top:18%;left:50%;width:520rpx;height:520rpx;transform:translate(-50%,-50%);background:#ff6c95}.orb2{bottom:18%;left:50%;width:380rpx;height:380rpx;transform:translate(-50%,50%);background:#00e5ff}.hero,.grid,.actions{position:relative;z-index:1}.hero{display:flex;flex-direction:column;align-items:center;gap:22rpx;margin:26rpx 0 44rpx}.badge{width:144rpx;height:144rpx;border-radius:9999rpx;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.06);box-shadow:0 0 30rpx rgba(255,108,149,.2)}.badge-inner{width:100%;height:100%;border-radius:9999rpx;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,rgba(255,108,149,.2),rgba(0,229,255,.2));font-size:56rpx}.headline{display:block;font-size:54rpx;font-weight:800;text-align:center}.subtitle{display:block;margin-top:8rpx;color:#00e5ff;letter-spacing:4rpx;text-transform:uppercase;text-align:center}.grid{display:grid;grid-template-columns:1fr 1fr;gap:18rpx}.hero-card{grid-column:1/-1;background:rgba(29,32,37,.7);backdrop-filter:blur(20rpx);border:1rpx solid rgba(255,255,255,.1);border-radius:28rpx;padding:32rpx;box-sizing:border-box;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);position:relative;overflow:hidden}.hero-card:after{content:'';position:absolute;top:-40rpx;right:-40rpx;width:160rpx;height:160rpx;border-radius:9999rpx;background:rgba(193,156,255,.1);filter:blur(48rpx)}.hero-card-head{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}.metric-label{display:block;color:#aaabb0;font-size:20rpx;letter-spacing:4rpx;text-transform:uppercase}.metric-row{display:flex;align-items:baseline;gap:10rpx}.metric-big{font-size:88rpx;font-weight:800;color:#fff;line-height:1}.metric-unit{font-size:32rpx;color:#aaabb0;font-weight:700}.ring{width:124rpx;height:124rpx;border-radius:9999rpx;border:4rpx solid #23262c;display:flex;align-items:center;justify-content:center;color:#c19cff;font-size:52rpx;position:relative}.mini-card{background:rgba(29,32,37,.7);backdrop-filter:blur(20rpx);border:1rpx solid rgba(255,255,255,.1);border-radius:28rpx;padding:24rpx;box-sizing:border-box;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);display:flex;flex-direction:column;gap:18rpx}.mini-head{display:flex;align-items:center;gap:14rpx}.mini-ico{width:56rpx;height:56rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center;font-size:28rpx}.mini-label{color:#aaabb0;font-size:20rpx;letter-spacing:4rpx;text-transform:uppercase}.mini-value{font-size:36rpx;font-weight:800}.breakdown{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:0;border-radius:28rpx;overflow:hidden;border:1rpx solid rgba(255,255,255,.1)}.break-item{background:#1d2025;padding:30rpx;text-align:center}.break-num{display:block;font-size:54rpx;font-weight:800}.cyan{color:#00e5ff}.pink{color:#ff6c95}.break-label{display:block;margin-top:10rpx;color:#aaabb0;font-size:24rpx}.cloze-panel{grid-column:1/-1;margin-top:8rpx;padding:28rpx;border-radius:28rpx;background:rgba(29,32,37,.7);border:1rpx solid rgba(255,255,255,.1);box-shadow:0 20rpx 40rpx rgba(0,0,0,.28)}.panel-title{display:block;font-size:28rpx;font-weight:800;margin-bottom:18rpx;color:#f6f6fc}.cloze-result-card{padding:22rpx 20rpx;border-radius:22rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06);margin-bottom:16rpx}.cloze-result-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12rpx}.cloze-result-index{font-size:24rpx;color:#aaabb0}.cloze-result-status{font-size:22rpx;font-weight:700;padding:8rpx 16rpx;border-radius:9999rpx}.cloze-result-status.ok{background:rgba(0,229,255,.12);color:#00e5ff}.cloze-result-status.bad{background:rgba(255,108,149,.12);color:#ff6c95}.cloze-result-prompt{margin-bottom:14rpx;color:#d8c7ff;font-size:24rpx;line-height:1.6}.cloze-result-body{display:flex;flex-direction:column;gap:10rpx}.cloze-blank-line{display:flex;flex-direction:column;gap:6rpx;padding:14rpx 14rpx;border-radius:16rpx;background:rgba(255,255,255,.03)}.cloze-result-answer{font-size:24rpx;line-height:1.6;color:#e8e9ee}.actions{display:flex;gap:18rpx;margin-top:28rpx;position:relative;z-index:1}.primary-btn,.secondary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:800}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.secondary-btn{background:#23262c;color:#f6f6fc}

View File

@ -18,6 +18,8 @@ Page({
word: '',
meaning: '',
answer: '',
answers: [],
clozeAnswers: [],
feedback: '',
revealAnswer: '',
feedbackType: '',
@ -28,6 +30,12 @@ Page({
loading: false,
error: '',
settings: getReviewSettings(),
questionBadge: '',
entryTypeHint: '',
isCloze: false,
currentClozeQuestion: null,
currentClozeResults: [],
showClozeFeedback: false,
},
submitting: false,
judged: false,
@ -68,7 +76,8 @@ Page({
if (item.question_mode === 'C') {
return {
...item,
blank_prompt: item.blank_prompt || item.prompt,
blank_prompt: item.blank_prompt || item.cloze_question?.prompt_text || item.prompt,
cloze_question: item.cloze_question || null,
}
}
return {
@ -83,6 +92,7 @@ Page({
wrongCount: 0,
completed: false,
currentAnswer: '',
currentAnswers: [],
startedAt: Date.now(),
}
this.setData({ questions })
@ -96,25 +106,50 @@ Page({
const session = app.globalData.reviewSession
const current = session.deck[session.currentIndex]
const total = session.deck.length || 1
const isCloze = current?.question_mode === 'C'
const clozeQuestion = isCloze ? (current?.cloze_question || null) : null
this.setData({
progress: Math.round((session.currentIndex / total) * 100),
current: session.currentIndex + 1,
total,
word: current?.question_mode === 'C' ? (current?.blank_prompt || current?.prompt || '') : (current?.prompt || ''),
meaning: current?.question_mode === 'C' ? (current?.answer_hint || '') : '',
word: isCloze ? (current?.blank_prompt || clozeQuestion?.prompt_text || current?.prompt || '') : (current?.prompt || ''),
meaning: isCloze ? (current?.answer_hint || '') : '',
answer: session.currentAnswer || '',
clozeAnswers: isCloze ? (session.currentAnswers || []) : [],
feedback: '',
revealAnswer: '',
feedbackType: '',
showFeedback: false,
completed: session.completed,
masterHint: false,
questionBadge: isCloze ? `填空 · ${current?.entry_type === 'sentence' ? '句子' : '单词'}` : `${current?.entry_type === 'sentence' ? '句子' : '单词'} · ${current?.question_mode || 'A'}`,
entryTypeHint: isCloze ? '请逐空作答,提交后会显示每个空的结果。' : (current?.entry_type === 'sentence' ? '当前题目来自句子库,注意上下文表达。' : ''),
isCloze,
currentClozeQuestion: clozeQuestion,
currentClozeResults: session.lastClozeResults || [],
showClozeFeedback: false,
})
},
onAnswerInput(e) {
const answer = e.detail.value
app.globalData.reviewSession.currentAnswer = answer
this.setData({ answer, showFeedback: false, revealAnswer: '', feedback: '', feedbackType: '', judged: false })
this.setData({ answer, showFeedback: false, revealAnswer: '', feedback: '', feedbackType: '', judged: false, showClozeFeedback: false })
},
onClozeAnswerInput(e) {
const { index } = e.currentTarget.dataset
const value = e.detail.value
const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
answers[index] = value
app.globalData.reviewSession.currentAnswers = answers
this.setData({
[`clozeAnswers[${index}]`]: value,
showFeedback: false,
revealAnswer: '',
feedback: '',
feedbackType: '',
judged: false,
showClozeFeedback: false,
})
},
submitAnswer(showExplanation = false, advanceAfterSubmit = false) {
const session = app.globalData.reviewSession
@ -122,7 +157,21 @@ Page({
if (!current || this.submitting || this.judged) return
const answer = (this.data.answer || '').trim()
if (!answer) {
const clozeQuestion = current.cloze_question
const payload = {
session_id: app.globalData.reviewSessionMeta.id,
record_id: current.record_id,
user_answer: answer,
}
if (current.question_mode === 'C' && clozeQuestion?.blanks?.length) {
const answers = (this.data.clozeAnswers || []).map((item) => (item || '').trim())
while (answers.length < clozeQuestion.blanks.length) answers.push('')
if (!answers.some((item) => item)) {
wx.showToast({ title: '请先输入答案', icon: 'none' })
return
}
payload.user_answers = answers
} else if (!answer) {
wx.showToast({ title: '请先输入答案', icon: 'none' })
return
}
@ -132,22 +181,26 @@ Page({
url: `${app.globalData.baseUrl}/api/v1/review/submit`,
method: 'POST',
header: { 'Content-Type': 'application/json', 'X-OpenID': app.globalData.openid || '' },
data: {
session_id: app.globalData.reviewSessionMeta.id,
record_id: current.record_id,
user_answer: answer,
},
data: payload,
success: (res) => {
const result = res.data?.data || {}
session.correctCount += result.is_correct ? 1 : 0
session.wrongCount += result.is_correct ? 0 : 1
this.judged = true
const shouldShowAnswer = showExplanation || this.data.settings.showAnswerAfterSubmit
const correctAnswerText = result.correct_answer_text || ''
const clozeResults = Array.isArray(result.cloze_results) ? result.cloze_results : []
if (clozeResults.length) {
session.lastClozeResults = clozeResults
app.globalData.reviewSession.lastClozeResults = clozeResults
}
this.setData({
feedback: result.is_correct ? '回答正确' : '回答错误',
feedbackType: result.is_correct ? 'correct' : 'wrong',
revealAnswer: shouldShowAnswer ? `正确答案:${result.correct_answer}` : '',
revealAnswer: shouldShowAnswer && correctAnswerText ? `正确答案:${correctAnswerText}` : '',
showFeedback: true,
showClozeFeedback: clozeResults.length > 0,
currentClozeResults: clozeResults,
})
if (this.data.settings.autoNextQuestion || advanceAfterSubmit) {
setTimeout(() => this.nextQuestion(), shouldShowAnswer ? 1200 : 0)

View File

@ -16,26 +16,42 @@
<view wx:else class="glass-card question-card ghost-border">
<view class="ambient-glow"></view>
<view class="mode-tag-wrap">
<text class="mode-tag">{{questions[current-1].entry_type === 'sentence' ? '句子' : '单词'}} · {{questions[current-1].question_mode || 'C'}}</text>
<text class="mode-tag">{{questionBadge}}</text>
</view>
<view class="meaning-wrap" wx:if="{{meaning}}">
<text class="meaning">{{meaning}}</text>
</view>
<view wx:if="{{questions[current-1].question_mode === 'C'}}" class="cloze-wrap">
<text class="cloze">{{questions[current-1].blank_prompt || word}}</text>
<view wx:if="{{isCloze}}" class="cloze-wrap">
<text class="cloze">{{word}}</text>
<view wx:if="{{currentClozeQuestion && currentClozeQuestion.blanks.length}}" class="cloze-input-list">
<view wx:for="{{currentClozeQuestion.blanks}}" wx:for-item="blank" wx:for-index="blankIndex" wx:key="index" class="cloze-input-item">
<text class="cloze-input-label">空 {{blankIndex + 1}}</text>
<view wx:if="{{blank.hint && blank.hint.first_letter}}" class="cloze-hint">提示:首字母 {{blank.hint.first_letter}} · 长度 {{blank.hint.length}}</view>
<input class="answer cloze-answer" data-index="{{blankIndex}}" value="{{clozeAnswers[blankIndex] || ''}}" placeholder="输入第 {{blankIndex + 1}} 个空" bindinput="onClozeAnswerInput" />
</view>
</view>
</view>
<view wx:else class="word-wrap">
<text class="word">{{word}}</text>
</view>
<view class="entry-type-hint" wx:if="{{questions[current-1].entry_type === 'sentence'}}">当前题目来自句子库,注意上下文表达。</view>
<view class="entry-type-hint" wx:if="{{entryTypeHint}}">{{entryTypeHint}}</view>
<view class="input-shell">
<view class="input-shell" wx:if="{{!isCloze}}">
<input class="answer" value="{{answer}}" placeholder="输入答案..." bindinput="onAnswerInput" />
</view>
<view wx:if="{{showFeedback}}" class="feedback-box {{feedbackType}}">
<text class="feedback">{{feedback}}</text>
<text wx:if="{{revealAnswer}}" class="reveal-answer">{{revealAnswer}}</text>
<view wx:if="{{showClozeFeedback && currentClozeResults.length}}" class="cloze-feedback-list">
<block wx:for="{{currentClozeResults}}" wx:for-item="blankResult" wx:for-index="blankIndex" wx:key="index">
<view class="cloze-feedback-item">
<text class="cloze-feedback-title">空 {{blankIndex + 1}} · {{blankResult.is_correct ? '正确' : '错误'}}</text>
<text class="cloze-feedback-text">你的答案:{{blankResult.user_answer || '(空)'}}</text>
<text class="cloze-feedback-text">标准答案:{{blankResult.correct_answers.join(' / ')}}</text>
</view>
</block>
</view>
</view>
</view>

View File

@ -1 +1 @@
page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;background:#0c0e12}.topbar{display:flex;justify-content:space-between;align-items:center;padding:24rpx 32rpx;background:rgba(12,14,18,.8);border-bottom:1rpx solid rgba(255,255,255,.08);backdrop-filter:blur(24px);box-shadow:0 1rpx 20rpx rgba(0,0,0,.08)}.topbar-left{display:flex;align-items:center;gap:18rpx}.avatar-shell{width:80rpx;height:80rpx;border-radius:9999rpx;overflow:hidden;border:2rpx solid rgba(193,156,255,.2);flex-shrink:0}.avatar-img{width:100%;height:100%}.brand-text{font-size:38rpx;font-weight:800;color:#c19cff}.topbar-icon{width:72rpx;height:72rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center}.content{height:calc(100vh - 120rpx);padding:32rpx;box-sizing:border-box}.progress-box{margin-bottom:28rpx}.progress-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:16rpx}.progress-label{font-size:24rpx;color:#aaabb0}.progress-count{font-size:28rpx;font-weight:700;color:#00e3fd}.progress-total{color:#aaabb0;font-size:22rpx;font-weight:400}.progress-bar{height:12rpx;background:#23262c;border-radius:9999rpx;overflow:hidden}.ghost-border{border:1rpx solid rgba(255,255,255,.1)}.progress-fill{height:100%;background:linear-gradient(90deg,#c19cff,#00e3fd)}.glass-card{background:rgba(29,32,37,.4);backdrop-filter:blur(24rpx)}.question-card{position:relative;overflow:hidden;border-radius:32rpx;padding:40rpx 32rpx;text-align:center;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);border:1rpx solid rgba(255,255,255,.1)}.ambient-glow{position:absolute;inset:0;background:linear-gradient(to bottom,rgba(193,156,255,.05),transparent);pointer-events:none}.mode-tag-wrap{display:flex;justify-content:center;margin-bottom:20rpx}.mode-tag{display:inline-flex;align-items:center;gap:10rpx;padding:10rpx 22rpx;border-radius:9999rpx;background:rgba(193,156,255,.12);border:1rpx solid rgba(193,156,255,.18);color:#d8c7ff;font-size:22rpx;letter-spacing:2rpx}.meaning-wrap{margin-bottom:28rpx}.meaning{display:block;color:#aaabb0;font-size:26rpx;letter-spacing:2rpx}.cloze-wrap,.word-wrap{margin:14rpx 0 10rpx}.cloze,.word{display:block;font-size:58rpx;font-weight:800;letter-spacing:2rpx;line-height:1.25}.cloze{color:#f6f6fc}.word{color:#f6f6fc}.entry-type-hint{margin-top:14rpx;color:#8f9198;font-size:22rpx;line-height:1.6}.gap{color:#9146ff;opacity:.5}.input-shell{margin:26rpx auto 0;max-width:520rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);border-radius:9999rpx}.answer{padding:22rpx 28rpx;text-align:center;color:#f6f6fc;font-size:32rpx;caret-color:#c19cff}.answer::placeholder{color:rgba(170,171,176,.72)}.answer::-webkit-input-placeholder{color:rgba(170,171,176,.72)}.answer::-moz-placeholder{color:rgba(170,171,176,.72)}.answer:-ms-input-placeholder{color:rgba(170,171,176,.72)}.feedback-box{margin-top:22rpx;padding:20rpx 24rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.feedback{display:block;color:#00e3fd}.reveal-answer{display:block;margin-top:10rpx;color:#f6f6fc;font-weight:700}.actions-cluster{display:flex;flex-direction:row;gap:18rpx;justify-content:center;align-items:center;margin-top:24rpx}.secondary-btn,.primary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:700}.secondary-btn{background:#23262c;color:#f6f6fc}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.master-action{margin-top:34rpx;text-align:center}.master-text{color:#aaabb0;font-size:22rpx;border-bottom:1rpx solid transparent}.master-text.active{color:#00e3fd;border-color:#00e3fd}
page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;background:#0c0e12}.topbar{display:flex;justify-content:space-between;align-items:center;padding:24rpx 32rpx;background:rgba(12,14,18,.8);border-bottom:1rpx solid rgba(255,255,255,.08);backdrop-filter:blur(24px);box-shadow:0 1rpx 20rpx rgba(0,0,0,.08)}.topbar-left{display:flex;align-items:center;gap:18rpx}.avatar-shell{width:80rpx;height:80rpx;border-radius:9999rpx;overflow:hidden;border:2rpx solid rgba(193,156,255,.2);flex-shrink:0}.avatar-img{width:100%;height:100%}.brand-text{font-size:38rpx;font-weight:800;color:#c19cff}.topbar-icon{width:72rpx;height:72rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center}.content{height:calc(100vh - 120rpx);padding:32rpx;box-sizing:border-box}.progress-box{margin-bottom:28rpx}.progress-head{display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:16rpx}.progress-label{font-size:24rpx;color:#aaabb0}.progress-count{font-size:28rpx;font-weight:700;color:#00e3fd}.progress-total{color:#aaabb0;font-size:22rpx;font-weight:400}.progress-bar{height:12rpx;background:#23262c;border-radius:9999rpx;overflow:hidden}.ghost-border{border:1rpx solid rgba(255,255,255,.1)}.progress-fill{height:100%;background:linear-gradient(90deg,#c19cff,#00e3fd)}.glass-card{background:rgba(29,32,37,.4);backdrop-filter:blur(24rpx)}.question-card{position:relative;overflow:hidden;border-radius:32rpx;padding:40rpx 32rpx;text-align:center;box-shadow:0 20rpx 40rpx rgba(0,0,0,.4);border:1rpx solid rgba(255,255,255,.1)}.ambient-glow{position:absolute;inset:0;background:linear-gradient(to bottom,rgba(193,156,255,.05),transparent);pointer-events:none}.mode-tag-wrap{display:flex;justify-content:center;margin-bottom:20rpx}.mode-tag{display:inline-flex;align-items:center;gap:10rpx;padding:10rpx 22rpx;border-radius:9999rpx;background:rgba(193,156,255,.12);border:1rpx solid rgba(193,156,255,.18);color:#d8c7ff;font-size:22rpx;letter-spacing:2rpx}.meaning-wrap{margin-bottom:28rpx}.meaning{display:block;color:#aaabb0;font-size:26rpx;letter-spacing:2rpx}.cloze-wrap,.word-wrap{margin:14rpx 0 10rpx}.cloze,.word{display:block;font-size:58rpx;font-weight:800;letter-spacing:2rpx;line-height:1.25}.cloze{color:#f6f6fc}.word{color:#f6f6fc}.entry-type-hint{margin-top:14rpx;color:#8f9198;font-size:22rpx;line-height:1.6}.cloze-input-list{margin-top:28rpx;display:flex;flex-direction:column;gap:20rpx}.cloze-input-item{padding:20rpx 18rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);text-align:left}.cloze-input-label{display:block;margin-bottom:12rpx;color:#d8c7ff;font-size:22rpx;font-weight:700;letter-spacing:1rpx}.cloze-hint{margin-bottom:12rpx;color:#8f9198;font-size:20rpx;line-height:1.5}.input-shell{margin:26rpx auto 0;max-width:520rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.08);border-radius:9999rpx}.answer{padding:22rpx 28rpx;text-align:center;color:#f6f6fc;font-size:32rpx;caret-color:#c19cff}.cloze-answer{max-width:100%;background:rgba(12,14,18,.35);border-radius:18rpx;border:1rpx solid rgba(255,255,255,.08);text-align:left;padding:22rpx 24rpx;margin:0 4rpx}.answer::placeholder{color:rgba(170,171,176,.72)}.answer::-webkit-input-placeholder{color:rgba(170,171,176,.72)}.answer::-moz-placeholder{color:rgba(170,171,176,.72)}.answer:-ms-input-placeholder{color:rgba(170,171,176,.72)}.feedback-box{margin-top:22rpx;padding:20rpx 24rpx;border-radius:24rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.feedback{display:block;color:#00e3fd}.reveal-answer{display:block;margin-top:10rpx;color:#f6f6fc;font-weight:700}.cloze-feedback-list{margin-top:18rpx;display:flex;flex-direction:column;gap:14rpx}.cloze-feedback-item{padding:18rpx 16rpx;border-radius:18rpx;background:rgba(255,255,255,.04);border:1rpx solid rgba(255,255,255,.06)}.cloze-feedback-title{display:block;margin-bottom:8rpx;font-size:22rpx;font-weight:700;color:#c19cff}.cloze-feedback-text{display:block;font-size:22rpx;line-height:1.55;color:#e8e9ee}.actions-cluster{display:flex;flex-direction:row;gap:18rpx;justify-content:center;align-items:center;margin-top:24rpx}.secondary-btn,.primary-btn{flex:1;border-radius:9999rpx;padding:24rpx 0;font-weight:700}.secondary-btn{background:#23262c;color:#f6f6fc}.primary-btn{background:linear-gradient(90deg,#c19cff,#9146ff);color:#fff}.master-action{margin-top:34rpx;text-align:center}.master-text{color:#aaabb0;font-size:22rpx;border-bottom:1rpx solid transparent}.master-text.active{color:#00e5fd;border-color:#00e5fd}

View File

@ -6,24 +6,35 @@ const difficultyOptions = [
{ label: '困难', value: 'hard' },
]
const reviewOrderOptions = [
{ label: '顺序复习', value: 'sequential' },
{ label: '随机复习', value: 'shuffle' },
]
Page({
data: {
settings: null,
loading: false,
error: '',
difficultyOptions,
reviewOrderOptions,
difficultyIndex: 1,
reviewOrderIndex: 0,
difficultyLabel: '普通',
reviewOrderLabel: '顺序复习',
},
onShow() {
this.loadSettings()
},
syncDerivedState(settings) {
const difficultyIndex = Math.max(0, difficultyOptions.findIndex((item) => item.value === settings.blank_difficulty))
const reviewOrderIndex = Math.max(0, reviewOrderOptions.findIndex((item) => item.value === settings.review_order))
this.setData({
settings,
difficultyIndex: difficultyIndex >= 0 ? difficultyIndex : 1,
difficultyLabel: difficultyOptions[difficultyIndex >= 0 ? difficultyIndex : 1].label,
reviewOrderIndex: reviewOrderIndex >= 0 ? reviewOrderIndex : 0,
reviewOrderLabel: reviewOrderOptions[reviewOrderIndex >= 0 ? reviewOrderIndex : 0].label,
})
},
loadSettings() {
@ -63,6 +74,18 @@ Page({
},
})
},
onReviewOrderChange(e) {
const index = Number(e.detail.value)
const option = reviewOrderOptions[index] || reviewOrderOptions[0]
this.setData({
reviewOrderIndex: index,
reviewOrderLabel: option.label,
settings: {
...this.data.settings,
review_order: option.value,
},
})
},
toggleSetting(e) {
const key = e.currentTarget.dataset.key
const settings = { ...this.data.settings }

View File

@ -34,6 +34,14 @@
</picker>
</view>
</view>
<view class="row">
<view><text class="label">复习顺序</text><text class="desc">控制复习题目按顺序还是随机展示</text></view>
<view class="value-group">
<picker mode="selector" range="{{reviewOrderOptions}}" range-key="label" value="{{reviewOrderIndex}}" bindchange="onReviewOrderChange">
<view class="picker-value accent">{{reviewOrderLabel}}</view>
</picker>
</view>
</view>
</view>
</view>