修复小程序端的BUG
This commit is contained in:
parent
6ec4b114da
commit
c64a701b10
@ -1,4 +1,4 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@ -10,18 +10,31 @@ Page({
|
||||
this.loadStats()
|
||||
},
|
||||
async loadStats() {
|
||||
const app = appContext.withApp()
|
||||
this.setData({ loading: true, error: '' })
|
||||
await app.ensureLogin()
|
||||
await appContext.ensureLogin()
|
||||
|
||||
if (!app.globalData.authToken) {
|
||||
this.setData({ error: '登录失败,请稍后重试', loading: false })
|
||||
this.setData({ error: '请先登录后查看首页数据', loading: false })
|
||||
return
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/home/stats`,
|
||||
header: { 'X-Auth-Token': app.globalData.authToken },
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const payload = res.data?.data
|
||||
if (payload) this.setData({ stats: payload })
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0 && payload
|
||||
if (!ok) {
|
||||
this.setData({ error: res.data?.detail || res.data?.message || '首页数据加载失败' })
|
||||
return
|
||||
}
|
||||
this.setData({ stats: payload })
|
||||
},
|
||||
fail: () => this.setData({ error: '首页数据加载失败' }),
|
||||
complete: () => this.setData({ loading: false }),
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
<view class="page">
|
||||
<scroll-view scroll-y class="content">
|
||||
<view class="greeting">
|
||||
<text class="title">你好,同学!</text>
|
||||
<text class="subtitle">专注高频复习,持续积累词库</text>
|
||||
<text class="title">你好,同学</text>
|
||||
<text class="subtitle">专注高频复习,持续积累你的词库与掌握度</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!stats}}" class="empty-state">
|
||||
<view wx:if="{{loading}}" class="empty-state">
|
||||
<text class="empty-title">加载中...</text>
|
||||
<text class="empty-subtitle">正在同步你的学习数据</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{error}}" class="empty-state">
|
||||
<text class="empty-title">暂时无法加载首页</text>
|
||||
<text class="empty-subtitle">{{error}}</text>
|
||||
</view>
|
||||
|
||||
<view wx:elif="{{stats.review_count === 0 && stats.total_entries === 0}}" class="empty-state">
|
||||
<text class="empty-title">还没有词库</text>
|
||||
<text class="empty-title">还没有词库内容</text>
|
||||
<text class="empty-subtitle">先去“我的”里上传一些单词或句子吧</text>
|
||||
</view>
|
||||
|
||||
@ -23,7 +28,7 @@
|
||||
<text class="hero-number">{{stats.review_count}}</text>
|
||||
<text class="label">今日待复习</text>
|
||||
</view>
|
||||
<view class="hero-icon">🧠</view>
|
||||
<view class="hero-icon">📘</view>
|
||||
</view>
|
||||
<view class="hero-grid">
|
||||
<view class="metric-card"><text class="metric-value">{{stats.total_entries}}</text><text class="label small">词库总数</text></view>
|
||||
@ -51,7 +56,7 @@
|
||||
|
||||
<view class="bottom-nav">
|
||||
<view class="nav-item active" bindtap="goHome"><text class="nav-icon">⌂</text><text class="nav-label">首页</text></view>
|
||||
<view class="nav-item" bindtap="goToReview"><text class="nav-icon">📖</text><text class="nav-label">复习</text></view>
|
||||
<view class="nav-item" bindtap="goToReview"><text class="nav-icon">↻</text><text class="nav-label">复习</text></view>
|
||||
<view class="nav-item" bindtap="goToMe"><text class="nav-icon">☺</text><text class="nav-label">我的</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
141
me/me.js
141
me/me.js
@ -1,4 +1,4 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
function normalizeAvatarUrl(avatarUrl, baseUrl) {
|
||||
if (!avatarUrl) return ''
|
||||
@ -6,13 +6,27 @@ function normalizeAvatarUrl(avatarUrl, baseUrl) {
|
||||
}
|
||||
|
||||
function requestFailHint(errMsg) {
|
||||
const m = String(errMsg || '').toLowerCase()
|
||||
if (m.includes('domain')) return '域名未配置或未开启 HTTPS'
|
||||
if (m.includes('ssl') || m.includes('certificate')) return 'HTTPS 证书异常,请检查服务端配置'
|
||||
if (m.includes('timeout') || m.includes('timed out')) return '连接超时,请稍后重试'
|
||||
const message = String(errMsg || '').toLowerCase()
|
||||
if (message.includes('domain')) return '域名未配置或未开启 HTTPS'
|
||||
if (message.includes('ssl') || message.includes('certificate')) return 'HTTPS 证书异常,请检查服务端配置'
|
||||
if (message.includes('timeout') || message.includes('timed out')) return '连接超时,请稍后重试'
|
||||
return '网络异常,请稍后重试'
|
||||
}
|
||||
|
||||
function buildLoggedOutState() {
|
||||
return {
|
||||
loading: false,
|
||||
name: '未登录',
|
||||
nickname: '未登录',
|
||||
username: '',
|
||||
avatarUrl: '',
|
||||
nicknameDraft: '',
|
||||
badges: 0,
|
||||
days: 0,
|
||||
error: '',
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_AVATAR = 'data:image/svg+xml;utf8,' + encodeURIComponent(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="320" viewBox="0 0 320 320">
|
||||
<defs>
|
||||
@ -52,6 +66,7 @@ Page({
|
||||
this.loadProfile()
|
||||
},
|
||||
isLoggedIn() {
|
||||
const app = appContext.withApp()
|
||||
return !!app.globalData.authToken
|
||||
},
|
||||
onAuthModeChange(e) {
|
||||
@ -63,7 +78,8 @@ Page({
|
||||
this.setData({ [`authForm.${field}`]: value })
|
||||
},
|
||||
submitAuth() {
|
||||
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
|
||||
const app = appContext.withApp()
|
||||
const baseUrl = appContext.getApiBaseUrl()
|
||||
const mode = this.data.authMode
|
||||
const username = (this.data.authForm.username || '').trim()
|
||||
const password = (this.data.authForm.password || '').trim()
|
||||
@ -87,6 +103,10 @@ Page({
|
||||
data: mode === 'register' ? { username, password, nickname } : { username, password },
|
||||
success: (res) => {
|
||||
const payload = res.data?.data || null
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
if (res.statusCode < 200 || res.statusCode >= 300 || !payload?.auth_token) {
|
||||
const message = res.data?.detail || res.data?.message || `${mode === 'register' ? '注册' : '登录'}失败`
|
||||
this.setData({ error: message })
|
||||
@ -94,10 +114,11 @@ Page({
|
||||
return
|
||||
}
|
||||
|
||||
app.setLoginSession(payload)
|
||||
appContext.setLoginSession(payload)
|
||||
this.setData({
|
||||
authForm: { username: '', password: '', nickname: '' },
|
||||
username: payload.username || '',
|
||||
error: '',
|
||||
})
|
||||
wx.showToast({ title: mode === 'register' ? '注册成功' : '登录成功', icon: 'success' })
|
||||
this.loadProfile()
|
||||
@ -111,30 +132,26 @@ Page({
|
||||
})
|
||||
},
|
||||
loadProfile() {
|
||||
const token = app.ensureLogin()
|
||||
const app = appContext.withApp()
|
||||
const token = appContext.ensureLogin()
|
||||
Promise.resolve(token).then((authToken) => {
|
||||
if (!authToken) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
name: '未登录',
|
||||
nickname: '未登录',
|
||||
username: '',
|
||||
avatarUrl: '',
|
||||
nicknameDraft: '',
|
||||
badges: 0,
|
||||
days: 0,
|
||||
error: '',
|
||||
})
|
||||
this.setData(buildLoggedOutState())
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ loading: true, error: '' })
|
||||
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
|
||||
const baseUrl = appContext.getApiBaseUrl()
|
||||
wx.request({
|
||||
url: `${baseUrl}/api/v1/auth/profile`,
|
||||
method: 'GET',
|
||||
header: app.getAuthHeader(),
|
||||
header: appContext.getAuthHeader(),
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const body = res.data || {}
|
||||
const profile = body.data
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && profile
|
||||
@ -189,19 +206,34 @@ Page({
|
||||
this.setData({ nicknameDraft: e.detail.value })
|
||||
},
|
||||
saveNickname() {
|
||||
const app = appContext.withApp()
|
||||
if (!this.isLoggedIn()) return
|
||||
|
||||
const nickname = (this.data.nicknameDraft || '').trim()
|
||||
if (!nickname) {
|
||||
wx.showToast({ title: '请输入昵称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: `${app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl}/api/v1/auth/profile`,
|
||||
url: `${appContext.getApiBaseUrl()}/api/v1/auth/profile`,
|
||||
method: 'PUT',
|
||||
header: app.getAuthHeader({ 'Content-Type': 'application/json' }),
|
||||
header: appContext.getAuthHeader({ 'Content-Type': 'application/json' }),
|
||||
data: { nickname },
|
||||
success: (res) => {
|
||||
const profile = res.data?.data || {}
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const body = res.data || {}
|
||||
const profile = body.data || {}
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0
|
||||
if (!ok) {
|
||||
wx.showToast({ title: body.detail || body.message || '保存失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const nextNickname = profile.nickname || nickname
|
||||
const currentUser = { ...(app.globalData.currentUser || {}), nickname: nextNickname }
|
||||
app.globalData.currentUser = currentUser
|
||||
@ -209,10 +241,11 @@ Page({
|
||||
this.setData({ nickname: nextNickname, name: nextNickname, nicknameDraft: nextNickname, showNicknameModal: false })
|
||||
wx.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
fail: () => wx.showToast({ title: '保存失败', icon: 'none' }),
|
||||
fail: (err) => wx.showToast({ title: requestFailHint(err.errMsg), icon: 'none' }),
|
||||
})
|
||||
},
|
||||
onChooseAvatar(e) {
|
||||
const app = appContext.withApp()
|
||||
const tempPath = e.detail?.avatarUrl || ''
|
||||
if (!this.isLoggedIn()) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
@ -222,13 +255,19 @@ Page({
|
||||
wx.showToast({ title: '未选择头像', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
|
||||
|
||||
const baseUrl = appContext.getApiBaseUrl()
|
||||
wx.uploadFile({
|
||||
url: `${baseUrl}/api/v1/auth/avatar`,
|
||||
filePath: tempPath,
|
||||
name: 'file',
|
||||
header: app.getAuthHeader(),
|
||||
header: appContext.getAuthHeader(),
|
||||
success: (uploadRes) => {
|
||||
if (Number(uploadRes.statusCode) === 401) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(uploadRes.data || '{}')
|
||||
const ok = uploadRes.statusCode >= 200 && uploadRes.statusCode < 300 && payload.code === 0 && payload.data
|
||||
@ -236,6 +275,7 @@ Page({
|
||||
wx.showToast({ title: payload.message || '头像上传失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const rel = payload.data.avatar_url || ''
|
||||
const full = normalizeAvatarUrl(rel, baseUrl)
|
||||
const key = this.data.username || app.globalData.currentUser?.username || ''
|
||||
@ -251,39 +291,50 @@ Page({
|
||||
},
|
||||
loadAchievements() {
|
||||
if (!this.isLoggedIn()) return
|
||||
const baseUrl = app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl
|
||||
|
||||
wx.request({
|
||||
url: `${baseUrl}/api/v1/achievements`,
|
||||
header: app.getAuthHeader(),
|
||||
url: `${appContext.getApiBaseUrl()}/api/v1/achievements`,
|
||||
header: appContext.getAuthHeader(),
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const body = res.data || {}
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && body.code === 0 && Array.isArray(body.data)
|
||||
if (!ok) return
|
||||
const badges = body.data.filter((i) => i.is_unlocked).length
|
||||
const badges = body.data.filter((item) => item.is_unlocked).length
|
||||
this.setData({ badges })
|
||||
},
|
||||
})
|
||||
},
|
||||
logout() {
|
||||
if (!this.isLoggedIn()) return
|
||||
|
||||
wx.request({
|
||||
url: `${app.getApiBaseUrl ? app.getApiBaseUrl() : app.globalData.baseUrl}/api/v1/auth/logout`,
|
||||
url: `${appContext.getApiBaseUrl()}/api/v1/auth/logout`,
|
||||
method: 'POST',
|
||||
header: app.getAuthHeader(),
|
||||
complete: () => {
|
||||
app.clearLoginSession()
|
||||
this.setData({
|
||||
name: '未登录',
|
||||
nickname: '未登录',
|
||||
username: '',
|
||||
avatarUrl: '',
|
||||
nicknameDraft: '',
|
||||
badges: 0,
|
||||
days: 0,
|
||||
error: '',
|
||||
})
|
||||
header: appContext.getAuthHeader(),
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized({ title: '登录状态已失效,请重新登录' })
|
||||
return
|
||||
}
|
||||
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0
|
||||
if (!ok) {
|
||||
wx.showToast({ title: res.data?.detail || res.data?.message || '退出登录失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
appContext.clearLoginSession()
|
||||
this.setData(buildLoggedOutState())
|
||||
wx.showToast({ title: '已退出登录', icon: 'success' })
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.showToast({ title: requestFailHint(err.errMsg), icon: 'none' })
|
||||
},
|
||||
})
|
||||
},
|
||||
goTo(e) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@ -16,6 +16,7 @@ Page({
|
||||
error: '',
|
||||
},
|
||||
onLoad() {
|
||||
const app = appContext.withApp()
|
||||
const plan = app.globalData.reviewPlan
|
||||
this.setData({
|
||||
activeTimeRange: plan.timeRangeIndex,
|
||||
@ -28,6 +29,7 @@ Page({
|
||||
})
|
||||
},
|
||||
persistPatch(patch) {
|
||||
const app = appContext.withApp()
|
||||
app.globalData.reviewPlan = { ...app.globalData.reviewPlan, ...patch }
|
||||
},
|
||||
setTimeRange(e) {
|
||||
@ -61,6 +63,7 @@ Page({
|
||||
this.persistPatch({ questionCount })
|
||||
},
|
||||
startReview() {
|
||||
const app = appContext.withApp()
|
||||
const scopeTypeMap = ['today', 'week', 'month']
|
||||
const contentTypeMap = ['word', 'sentence']
|
||||
const questionModeMap = ['A', 'B', 'C']
|
||||
@ -68,12 +71,14 @@ Page({
|
||||
const content_type = contentTypeMap[this.data.activeContentType]
|
||||
const question_mode = questionModeMap[this.data.activeMode]
|
||||
const scope_config = {}
|
||||
|
||||
this.setData({ loading: true, error: '' })
|
||||
app.ensureLogin().then((authToken) => {
|
||||
appContext.ensureLogin().then((authToken) => {
|
||||
if (!authToken) {
|
||||
this.setData({ loading: false, error: '请先登录后再开始复习' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/review/generate`,
|
||||
method: 'POST',
|
||||
@ -88,6 +93,11 @@ Page({
|
||||
total_count: this.data.questionCount,
|
||||
},
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const session = res.data?.data || null
|
||||
if (res.statusCode < 200 || res.statusCode >= 300 || !session?.id) {
|
||||
const message = res.data?.detail || res.data?.message || '生成复习会话失败'
|
||||
@ -95,12 +105,14 @@ Page({
|
||||
wx.showToast({ title: message, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!session.record_ids?.length) {
|
||||
const message = '当前条件下没有可复习题目,请切换时间范围或关闭仅复习'
|
||||
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' })
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<scroll-view scroll-y class="content config-content">
|
||||
<view class="heading">
|
||||
<text class="title">配置复习计划</text>
|
||||
<text class="subtitle">定制你的复习范围、题型和题量。</text>
|
||||
<text class="subtitle">定制你的复习范围、题型和题量</text>
|
||||
</view>
|
||||
|
||||
<view class="config-card">
|
||||
@ -20,7 +20,7 @@
|
||||
</view>
|
||||
|
||||
<view class="config-card">
|
||||
<text class="section-title">训练模式</text>
|
||||
<text class="section-title">练习模式</text>
|
||||
<view class="mode-list">
|
||||
<view wx:for="{{modes}}" wx:key="index" data-index="{{index}}" bindtap="setMode" class="mode-item {{index===activeMode?'active':''}}">{{item}}</view>
|
||||
</view>
|
||||
@ -34,12 +34,16 @@
|
||||
<slider min="5" max="100" step="5" value="{{questionCount}}" activeColor="#c19cff" backgroundColor="#23262c" bindchanging="onQuestionCountInput" bindchange="onQuestionCountInput" />
|
||||
</view>
|
||||
|
||||
<button class="start-btn" bindtap="startReview">开始复习</button>
|
||||
<view wx:if="{{error}}" class="config-card">
|
||||
<text class="subtitle">{{error}}</text>
|
||||
</view>
|
||||
|
||||
<button class="start-btn" loading="{{loading}}" bindtap="startReview">开始复习</button>
|
||||
</scroll-view>
|
||||
|
||||
<view class="bottom-nav">
|
||||
<view class="nav-item" bindtap="goHome"><text class="nav-icon">⌂</text><text class="nav-label">首页</text></view>
|
||||
<view class="nav-item active"><text class="nav-icon">📖</text><text class="nav-label">复习</text></view>
|
||||
<view class="nav-item active"><text class="nav-icon">↻</text><text class="nav-label">复习</text></view>
|
||||
<view class="nav-item" bindtap="goToMe"><text class="nav-icon">☺</text><text class="nav-label">我的</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@ -17,8 +17,10 @@ Page({
|
||||
this.loadResult()
|
||||
},
|
||||
loadResult() {
|
||||
const sessionId = app.globalData.reviewSessionMeta?.id
|
||||
const app = appContext.withApp()
|
||||
const sessionId = app.globalData.reviewSessionMeta?.id || wx.getStorageSync('reviewSessionMeta')?.id
|
||||
const fallbackSession = app.globalData.reviewSession
|
||||
|
||||
if (!sessionId) {
|
||||
if (fallbackSession?.completed) {
|
||||
this.setData({ error: '本地复习已完成,但结果会话信息已丢失' })
|
||||
@ -27,11 +29,23 @@ Page({
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ loading: true, error: '' })
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/review-result?session_id=${sessionId}`,
|
||||
header: { 'X-Auth-Token': app.globalData.authToken || '' },
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0
|
||||
if (!ok) {
|
||||
this.setData({ error: res.data?.detail || res.data?.message || '结果加载失败' })
|
||||
return
|
||||
}
|
||||
|
||||
const data = res.data?.data || {}
|
||||
const total = Number(data.total || 0)
|
||||
const correct = Number(data.correct || 0)
|
||||
@ -52,9 +66,10 @@ Page({
|
||||
is_correct: !!blank.is_correct,
|
||||
}))
|
||||
: [],
|
||||
is_correct: !!item.blanks?.every?.((blank) => blank.is_correct),
|
||||
is_correct: Array.isArray(item.blanks) ? item.blanks.every((blank) => blank.is_correct) : false,
|
||||
}))
|
||||
: []
|
||||
|
||||
this.setData({
|
||||
total,
|
||||
accuracy,
|
||||
@ -71,7 +86,17 @@ Page({
|
||||
})
|
||||
},
|
||||
restart() {
|
||||
app.globalData.reviewSession = { deck: [], currentIndex: 0, correctCount: 0, wrongCount: 0, completed: false, currentAnswer: '' }
|
||||
const app = appContext.withApp()
|
||||
app.globalData.reviewSession = {
|
||||
deck: [],
|
||||
currentIndex: 0,
|
||||
correctCount: 0,
|
||||
wrongCount: 0,
|
||||
completed: false,
|
||||
currentAnswer: '',
|
||||
currentAnswers: [],
|
||||
lastClozeResults: [],
|
||||
}
|
||||
wx.reLaunch({ url: '/pages/review-config/review-config' })
|
||||
},
|
||||
goHome() {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
const getReviewSettings = () => {
|
||||
const app = appContext.withApp()
|
||||
const settings = app.globalData.userSettings || {}
|
||||
return {
|
||||
autoNextQuestion: Number(settings.auto_next_question || 0) === 1,
|
||||
@ -45,17 +46,30 @@ Page({
|
||||
onLoad() {
|
||||
this.loadSettingsAndQuestions()
|
||||
},
|
||||
getReviewSessionState() {
|
||||
const app = appContext.withApp()
|
||||
return app.globalData.reviewSession
|
||||
},
|
||||
loadSettingsAndQuestions() {
|
||||
const app = appContext.withApp()
|
||||
if (!app.globalData.authToken) {
|
||||
this.setData({ error: '请先登录' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/settings`,
|
||||
header: { 'X-Auth-Token': app.globalData.authToken },
|
||||
success: (res) => {
|
||||
app.globalData.userSettings = res.data?.data || {}
|
||||
this.setData({ settings: getReviewSettings() })
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0) {
|
||||
app.globalData.userSettings = res.data?.data || {}
|
||||
this.setData({ settings: getReviewSettings() })
|
||||
}
|
||||
this.loadQuestions()
|
||||
},
|
||||
fail: () => {
|
||||
@ -98,11 +112,6 @@ Page({
|
||||
return segments
|
||||
}
|
||||
|
||||
if (blanks.length === 1 && workingPrompt === (clozeQuestion.original_text || prompt)) {
|
||||
// Keep the original word context visible for single-word cloze questions.
|
||||
// The general start/end slicing below will render visible letters around the blank.
|
||||
}
|
||||
|
||||
let cursor = 0
|
||||
blanks.forEach((blank, idx) => {
|
||||
const start = Math.max(0, Number(blank.start || 0))
|
||||
@ -114,27 +123,35 @@ Page({
|
||||
cursor = end
|
||||
})
|
||||
if (cursor < workingPrompt.length) {
|
||||
segments.push({ type: 'text', text: workingPrompt.slice(cursor), index: `text-tail` })
|
||||
segments.push({ type: 'text', text: workingPrompt.slice(cursor), index: 'text-tail' })
|
||||
}
|
||||
return segments
|
||||
},
|
||||
loadQuestions() {
|
||||
const app = appContext.withApp()
|
||||
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-Auth-Token': app.globalData.authToken || '' },
|
||||
success: (res) => {
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
if (res.statusCode < 200 || res.statusCode >= 300 || res.data?.code !== 0) {
|
||||
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 {
|
||||
@ -148,10 +165,12 @@ Page({
|
||||
entry_type: item.entry_type || 'word',
|
||||
}
|
||||
})
|
||||
|
||||
if (!questions.length) {
|
||||
this.setData({ error: '当前复习会话没有题目,请返回重新选择范围', questions: [] })
|
||||
return
|
||||
}
|
||||
|
||||
app.globalData.reviewSession = {
|
||||
deck: questions,
|
||||
currentIndex: 0,
|
||||
@ -160,6 +179,7 @@ Page({
|
||||
completed: false,
|
||||
currentAnswer: '',
|
||||
currentAnswers: [],
|
||||
lastClozeResults: [],
|
||||
startedAt: Date.now(),
|
||||
}
|
||||
this.setData({ questions })
|
||||
@ -170,8 +190,10 @@ Page({
|
||||
})
|
||||
},
|
||||
syncSession() {
|
||||
const session = app.globalData.reviewSession
|
||||
const session = this.getReviewSessionState()
|
||||
const current = session.deck[session.currentIndex]
|
||||
if (!current) return
|
||||
|
||||
const total = session.deck.length || 1
|
||||
const isCloze = current?.question_mode === 'C'
|
||||
const clozeQuestion = isCloze ? (current?.cloze_question || null) : null
|
||||
@ -191,7 +213,7 @@ Page({
|
||||
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' ? '当前题目来自句子库,注意上下文表达。' : ''),
|
||||
entryTypeHint: isCloze ? '请逐空作答,提交后会显示每个空的结果。' : (current?.entry_type === 'sentence' ? '当前题目来自句子库,请注意上下文表达。' : ''),
|
||||
isCloze,
|
||||
currentClozeQuestion: clozeQuestion,
|
||||
currentClozeResults: session.lastClozeResults || [],
|
||||
@ -201,20 +223,22 @@ Page({
|
||||
})
|
||||
},
|
||||
onAnswerInput(e) {
|
||||
const session = this.getReviewSessionState()
|
||||
const answer = e.detail.value
|
||||
app.globalData.reviewSession.currentAnswer = answer
|
||||
session.currentAnswer = answer
|
||||
this.setData({ answer, showFeedback: false, revealAnswer: '', feedback: '', feedbackType: '', judged: false, showClozeFeedback: false })
|
||||
},
|
||||
onClozeLetterInput(e) {
|
||||
const session = this.getReviewSessionState()
|
||||
const { index, letterIndex } = e.currentTarget.dataset
|
||||
const rawValue = e.detail.value || ''
|
||||
const cleaned = rawValue.slice(-1)
|
||||
const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
|
||||
const answers = [...(session.currentAnswers || [])]
|
||||
const current = answers[index] || ''
|
||||
const chars = current.split('')
|
||||
chars[letterIndex] = cleaned
|
||||
answers[index] = chars.join('').slice(0, Math.max(chars.length, letterIndex + 1))
|
||||
app.globalData.reviewSession.currentAnswers = answers
|
||||
session.currentAnswers = answers
|
||||
this.setData({
|
||||
[`clozeAnswers[${index}]`]: answers[index],
|
||||
showFeedback: false,
|
||||
@ -239,17 +263,18 @@ Page({
|
||||
})
|
||||
},
|
||||
handleLetterKeydown(e) {
|
||||
const session = this.getReviewSessionState()
|
||||
const key = e?.detail?.key || e?.detail?.value || ''
|
||||
const { index, letterIndex } = e.currentTarget.dataset
|
||||
if (key === 'Backspace' || key === 'Delete') {
|
||||
const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
|
||||
const answers = [...(session.currentAnswers || [])]
|
||||
const current = answers[index] || ''
|
||||
const chars = current.split('')
|
||||
const pos = Number(letterIndex)
|
||||
if (chars[pos]) {
|
||||
chars[pos] = ''
|
||||
answers[index] = chars.join('')
|
||||
app.globalData.reviewSession.currentAnswers = answers
|
||||
session.currentAnswers = answers
|
||||
this.setData({ [`clozeAnswers[${index}]`]: answers[index] })
|
||||
return
|
||||
}
|
||||
@ -267,15 +292,16 @@ Page({
|
||||
}
|
||||
},
|
||||
onClozeAnswerInput(e) {
|
||||
const session = this.getReviewSessionState()
|
||||
const { index } = e.currentTarget.dataset
|
||||
const rawValue = e.detail.value || ''
|
||||
const clozeQuestion = this.data.currentClozeQuestion
|
||||
const blank = clozeQuestion?.blanks?.[index]
|
||||
const maxLength = Math.max(1, Number(blank?.answer?.length || 1))
|
||||
const normalizedValue = rawValue.slice(-maxLength).slice(0, maxLength)
|
||||
const answers = [...(app.globalData.reviewSession.currentAnswers || [])]
|
||||
const answers = [...(session.currentAnswers || [])]
|
||||
answers[index] = normalizedValue
|
||||
app.globalData.reviewSession.currentAnswers = answers
|
||||
session.currentAnswers = answers
|
||||
const nextIndex = Number(index) + (normalizedValue.length >= maxLength ? 1 : 0)
|
||||
const prevIndex = Number(index) - 1
|
||||
this.setData({
|
||||
@ -307,7 +333,8 @@ Page({
|
||||
})
|
||||
},
|
||||
submitAnswer(showExplanation = false, advanceAfterSubmit = false) {
|
||||
const session = app.globalData.reviewSession
|
||||
const app = appContext.withApp()
|
||||
const session = this.getReviewSessionState()
|
||||
const current = session.deck[session.currentIndex]
|
||||
if (!current || this.submitting || this.judged) return
|
||||
|
||||
@ -318,18 +345,15 @@ Page({
|
||||
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('')
|
||||
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)) {
|
||||
if (!answers.some((item) => item)) {
|
||||
wx.showToast({ title: '请先输入答案', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payload.user_answers = normalizedAnswers
|
||||
payload.user_answers = answers
|
||||
} else if (!answer) {
|
||||
wx.showToast({ title: '请先输入答案', icon: 'none' })
|
||||
return
|
||||
@ -342,10 +366,22 @@ Page({
|
||||
header: { 'Content-Type': 'application/json', 'X-Auth-Token': app.globalData.authToken || '' },
|
||||
data: payload,
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0
|
||||
if (!ok) {
|
||||
wx.showToast({ title: res.data?.detail || res.data?.message || '提交答案失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
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 : []
|
||||
@ -372,15 +408,17 @@ Page({
|
||||
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({
|
||||
feedback: clozeFeedbackSummary,
|
||||
@ -391,10 +429,12 @@ Page({
|
||||
currentClozeResults: derivedClozeResults,
|
||||
clozeFeedbackSummary,
|
||||
})
|
||||
|
||||
if (this.data.isCloze) {
|
||||
app.globalData.reviewSession.currentAnswers = []
|
||||
session.currentAnswers = []
|
||||
this.setData({ clozeAnswers: [] })
|
||||
}
|
||||
|
||||
if (this.data.settings.autoNextQuestion || advanceAfterSubmit) {
|
||||
const delay = shouldShowAnswer ? 2500 : 0
|
||||
setTimeout(() => this.nextQuestion(), delay)
|
||||
@ -409,12 +449,14 @@ Page({
|
||||
this.submitAnswer(true)
|
||||
},
|
||||
nextQuestion() {
|
||||
const session = app.globalData.reviewSession
|
||||
const app = appContext.withApp()
|
||||
const session = this.getReviewSessionState()
|
||||
if (!session.deck.length) return
|
||||
if (!this.judged) {
|
||||
this.submitAnswer(true, true)
|
||||
return
|
||||
}
|
||||
|
||||
this.judged = false
|
||||
if (session.currentIndex >= session.deck.length - 1) {
|
||||
session.completed = true
|
||||
@ -423,14 +465,21 @@ Page({
|
||||
url: `${app.globalData.baseUrl}/api/v1/review/finish?session_id=${app.globalData.reviewSessionMeta.id}`,
|
||||
method: 'POST',
|
||||
header: { 'X-Auth-Token': app.globalData.authToken || '' },
|
||||
success: () => wx.navigateTo({ url: '/pages/review-result/review-result' }),
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/review-result/review-result' })
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({ title: '提交结果失败,已本地完成', icon: 'none' })
|
||||
wx.showToast({ title: '提交结果失败,已按本地完成处理', icon: 'none' })
|
||||
wx.navigateTo({ url: '/pages/review-result/review-result' })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session.currentIndex += 1
|
||||
session.currentAnswer = ''
|
||||
session.currentAnswers = []
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const app = getApp()
|
||||
const appContext = require('../../utils/app-context.js')
|
||||
|
||||
const difficultyOptions = [
|
||||
{ label: '简单', value: 'easy' },
|
||||
@ -11,6 +11,16 @@ const reviewOrderOptions = [
|
||||
{ label: '随机复习', value: 'shuffle' },
|
||||
]
|
||||
|
||||
function normalizeSettings(settings) {
|
||||
return {
|
||||
blank_count: Number(settings?.blank_count || 2),
|
||||
blank_difficulty: settings?.blank_difficulty || 'normal',
|
||||
review_order: settings?.review_order || 'sequential',
|
||||
auto_next_question: Number(settings?.auto_next_question || 0),
|
||||
show_answer_after_submit: Number(settings?.show_answer_after_submit || 0),
|
||||
}
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
settings: null,
|
||||
@ -27,26 +37,42 @@ Page({
|
||||
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))
|
||||
const nextSettings = normalizeSettings(settings)
|
||||
const difficultyIndex = difficultyOptions.findIndex((item) => item.value === nextSettings.blank_difficulty)
|
||||
const reviewOrderIndex = reviewOrderOptions.findIndex((item) => item.value === nextSettings.review_order)
|
||||
const safeDifficultyIndex = difficultyIndex >= 0 ? difficultyIndex : 1
|
||||
const safeReviewOrderIndex = reviewOrderIndex >= 0 ? reviewOrderIndex : 0
|
||||
|
||||
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,
|
||||
settings: nextSettings,
|
||||
difficultyIndex: safeDifficultyIndex,
|
||||
difficultyLabel: difficultyOptions[safeDifficultyIndex].label,
|
||||
reviewOrderIndex: safeReviewOrderIndex,
|
||||
reviewOrderLabel: reviewOrderOptions[safeReviewOrderIndex].label,
|
||||
})
|
||||
},
|
||||
loadSettings() {
|
||||
const app = appContext.withApp()
|
||||
if (!app.globalData.authToken) {
|
||||
this.setData({ error: '请先登录' })
|
||||
return
|
||||
}
|
||||
|
||||
this.setData({ loading: true, error: '' })
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/settings`,
|
||||
header: { 'X-Auth-Token': app.globalData.authToken },
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0
|
||||
if (!ok) {
|
||||
this.setData({ error: res.data?.detail || res.data?.message || '设置加载失败' })
|
||||
return
|
||||
}
|
||||
this.syncDerivedState(res.data?.data || null)
|
||||
},
|
||||
fail: () => this.setData({ error: '设置加载失败' }),
|
||||
@ -57,7 +83,7 @@ Page({
|
||||
const value = Number(e.detail.value || 0)
|
||||
this.setData({
|
||||
settings: {
|
||||
...this.data.settings,
|
||||
...normalizeSettings(this.data.settings),
|
||||
blank_count: Number.isFinite(value) ? value : 0,
|
||||
},
|
||||
})
|
||||
@ -69,7 +95,7 @@ Page({
|
||||
difficultyIndex: index,
|
||||
difficultyLabel: option.label,
|
||||
settings: {
|
||||
...this.data.settings,
|
||||
...normalizeSettings(this.data.settings),
|
||||
blank_difficulty: option.value,
|
||||
},
|
||||
})
|
||||
@ -81,29 +107,44 @@ Page({
|
||||
reviewOrderIndex: index,
|
||||
reviewOrderLabel: option.label,
|
||||
settings: {
|
||||
...this.data.settings,
|
||||
...normalizeSettings(this.data.settings),
|
||||
review_order: option.value,
|
||||
},
|
||||
})
|
||||
},
|
||||
toggleSetting(e) {
|
||||
const key = e.currentTarget.dataset.key
|
||||
const settings = { ...this.data.settings }
|
||||
const settings = normalizeSettings(this.data.settings)
|
||||
settings[key] = settings[key] ? 0 : 1
|
||||
this.setData({ settings })
|
||||
},
|
||||
save() {
|
||||
const app = appContext.withApp()
|
||||
if (!app.globalData.authToken) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const settings = this.data.settings || {}
|
||||
|
||||
const settings = normalizeSettings(this.data.settings)
|
||||
wx.request({
|
||||
url: `${app.globalData.baseUrl}/api/v1/settings`,
|
||||
method: 'PUT',
|
||||
header: { 'Content-Type': 'application/json', 'X-Auth-Token': app.globalData.authToken },
|
||||
data: settings,
|
||||
success: () => wx.showToast({ title: '已保存', icon: 'success' }),
|
||||
success: (res) => {
|
||||
if (appContext.isUnauthorizedResponse(res)) {
|
||||
appContext.handleUnauthorized()
|
||||
return
|
||||
}
|
||||
|
||||
const ok = res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0
|
||||
if (!ok) {
|
||||
wx.showToast({ title: res.data?.detail || res.data?.message || '保存失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.syncDerivedState(res.data?.data || settings)
|
||||
wx.showToast({ title: '已保存', icon: 'success' })
|
||||
},
|
||||
fail: () => wx.showToast({ title: '保存失败', icon: 'none' }),
|
||||
})
|
||||
},
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<scroll-view scroll-y class="content">
|
||||
<view class="header">
|
||||
<text class="title">设置</text>
|
||||
<text class="subtitle">调整你的学习体验与交互偏好</text>
|
||||
<text class="subtitle">调整你的复习偏好与交互方式</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{loading}}" class="empty-state">
|
||||
@ -16,18 +16,18 @@
|
||||
|
||||
<view wx:if="{{settings}}" class="section-block">
|
||||
<view class="section-head">
|
||||
<view class="section-ico">⚙</view>
|
||||
<view class="section-ico">✦</view>
|
||||
<text class="section-title">复习参数</text>
|
||||
</view>
|
||||
<view class="panel">
|
||||
<view class="row">
|
||||
<view><text class="label">挖空数量</text><text class="desc">题型 C 中每题挖空的词数</text></view>
|
||||
<view><text class="label">挖空数量</text><text class="desc">题型 C 中每题需要挖空的单词数</text></view>
|
||||
<view class="value-group">
|
||||
<input class="setting-input" type="number" value="{{settings.blank_count}}" bindinput="onBlankCountInput" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view><text class="label">挖空难度</text><text class="desc">影响挖空比例与保留字母</text></view>
|
||||
<view><text class="label">挖空难度</text><text class="desc">影响挖空比例与保留字符</text></view>
|
||||
<view class="value-group">
|
||||
<picker mode="selector" range="{{difficultyOptions}}" range-key="label" value="{{difficultyIndex}}" bindchange="onDifficultyChange">
|
||||
<view class="picker-value accent">{{difficultyLabel}}</view>
|
||||
@ -35,7 +35,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<view><text class="label">复习顺序</text><text class="desc">控制复习题目按顺序还是随机展示</text></view>
|
||||
<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>
|
||||
@ -47,16 +47,16 @@
|
||||
|
||||
<view wx:if="{{settings}}" class="section-block">
|
||||
<view class="section-head">
|
||||
<view class="section-ico">☀</view>
|
||||
<view class="section-ico">☰</view>
|
||||
<text class="section-title">交互设置</text>
|
||||
</view>
|
||||
<view class="panel">
|
||||
<view class="row" bindtap="toggleSetting" data-key="auto_next_question">
|
||||
<view><text class="label">自动进入下一词</text><text class="desc">答题后自动跳转,保持心流状态</text></view>
|
||||
<view><text class="label">自动进入下一题</text><text class="desc">答题后自动跳转,保持连续复习节奏</text></view>
|
||||
<view class="toggle {{settings.auto_next_question ? 'on' : ''}}">{{settings.auto_next_question ? 'ON' : 'OFF'}}</view>
|
||||
</view>
|
||||
<view class="row" bindtap="toggleSetting" data-key="show_answer_after_submit">
|
||||
<view><text class="label">提交后显示答案</text><text class="desc">答题后直接显示正确答案</text></view>
|
||||
<view><text class="label">提交后显示答案</text><text class="desc">答题后立即展示标准答案</text></view>
|
||||
<view class="toggle {{settings.show_answer_after_submit ? 'on' : ''}}">{{settings.show_answer_after_submit ? 'ON' : 'OFF'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user