修复填空复习功能2.1

This commit is contained in:
taiyi 2026-04-27 15:42:58 +08:00
parent af99db67cb
commit 9da98e9c82
4 changed files with 100 additions and 31 deletions

View File

@ -69,25 +69,45 @@ Page({
const question_mode = questionModeMap[this.data.activeMode]
const scope_config = {}
this.setData({ loading: true, error: '' })
wx.request({
url: `${app.globalData.baseUrl}/api/v1/review/generate`,
method: 'POST',
header: { 'Content-Type': 'application/json', 'X-OpenID': app.globalData.openid || '' },
data: {
scope_type,
scope_config,
content_type,
question_mode,
review_strategy: this.data.onlyReview ? 'review_only' : 'all',
sort_mode: this.data.reviewOrder === 'shuffle' ? 'shuffle' : 'sequential',
total_count: this.data.questionCount,
},
success: (res) => {
app.globalData.reviewSessionMeta = res.data?.data || null
wx.navigateTo({ url: '/pages/review-session/review-session' })
},
fail: () => this.setData({ error: '生成复习会话失败' }),
complete: () => this.setData({ loading: false }),
app.ensureLogin().then((openid) => {
if (!openid) {
this.setData({ loading: false, error: '请先登录后再开始复习' })
return
}
wx.request({
url: `${app.globalData.baseUrl}/api/v1/review/generate`,
method: 'POST',
header: { 'Content-Type': 'application/json', 'X-OpenID': openid },
data: {
scope_type,
scope_config,
content_type,
question_mode,
review_strategy: this.data.onlyReview ? 'review_only' : 'all',
sort_mode: this.data.reviewOrder === 'shuffle' ? 'shuffle' : 'sequential',
total_count: this.data.questionCount,
},
success: (res) => {
const session = res.data?.data || null
if (res.statusCode < 200 || res.statusCode >= 300 || !session?.id) {
const message = res.data?.detail || res.data?.message || '生成复习会话失败'
this.setData({ error: message })
wx.showToast({ title: message, icon: 'none' })
return
}
if (!session.record_ids?.length) {
const message = '当前条件下没有可复习题目,请切换时间范围或关闭仅复习'
this.setData({ error: message })
wx.showToast({ title: message, icon: 'none' })
return
}
app.globalData.reviewSessionMeta = session
wx.setStorageSync('reviewSessionMeta', session)
wx.navigateTo({ url: '/pages/review-session/review-session' })
},
fail: () => this.setData({ error: '生成复习会话失败' }),
complete: () => this.setData({ loading: false }),
})
})
},
goHome() {

View File

@ -36,6 +36,7 @@ Page({
currentClozeQuestion: null,
currentClozeResults: [],
showClozeFeedback: false,
clozeSegments: [],
},
submitting: false,
judged: false,
@ -61,17 +62,55 @@ Page({
},
})
},
buildClozeSegments(clozeQuestion) {
if (!clozeQuestion || !Array.isArray(clozeQuestion.blanks)) return []
const prompt = clozeQuestion.prompt_text || ''
const segments = []
let blankIndex = 0
if (prompt.includes('____')) {
prompt.split('____').forEach((text, index, parts) => {
if (text) segments.push({ type: 'text', text, index: `text-${index}` })
if (index < parts.length - 1) {
segments.push({ type: 'blank', blankIndex, index: `blank-${blankIndex}` })
blankIndex += 1
}
})
return segments
}
const blanks = clozeQuestion.blanks || []
let cursor = 0
blanks.forEach((blank, idx) => {
const start = Math.max(0, Number(blank.start || 0))
const end = Math.max(start, Number(blank.end || start + 1))
if (start > cursor) {
segments.push({ type: 'text', text: prompt.slice(cursor, start), index: `text-${idx}` })
}
segments.push({ type: 'blank', blankIndex: idx, index: `blank-${idx}` })
cursor = end
})
if (cursor < prompt.length) {
segments.push({ type: 'text', text: prompt.slice(cursor), index: `text-tail` })
}
return segments
},
loadQuestions() {
const session = app.globalData.reviewSessionMeta
if (!session) {
const session = app.globalData.reviewSessionMeta || wx.getStorageSync('reviewSessionMeta')
if (!session?.id) {
this.setData({ error: '暂无复习会话' })
return
}
app.globalData.reviewSessionMeta = session
this.setData({ loading: true, error: '' })
wx.request({
url: `${app.globalData.baseUrl}/api/v1/review/questions?session_id=${session.id}`,
header: { 'X-OpenID': app.globalData.openid || '' },
success: (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
const message = res.data?.detail || res.data?.message || '题目加载失败'
this.setData({ error: message, questions: [] })
return
}
const questions = (res.data?.data || []).map((item) => {
if (item.question_mode === 'C') {
return {
@ -85,6 +124,10 @@ Page({
entry_type: item.entry_type || 'word',
}
})
if (!questions.length) {
this.setData({ error: '当前复习会话没有题目,请返回重新选择范围', questions: [] })
return
}
app.globalData.reviewSession = {
deck: questions,
currentIndex: 0,
@ -115,7 +158,7 @@ Page({
word: isCloze ? (current?.blank_prompt || clozeQuestion?.prompt_text || current?.prompt || '') : (current?.prompt || ''),
meaning: isCloze ? (current?.answer_hint || '') : '',
answer: session.currentAnswer || '',
clozeAnswers: isCloze ? (session.currentAnswers || []) : [],
clozeAnswers: isCloze ? (Array.isArray(session.currentAnswers) ? session.currentAnswers : Array.from({ length: currentClozeQuestion?.blanks?.length || 0 }, () => '')) : [],
feedback: '',
revealAnswer: '',
feedbackType: '',
@ -128,6 +171,7 @@ Page({
currentClozeQuestion: clozeQuestion,
currentClozeResults: session.lastClozeResults || [],
showClozeFeedback: false,
clozeSegments: isCloze ? this.buildClozeSegments(clozeQuestion) : [],
})
},
onAnswerInput(e) {
@ -202,6 +246,10 @@ Page({
showClozeFeedback: clozeResults.length > 0,
currentClozeResults: clozeResults,
})
if (this.data.isCloze) {
app.globalData.reviewSession.currentAnswers = []
this.setData({ clozeAnswers: [] })
}
if (this.data.settings.autoNextQuestion || advanceAfterSubmit) {
setTimeout(() => this.nextQuestion(), shouldShowAnswer ? 1200 : 0)
}
@ -239,6 +287,7 @@ Page({
}
session.currentIndex += 1
session.currentAnswer = ''
session.currentAnswers = []
this.syncSession()
},
markMastered() {

View File

@ -10,7 +10,7 @@
<view wx:if="{{!questions.length}}" class="empty-state">
<text class="empty-title">暂无题目</text>
<text class="empty-subtitle">请先生成复习会话</text>
<text class="empty-subtitle">{{error || '请先生成复习会话'}}</text>
</view>
<view wx:else class="glass-card question-card ghost-border">
@ -23,14 +23,14 @@
</view>
<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 wx:if="{{currentClozeQuestion && currentClozeQuestion.blanks.length}}" class="inline-cloze">
<block wx:for="{{clozeSegments}}" wx:for-item="segment" wx:for-index="segmentIndex" wx:key="index">
<text wx:if="{{segment.type === 'text'}}" class="cloze-segment-text">{{segment.text}}</text>
<input wx:else class="answer inline-cloze-answer" data-index="{{segment.blankIndex}}" value="{{clozeAnswers[segment.blankIndex] || ''}}" placeholder="空{{segment.blankIndex + 1}}" bindinput="onClozeAnswerInput" />
</block>
</view>
<text wx:else class="cloze">{{word}}</text>
</view>
<view wx:else class="word-wrap">
<text class="word">{{word}}</text>

File diff suppressed because one or more lines are too long