kydc/review-session/review-session.js

298 lines
11 KiB
JavaScript
Raw Normal View History

2026-04-18 17:16:49 +08:00
const app = getApp()
2026-04-19 16:39:02 +08:00
const getReviewSettings = () => {
const settings = app.globalData.userSettings || {}
return {
autoNextQuestion: Number(settings.auto_next_question || 0) === 1,
showAnswerAfterSubmit: Number(settings.show_answer_after_submit || 0) === 1,
blankCount: Number(settings.blank_count || 2),
blankDifficulty: settings.blank_difficulty || 'normal',
}
}
2026-04-18 17:16:49 +08:00
Page({
data: {
progress: 0,
current: 1,
total: 0,
word: '',
meaning: '',
answer: '',
2026-04-27 12:37:15 +08:00
answers: [],
clozeAnswers: [],
2026-04-18 17:16:49 +08:00
feedback: '',
2026-04-19 16:39:02 +08:00
revealAnswer: '',
feedbackType: '',
2026-04-18 17:16:49 +08:00
showFeedback: false,
completed: false,
masterHint: false,
questions: [],
2026-04-19 11:56:49 +08:00
loading: false,
error: '',
2026-04-19 16:39:02 +08:00
settings: getReviewSettings(),
2026-04-27 12:37:15 +08:00
questionBadge: '',
entryTypeHint: '',
isCloze: false,
currentClozeQuestion: null,
currentClozeResults: [],
showClozeFeedback: false,
2026-04-27 15:42:58 +08:00
clozeSegments: [],
2026-04-18 17:16:49 +08:00
},
2026-04-19 16:39:02 +08:00
submitting: false,
judged: false,
2026-04-18 17:16:49 +08:00
onLoad() {
2026-04-19 16:39:02 +08:00
this.loadSettingsAndQuestions()
},
loadSettingsAndQuestions() {
if (!app.globalData.openid) {
this.setData({ error: '请先登录' })
return
}
wx.request({
url: `${app.globalData.baseUrl}/api/v1/settings`,
header: { 'X-OpenID': app.globalData.openid },
success: (res) => {
app.globalData.userSettings = res.data?.data || {}
this.setData({ settings: getReviewSettings() })
this.loadQuestions()
},
fail: () => {
this.setData({ error: '设置加载失败' })
this.loadQuestions()
},
})
},
2026-04-27 15:42:58 +08:00
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() {
2026-04-27 15:42:58 +08:00
const session = app.globalData.reviewSessionMeta || wx.getStorageSync('reviewSessionMeta')
if (!session?.id) {
2026-04-19 11:56:49 +08:00
this.setData({ error: '暂无复习会话' })
return
}
2026-04-27 15:42:58 +08:00
app.globalData.reviewSessionMeta = session
2026-04-19 11:56:49 +08:00
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) => {
2026-04-27 15:42:58 +08:00
if (res.statusCode < 200 || res.statusCode >= 300) {
const message = res.data?.detail || res.data?.message || '题目加载失败'
this.setData({ error: message, questions: [] })
return
}
2026-04-19 16:39:02 +08:00
const questions = (res.data?.data || []).map((item) => {
if (item.question_mode === 'C') {
return {
...item,
2026-04-27 12:37:15 +08:00
blank_prompt: item.blank_prompt || item.cloze_question?.prompt_text || item.prompt,
cloze_question: item.cloze_question || null,
2026-04-19 16:39:02 +08:00
}
}
2026-04-23 11:34:54 +08:00
return {
...item,
entry_type: item.entry_type || 'word',
}
2026-04-19 16:39:02 +08:00
})
2026-04-27 15:42:58 +08:00
if (!questions.length) {
this.setData({ error: '当前复习会话没有题目,请返回重新选择范围', questions: [] })
return
}
2026-04-19 16:39:02 +08:00
app.globalData.reviewSession = {
deck: questions,
currentIndex: 0,
correctCount: 0,
wrongCount: 0,
completed: false,
currentAnswer: '',
2026-04-27 12:37:15 +08:00
currentAnswers: [],
2026-04-19 16:39:02 +08:00
startedAt: Date.now(),
}
this.setData({ questions })
this.syncSession()
},
2026-04-19 11:56:49 +08:00
fail: () => this.setData({ error: '题目加载失败' }),
complete: () => this.setData({ loading: false }),
})
2026-04-18 17:16:49 +08:00
},
syncSession() {
const session = app.globalData.reviewSession
const current = session.deck[session.currentIndex]
const total = session.deck.length || 1
2026-04-27 12:37:15 +08:00
const isCloze = current?.question_mode === 'C'
const clozeQuestion = isCloze ? (current?.cloze_question || null) : null
2026-04-18 17:16:49 +08:00
this.setData({
progress: Math.round((session.currentIndex / total) * 100),
current: session.currentIndex + 1,
total,
2026-04-27 12:37:15 +08:00
word: isCloze ? (current?.blank_prompt || clozeQuestion?.prompt_text || current?.prompt || '') : (current?.prompt || ''),
meaning: isCloze ? (current?.answer_hint || '') : '',
2026-04-18 17:16:49 +08:00
answer: session.currentAnswer || '',
2026-04-27 15:42:58 +08:00
clozeAnswers: isCloze ? (Array.isArray(session.currentAnswers) ? session.currentAnswers : Array.from({ length: currentClozeQuestion?.blanks?.length || 0 }, () => '')) : [],
2026-04-18 17:16:49 +08:00
feedback: '',
2026-04-19 16:39:02 +08:00
revealAnswer: '',
feedbackType: '',
2026-04-18 17:16:49 +08:00
showFeedback: false,
completed: session.completed,
masterHint: false,
2026-04-27 12:37:15 +08:00
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,
2026-04-27 15:42:58 +08:00
clozeSegments: isCloze ? this.buildClozeSegments(clozeQuestion) : [],
2026-04-18 17:16:49 +08:00
})
},
onAnswerInput(e) {
const answer = e.detail.value
app.globalData.reviewSession.currentAnswer = answer
2026-04-27 12:37:15 +08:00
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,
})
2026-04-18 17:16:49 +08:00
},
2026-04-23 11:34:54 +08:00
submitAnswer(showExplanation = false, advanceAfterSubmit = false) {
2026-04-18 17:16:49 +08:00
const session = app.globalData.reviewSession
const current = session.deck[session.currentIndex]
2026-04-19 16:39:02 +08:00
if (!current || this.submitting || this.judged) return
const answer = (this.data.answer || '').trim()
2026-04-27 12:37:15 +08:00
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) {
2026-04-19 16:39:02 +08:00
wx.showToast({ title: '请先输入答案', icon: 'none' })
return
}
this.submitting = true
wx.request({
url: `${app.globalData.baseUrl}/api/v1/review/submit`,
method: 'POST',
header: { 'Content-Type': 'application/json', 'X-OpenID': app.globalData.openid || '' },
2026-04-27 12:37:15 +08:00
data: payload,
success: (res) => {
2026-04-19 11:56:49 +08:00
const result = res.data?.data || {}
session.correctCount += result.is_correct ? 1 : 0
session.wrongCount += result.is_correct ? 0 : 1
2026-04-19 16:39:02 +08:00
this.judged = true
const shouldShowAnswer = showExplanation || this.data.settings.showAnswerAfterSubmit
2026-04-27 12:37:15 +08:00
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({
2026-04-19 16:39:02 +08:00
feedback: result.is_correct ? '回答正确' : '回答错误',
feedbackType: result.is_correct ? 'correct' : 'wrong',
2026-04-27 12:37:15 +08:00
revealAnswer: shouldShowAnswer && correctAnswerText ? `正确答案:${correctAnswerText}` : '',
showFeedback: true,
2026-04-27 12:37:15 +08:00
showClozeFeedback: clozeResults.length > 0,
currentClozeResults: clozeResults,
})
2026-04-27 15:42:58 +08:00
if (this.data.isCloze) {
app.globalData.reviewSession.currentAnswers = []
this.setData({ clozeAnswers: [] })
}
2026-04-23 11:34:54 +08:00
if (this.data.settings.autoNextQuestion || advanceAfterSubmit) {
2026-04-19 16:39:02 +08:00
setTimeout(() => this.nextQuestion(), shouldShowAnswer ? 1200 : 0)
}
},
complete: () => {
this.submitting = false
},
2026-04-18 17:16:49 +08:00
})
},
2026-04-19 16:39:02 +08:00
checkAnswer() {
this.submitAnswer(true)
},
2026-04-18 17:16:49 +08:00
nextQuestion() {
const session = app.globalData.reviewSession
if (!session.deck.length) return
2026-04-19 16:39:02 +08:00
if (!this.judged) {
2026-04-23 11:34:54 +08:00
this.submitAnswer(true, true)
2026-04-19 16:39:02 +08:00
return
}
this.judged = false
2026-04-18 17:16:49 +08:00
if (session.currentIndex >= session.deck.length - 1) {
session.completed = true
2026-04-19 16:39:02 +08:00
session.endedAt = Date.now()
wx.request({
url: `${app.globalData.baseUrl}/api/v1/review/finish?session_id=${app.globalData.reviewSessionMeta.id}`,
method: 'POST',
header: { 'X-OpenID': app.globalData.openid || '' },
success: () => wx.navigateTo({ url: '/pages/review-result/review-result' }),
2026-04-23 11:34:54 +08:00
fail: () => {
wx.showToast({ title: '提交结果失败,已本地完成', icon: 'none' })
wx.navigateTo({ url: '/pages/review-result/review-result' })
},
})
2026-04-18 17:16:49 +08:00
return
}
session.currentIndex += 1
session.currentAnswer = ''
2026-04-27 15:42:58 +08:00
session.currentAnswers = []
2026-04-18 17:16:49 +08:00
this.syncSession()
},
markMastered() {
this.setData({ masterHint: true })
wx.showToast({ title: '已标记', icon: 'success' })
},
})