修复BUG

This commit is contained in:
taiyi 2026-04-30 11:15:39 +08:00
parent 44ac502d5b
commit 1a03509b40
6 changed files with 352 additions and 41 deletions

View File

@ -1,14 +1,12 @@
const app = getApp() const app = getApp()
const ACHIEVEMENT_ICON_MAP = { const ACHIEVEMENT_ICON_MAP = {
MASTERED_10: '/assets/icons/achievements/core/mastered-10.svg', MASTERED_100: '/assets/icons/achievements/core/100.svg',
MASTERED_50: '/assets/icons/achievements/core/mastered-50.svg', MASTERED_1000: '/assets/icons/achievements/core/1000.svg',
MASTERED_100: '/assets/icons/achievements/core/mastery-advanced.svg', MASTERED_3000: '/assets/icons/achievements/core/3000.svg',
MASTERED_500: '/assets/icons/achievements/core/mastery-advanced.svg', MASTERED_5000: '/assets/icons/achievements/core/5000.svg',
MASTERED_1000: '/assets/icons/achievements/core/mastery-advanced.svg', MASTERED_7000: '/assets/icons/achievements/core/7000.svg',
MASTERED_2000: '/assets/icons/achievements/core/mastery-2000.svg', MASTERED_10000: '/assets/icons/achievements/core/10000.svg',
MASTERED_5000: '/assets/icons/achievements/core/mastery-5000.svg',
MASTERED_10000: '/assets/icons/achievements/core/mastery-10000.svg',
TOTAL_100: '/assets/icons/achievements/core/total-100.svg', TOTAL_100: '/assets/icons/achievements/core/total-100.svg',
CHECKIN_7: '/assets/icons/achievements/core/checkin-30.svg', CHECKIN_7: '/assets/icons/achievements/core/checkin-30.svg',
CHECKIN_30: '/assets/icons/achievements/core/checkin-30.svg', CHECKIN_30: '/assets/icons/achievements/core/checkin-30.svg',
@ -50,7 +48,7 @@ const GROUP_META = {
} }
const GROUP_PROGRESS = { const GROUP_PROGRESS = {
mastery: [10, 50, 100, 500, 1000, 2000, 5000, 10000], mastery: [100, 1000, 3000, 5000, 7000, 10000],
checkin: [7, 30, 100, 180, 360], checkin: [7, 30, 100, 180, 360],
streak: [7, 30, 100, 180, 360], streak: [7, 30, 100, 180, 360],
accuracy: [80, 90], accuracy: [80, 90],

View File

@ -1 +1,287 @@
page{background:#0c0e12;color:#f6f6fc}.page{min-height:100vh;background:#0c0e12;box-sizing:border-box}.topbar{display:flex;justify-content:space-between;align-items:center;padding:24rpx 32rpx;background:rgba(12,14,18,.8);backdrop-filter:blur(24px);border-bottom:1rpx solid rgba(255,255,255,.08);box-shadow:0 1rpx 20rpx rgba(0,0,0,.08)}.brand{display:flex;align-items:center;gap:18rpx}.avatar-wrap{width:64rpx;height:64rpx;border-radius:9999rpx;overflow:hidden;border:1rpx solid rgba(255,255,255,.1);flex-shrink:0}.avatar{width:100%;height:100%}.brand-text{font-size:38rpx;font-weight:800;color:#c19cff}.icon-btn{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:48rpx 40rpx 32rpx;box-sizing:border-box}.heading{margin-bottom:28rpx}.title{display:block;font-size:52rpx;font-weight:800;line-height:1.1}.subtitle{display:block;margin-top:10rpx;color:#aaabb0;font-size:26rpx}.hero-card{position:relative;overflow:hidden;padding:32rpx;border-radius:32rpx;background:rgba(29,32,37,.6);backdrop-filter:blur(24rpx);border:1rpx solid rgba(255,255,255,.08);box-shadow:0 20rpx 40rpx rgba(0,0,0,.08);margin-bottom:24rpx}.hero-glow{position:absolute;right:-40rpx;top:-40rpx;width:160rpx;height:160rpx;border-radius:9999rpx;background:rgba(255,64,129,.12);filter:blur(30rpx)}.hero-row{display:flex;justify-content:space-between;align-items:center;position:relative;z-index:1}.hero-num{display:block;font-size:72rpx;font-weight:800;color:#fff}.hero-label{display:block;margin-top:8rpx;font-size:24rpx;color:#aaabb0}.hero-badge{width:112rpx;height:112rpx;border-radius:9999rpx;background:linear-gradient(135deg,#ff4081,#00e5ff);display:flex;align-items:center;justify-content:center;box-shadow:0 0 20rpx rgba(255,64,129,.2);overflow:hidden}.hero-badge-img{width:64rpx;height:64rpx}.state-card{margin-bottom:28rpx}.state-head{display:flex;justify-content:space-between;align-items:center;margin:0 6rpx 18rpx}.state-title{font-size:36rpx;font-weight:800}.state-subtitle{font-size:22rpx;color:#aaabb0}.section-card{margin-bottom:20rpx;padding:28rpx;border-radius:30rpx;background:rgba(29,32,37,.45);border:1rpx solid rgba(255,255,255,.07);backdrop-filter:blur(24rpx)}.section-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:14rpx}.section-title-wrap{display:flex;align-items:center;gap:16rpx}.section-icon{width:52rpx;height:52rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);padding:8rpx;box-sizing:border-box}.section-title{display:block;font-size:32rpx;font-weight:800;line-height:1.2}.section-subtitle{display:block;margin-top:4rpx;font-size:22rpx;color:#aaabb0}.section-count{font-size:22rpx;color:#c19cff;background:rgba(193,156,255,.12);padding:8rpx 14rpx;border-radius:9999rpx}.section-progress{display:block;margin:0 0 18rpx 68rpx;font-size:22rpx;color:#8fe3ff}.grid{display:grid;grid-template-columns:1fr 1fr;gap:18rpx}.item{position:relative;overflow:hidden;padding:28rpx;border-radius:28rpx;background:rgba(29,32,37,.56);backdrop-filter:blur(24rpx);border:1rpx solid rgba(255,255,255,.06);box-shadow:0 20rpx 40rpx rgba(0,0,0,.06);min-height:220rpx}.accent-tertiary{box-shadow:0 20rpx 40rpx rgba(255,64,129,.06)}.accent-secondary{box-shadow:0 20rpx 40rpx rgba(0,229,255,.06)}.accent-primary{box-shadow:0 20rpx 40rpx rgba(127,0,255,.06)}.item-icon{width:76rpx;height:76rpx;border-radius:9999rpx;background:rgba(255,255,255,.06);display:flex;align-items:center;justify-content:center;margin-bottom:24rpx;overflow:hidden}.item-icon-img{width:44rpx;height:44rpx}.item-title{display:block;font-size:30rpx;font-weight:700;margin-bottom:8rpx}.item-sub{display:block;font-size:22rpx;color:#aaabb0} page {
background: #0c0e12;
color: #f6f6fc
}
.page {
min-height: 100vh;
background: #0c0e12;
box-sizing: border-box
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 32rpx;
background: rgba(12, 14, 18, .8);
backdrop-filter: blur(24px);
border-bottom: 1rpx solid rgba(255, 255, 255, .08);
box-shadow: 0 1rpx 20rpx rgba(0, 0, 0, .08)
}
.brand {
display: flex;
align-items: center;
gap: 18rpx
}
.avatar-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 9999rpx;
overflow: hidden;
border: 1rpx solid rgba(255, 255, 255, .1);
flex-shrink: 0
}
.avatar {
width: 100%;
height: 100%
}
.brand-text {
font-size: 38rpx;
font-weight: 800;
color: #c19cff
}
.icon-btn {
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: 48rpx 40rpx 32rpx;
box-sizing: border-box
}
.heading {
margin-bottom: 28rpx
}
.title {
display: block;
font-size: 52rpx;
font-weight: 800;
line-height: 1.1
}
.subtitle {
display: block;
margin-top: 10rpx;
color: #aaabb0;
font-size: 26rpx
}
.hero-card {
position: relative;
overflow: hidden;
padding: 32rpx;
border-radius: 32rpx;
background: rgba(29, 32, 37, .6);
backdrop-filter: blur(24rpx);
border: 1rpx solid rgba(255, 255, 255, .08);
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, .08);
margin-bottom: 24rpx
}
.hero-glow {
position: absolute;
right: -40rpx;
top: -40rpx;
width: 160rpx;
height: 160rpx;
border-radius: 9999rpx;
background: rgba(255, 64, 129, .12);
filter: blur(30rpx)
}
.hero-row {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 1
}
.hero-num {
display: block;
font-size: 72rpx;
font-weight: 800;
color: #fff
}
.hero-label {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #aaabb0
}
.hero-badge {
width: 112rpx;
height: 112rpx;
border-radius: 9999rpx;
background: linear-gradient(135deg, #ff4081, #00e5ff);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20rpx rgba(255, 64, 129, .2);
overflow: hidden
}
.hero-badge-img {
width: 64rpx;
height: 64rpx
}
.state-card {
margin-bottom: 28rpx
}
.state-head {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 6rpx 18rpx
}
.state-title {
font-size: 36rpx;
font-weight: 800
}
.state-subtitle {
font-size: 22rpx;
color: #aaabb0
}
.section-card {
margin-bottom: 20rpx;
padding: 28rpx;
border-radius: 30rpx;
background: rgba(29, 32, 37, .45);
border: 1rpx solid rgba(255, 255, 255, .07);
backdrop-filter: blur(24rpx)
}
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14rpx
}
.section-title-wrap {
display: flex;
align-items: center;
gap: 16rpx
}
.section-icon {
width: 52rpx;
height: 52rpx;
border-radius: 9999rpx;
background: rgba(255, 255, 255, .06);
padding: 8rpx;
box-sizing: border-box
}
.section-title {
display: block;
font-size: 32rpx;
font-weight: 800;
line-height: 1.2
}
.section-subtitle {
display: block;
margin-top: 4rpx;
font-size: 22rpx;
color: #aaabb0
}
.section-count {
font-size: 22rpx;
color: #c19cff;
background: rgba(193, 156, 255, .12);
padding: 8rpx 14rpx;
border-radius: 9999rpx
}
.section-progress {
display: block;
margin: 0 0 18rpx 68rpx;
font-size: 22rpx;
color: #8fe3ff
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx
}
.item {
position: relative;
overflow: hidden;
padding: 28rpx;
border-radius: 28rpx;
background: rgba(29, 32, 37, .56);
backdrop-filter: blur(24rpx);
border: 1rpx solid rgba(255, 255, 255, .06);
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, .06);
min-height: 220rpx
}
.accent-tertiary {
box-shadow: 0 20rpx 40rpx rgba(255, 64, 129, .06)
}
.accent-secondary {
box-shadow: 0 20rpx 40rpx rgba(0, 229, 255, .06)
}
.accent-primary {
box-shadow: 0 20rpx 40rpx rgba(127, 0, 255, .06)
}
.item-icon {
width: 130rpx;
height: 130rpx;
border-radius: 9999rpx;
background: rgba(255, 255, 255, .06);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24rpx;
overflow: hidden
}
.item-icon-img {
width: 110rpx;
height: 110rpx;
flex-shrink: 0
}
.item-title {
display: block;
font-size: 30rpx;
font-weight: 700;
margin-bottom: 8rpx;
text-align: center
}
.item-sub {
display: block;
font-size: 22rpx;
color: #aaabb0;
text-align: center
}

View File

@ -1,7 +1,7 @@
const app = getApp() const app = getApp()
const groupByType = (list) => ({ const groupByType = (list) => ({
word: list.filter((item) => item.entry_type !== 'sentence'), word: list.filter((item) => item.entry_type === 'word'),
sentence: list.filter((item) => item.entry_type === 'sentence'), sentence: list.filter((item) => item.entry_type === 'sentence'),
}) })

View File

@ -37,6 +37,7 @@ Page({
currentClozeResults: [], currentClozeResults: [],
showClozeFeedback: false, showClozeFeedback: false,
clozeSegments: [], clozeSegments: [],
clozeFeedbackSummary: '',
activeClozeBlankIndex: 0, activeClozeBlankIndex: 0,
}, },
submitting: false, submitting: false,
@ -74,12 +75,13 @@ Page({
const pushWordBlank = (blankIndex, blank) => { const pushWordBlank = (blankIndex, blank) => {
const answerText = String(blank?.answer || '') const answerText = String(blank?.answer || '')
const answerLength = Math.max(1, answerText.length || Number(blank?.hint?.length || 1)) const answerLength = Math.max(1, answerText.length || Number(blank?.hint?.length || 1))
const blankWidth = Math.max(88, Math.min(320, 32 + answerLength * 28))
segments.push({ segments.push({
type: 'word-blank', type: 'word-blank',
blankIndex, blankIndex,
index: `blank-${blankIndex}`, index: `blank-${blankIndex}`,
width: `${Math.max(56, Math.min(260, 26 + answerLength * 24))}rpx`, width: `${blankWidth}rpx`,
minWidth: `${Math.max(56, Math.min(260, 26 + answerLength * 24))}rpx`, minWidth: `${blankWidth}rpx`,
maxLength: Math.max(1, answerLength), maxLength: Math.max(1, answerLength),
}) })
} }
@ -97,18 +99,8 @@ Page({
} }
if (blanks.length === 1 && workingPrompt === (clozeQuestion.original_text || prompt)) { if (blanks.length === 1 && workingPrompt === (clozeQuestion.original_text || prompt)) {
const blank = blanks[0] // Keep the original word context visible for single-word cloze questions.
const answerText = String(blank?.answer || '') // The general start/end slicing below will render visible letters around the blank.
const letterSegments = answerText.split('').map((char, idx) => ({
type: 'letter-cell',
blankIndex: 0,
letterIndex: idx,
index: `blank-0-letter-${idx}`,
width: '42rpx',
minWidth: '42rpx',
maxLength: 1,
}))
return letterSegments.length ? letterSegments : [{ type: 'word-blank', blankIndex: 0, index: 'blank-0', width: '80rpx', minWidth: '80rpx', maxLength: 1 }]
} }
let cursor = 0 let cursor = 0
@ -190,7 +182,7 @@ Page({
word: isCloze ? (current?.blank_prompt || clozeQuestion?.prompt_text || current?.prompt || '') : (current?.prompt || ''), word: isCloze ? (current?.blank_prompt || clozeQuestion?.prompt_text || current?.prompt || '') : (current?.prompt || ''),
meaning: isCloze ? (current?.answer_hint || '') : '', meaning: isCloze ? (current?.answer_hint || '') : '',
answer: session.currentAnswer || '', answer: session.currentAnswer || '',
clozeAnswers: isCloze ? (Array.isArray(session.currentAnswers) ? session.currentAnswers : Array.from({ length: currentClozeQuestion?.blanks?.length || 0 }, () => '')) : [], clozeAnswers: isCloze ? (Array.isArray(session.currentAnswers) ? session.currentAnswers : Array.from({ length: clozeQuestion?.blanks?.length || 0 }, () => '')) : [],
activeClozeBlankIndex: 0, activeClozeBlankIndex: 0,
feedback: '', feedback: '',
revealAnswer: '', revealAnswer: '',
@ -205,6 +197,7 @@ Page({
currentClozeResults: session.lastClozeResults || [], currentClozeResults: session.lastClozeResults || [],
showClozeFeedback: false, showClozeFeedback: false,
clozeSegments: isCloze ? this.buildClozeSegments(clozeQuestion) : [], clozeSegments: isCloze ? this.buildClozeSegments(clozeQuestion) : [],
clozeFeedbackSummary: '',
}) })
}, },
onAnswerInput(e) { onAnswerInput(e) {
@ -328,11 +321,15 @@ Page({
if (current.question_mode === 'C' && clozeQuestion?.blanks?.length) { if (current.question_mode === 'C' && clozeQuestion?.blanks?.length) {
const answers = (this.data.clozeAnswers || []).map((item) => (item || '').trim()) const answers = (this.data.clozeAnswers || []).map((item) => (item || '').trim())
while (answers.length < clozeQuestion.blanks.length) answers.push('') while (answers.length < clozeQuestion.blanks.length) answers.push('')
if (!answers.some((item) => item)) { const isLetterCellMode = Array.isArray(this.data.clozeSegments) && this.data.clozeSegments.some((segment) => segment.type === 'letter-cell')
const normalizedAnswers = isLetterCellMode
? answers
: answers
if (!normalizedAnswers.some((item) => item)) {
wx.showToast({ title: '请先输入答案', icon: 'none' }) wx.showToast({ title: '请先输入答案', icon: 'none' })
return return
} }
payload.user_answers = answers payload.user_answers = normalizedAnswers
} else if (!answer) { } else if (!answer) {
wx.showToast({ title: '请先输入答案', icon: 'none' }) wx.showToast({ title: '请先输入答案', icon: 'none' })
return return
@ -352,24 +349,55 @@ Page({
const shouldShowAnswer = showExplanation || this.data.settings.showAnswerAfterSubmit const shouldShowAnswer = showExplanation || this.data.settings.showAnswerAfterSubmit
const correctAnswerText = result.correct_answer_text || '' const correctAnswerText = result.correct_answer_text || ''
const clozeResults = Array.isArray(result.cloze_results) ? result.cloze_results : [] const clozeResults = Array.isArray(result.cloze_results) ? result.cloze_results : []
if (clozeResults.length) { const fallbackClozeResults = clozeResults.length
session.lastClozeResults = clozeResults ? clozeResults
app.globalData.reviewSession.lastClozeResults = clozeResults : (this.data.currentClozeQuestion?.blanks || []).map((blank, idx) => ({
index: typeof blank?.index === 'number' ? blank.index : idx,
user_answer: '',
correct_answers: [blank?.answer, ...(Array.isArray(blank?.accepted_answers) ? blank.accepted_answers : [])].filter(Boolean),
correct_answer_text: blank?.answer || '',
is_correct: false,
}))
const normalizedClozeResults = fallbackClozeResults.map((item, idx) => {
const correctAnswers = Array.isArray(item.correct_answers)
? item.correct_answers.filter(Boolean)
: [item.correct_answer_text, ...(Array.isArray(item.accepted_answers) ? item.accepted_answers : [])].filter(Boolean)
const standardAnswerText = correctAnswers.join(' / ') || item.correct_answer_text || ''
return {
index: item.index ?? idx,
user_answer: item.user_answer ?? '',
correct_answers: correctAnswers,
correct_answer_text: item.correct_answer_text || standardAnswerText,
standard_answer_text: standardAnswerText,
is_correct: Boolean(item.is_correct),
}
})
const derivedClozeResults = normalizedClozeResults.length
? normalizedClozeResults
: (correctAnswerText
? [{ index: 0, user_answer: '', correct_answers: [correctAnswerText], correct_answer_text: correctAnswerText, standard_answer_text: correctAnswerText, is_correct: false }]
: [])
if (derivedClozeResults.length) {
session.lastClozeResults = derivedClozeResults
app.globalData.reviewSession.lastClozeResults = derivedClozeResults
} }
const clozeFeedbackSummary = result.is_correct ? '回答正确' : '回答错误'
this.setData({ this.setData({
feedback: result.is_correct ? '回答正确' : '回答错误', feedback: clozeFeedbackSummary,
feedbackType: result.is_correct ? 'correct' : 'wrong', feedbackType: result.is_correct ? 'correct' : 'wrong',
revealAnswer: shouldShowAnswer && correctAnswerText ? `正确答案:${correctAnswerText}` : '', revealAnswer: '',
showFeedback: true, showFeedback: true,
showClozeFeedback: clozeResults.length > 0, showClozeFeedback: derivedClozeResults.length > 0,
currentClozeResults: clozeResults, currentClozeResults: derivedClozeResults,
clozeFeedbackSummary,
}) })
if (this.data.isCloze) { if (this.data.isCloze) {
app.globalData.reviewSession.currentAnswers = [] app.globalData.reviewSession.currentAnswers = []
this.setData({ clozeAnswers: [] }) this.setData({ clozeAnswers: [] })
} }
if (this.data.settings.autoNextQuestion || advanceAfterSubmit) { if (this.data.settings.autoNextQuestion || advanceAfterSubmit) {
setTimeout(() => this.nextQuestion(), shouldShowAnswer ? 1200 : 0) const delay = shouldShowAnswer ? 2500 : 0
setTimeout(() => this.nextQuestion(), delay)
} }
}, },
complete: () => { complete: () => {

View File

@ -47,14 +47,13 @@
<input class="answer" value="{{answer}}" placeholder="输入答案..." bindinput="onAnswerInput" /> <input class="answer" value="{{answer}}" placeholder="输入答案..." bindinput="onAnswerInput" />
</view> </view>
<view wx:if="{{showFeedback}}" class="feedback-box {{feedbackType}}"> <view wx:if="{{showFeedback}}" class="feedback-box {{feedbackType}}">
<text class="feedback">{{feedback}}</text> <text class="feedback">{{clozeFeedbackSummary || feedback}}</text>
<text wx:if="{{revealAnswer}}" class="reveal-answer">{{revealAnswer}}</text>
<view wx:if="{{showClozeFeedback && currentClozeResults.length}}" class="cloze-feedback-list"> <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"> <block wx:for="{{currentClozeResults}}" wx:for-item="blankResult" wx:for-index="blankIndex" wx:key="index">
<view class="cloze-feedback-item"> <view class="cloze-feedback-item">
<text class="cloze-feedback-title">空 {{blankIndex + 1}} · {{blankResult.is_correct ? '正确' : '错误'}}</text> <text class="cloze-feedback-title">空 {{blankIndex + 1}}</text>
<text class="cloze-feedback-text">你的答案:{{blankResult.user_answer || '(空)'}}</text> <text class="cloze-feedback-text">你的答案:{{blankResult.user_answer || '(空)'}}</text>
<text class="cloze-feedback-text">标准答案:{{blankResult.correct_answers.join(' / ')}}</text> <text class="cloze-feedback-text">标准答案:{{blankResult.standard_answer_text || (blankResult.correct_answers && blankResult.correct_answers.length ? blankResult.correct_answers.join(' / ') : (blankResult.correct_answer_text || '(未返回)'))}}</text>
</view> </view>
</block> </block>
</view> </view>

File diff suppressed because one or more lines are too long