Files
MBTI_wang/miniprogram/pages/test/sbti.js
Ghost 0aab56c842 fix: 小程序结果/选题页与 Test 路由及分享逻辑
1、修复了 result 与 test 各题型页及 test-select 的跳转与数据问题。

2、调整 api/Test 与 route 注册。

3、优化 app 入口与 share 工具。

Made-with: Cursor
2026-04-14 10:39:14 +08:00

269 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// pages/test/sbti.js — SBTI服务端全量拉题 + 组卷(闸口 DG1/DG2+ sbtiEngine 计分
const { fetchQuestionBank } = require('../../utils/questionBank')
const { buildShuffledPaper, getVisibleQuestions, computeSbtiResult } = require('../../utils/sbtiEngine')
const app = getApp()
const SBTI_TIME_SEC = 45 * 60
Page({
data: {
loading: true,
currentIndex: 0,
currentQuestion: null,
answers: {},
total: 0,
progress: 0,
timeRemaining: SBTI_TIME_SEC,
_initialSeconds: SBTI_TIME_SEC,
formatTime: '45:00',
isSubmitting: false
},
timer: null,
_paper: null,
_advanceTimer: null,
onLoad(options) {
try {
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
} catch (e) {}
fetchQuestionBank('sbti', {})
.then((all) => {
if (!all || !all.length) {
wx.showToast({ title: '暂无题目', icon: 'none' })
this.setData({ loading: false })
return
}
const paper = buildShuffledPaper(all)
this._paper = paper
const visible = getVisibleQuestions(paper.ordered, {}, paper.dg2)
const total = visible.length
if (!total) {
wx.showToast({ title: '组卷失败', icon: 'none' })
this.setData({ loading: false })
return
}
this.setData({
loading: false,
currentIndex: 0,
currentQuestion: visible[0],
total,
progress: total ? Math.round((1 / total) * 100) : 0,
timeRemaining: SBTI_TIME_SEC,
_initialSeconds: SBTI_TIME_SEC,
formatTime: '45:00'
})
try {
require('../../utils/analytics').track('test_start', { type: 'sbti', total })
} catch (e) {}
try {
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
} catch (e) {}
this.startTimer()
})
.catch((err) => {
this.setData({ loading: false })
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
})
},
onUnload() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.timer) {
clearInterval(this.timer)
}
},
startTimer() {
this.timer = setInterval(() => {
let time = this.data.timeRemaining - 1
if (time <= 0) {
clearInterval(this.timer)
this.submitTest({ allowIncomplete: true })
return
}
const minutes = Math.floor(time / 60)
const seconds = time % 60
this.setData({
timeRemaining: time,
formatTime: `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
})
}, 1000)
},
_rebuildVisible() {
const paper = this._paper
if (!paper) return []
return getVisibleQuestions(paper.ordered, this.data.answers, paper.dg2)
},
selectAnswer(e) {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
const value = e.currentTarget.dataset.value
const cq = this.data.currentQuestion
if (value == null || !cq || cq.id == null) return
const questionId = cq.id
const idx = this.data.currentIndex
const answers = { ...this.data.answers, [questionId]: value }
const visible = getVisibleQuestions(this._paper.ordered, answers, this._paper.dg2)
const tot = visible.length
const curPos = visible.findIndex((q) => q.id === questionId)
const nextIdx = curPos >= 0 ? curPos + 1 : idx + 1
this.setData({ answers }, () => {
this._advanceTimer = setTimeout(() => {
this._advanceTimer = null
const d = this.data
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
if (nextIdx < visible.length) {
const nq = visible[nextIdx]
this.setData({
currentIndex: nextIdx,
currentQuestion: nq,
total: tot,
progress: tot ? Math.round(((nextIdx + 1) / tot) * 100) : 0
})
} else {
this.submitTest()
}
}, 320)
})
},
prevQuestion() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.data.currentIndex <= 0) return
const visible = this._rebuildVisible()
const newIndex = this.data.currentIndex - 1
const nq = visible[newIndex]
if (!nq) return
const tot = visible.length
this.setData({
currentIndex: newIndex,
currentQuestion: nq,
total: tot,
progress: tot ? Math.round(((newIndex + 1) / tot) * 100) : 0
})
},
nextQuestion() {
const visible = this._rebuildVisible()
if (this.data.currentIndex < visible.length - 1) {
const newIndex = this.data.currentIndex + 1
const nq = visible[newIndex]
const tot = visible.length
this.setData({
currentIndex: newIndex,
currentQuestion: nq,
total: tot,
progress: tot ? Math.round(((newIndex + 1) / tot) * 100) : 0
})
}
},
finishTest() {
const q = this.data.currentQuestion
if (!q) return
if (this.data.answers[q.id] == null) {
wx.showToast({ title: '请先选择一项', icon: 'none' })
return
}
const visible = this._rebuildVisible()
const missing = visible.filter((x) => this.data.answers[x.id] == null || this.data.answers[x.id] === '')
if (missing.length) {
wx.showToast({ title: '还有题目未作答', icon: 'none' })
return
}
this.submitTest()
},
/**
* @param {{ allowIncomplete?: boolean }} opt 计时结束允许未答完也提交
*/
submitTest(opt = {}) {
if (this.data.isSubmitting) return
const allowIncomplete = !!opt.allowIncomplete
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
const visible = this._rebuildVisible()
const answers = this.data.answers
if (!allowIncomplete) {
const missing = visible.filter((x) => answers[x.id] == null || answers[x.id] === '')
if (missing.length) {
wx.showToast({ title: `还有 ${missing.length} 题未作答`, icon: 'none' })
this.startTimer()
return
}
}
this.setData({ isSubmitting: true })
let result
try {
const paper = this._paper
const qs = paper ? paper.ordered.slice() : []
if (paper && paper.dg2 && !qs.some((q) => q.id === paper.dg2.id)) {
qs.push(paper.dg2)
}
result = computeSbtiResult(qs, answers)
} catch (err) {
console.error('computeSbtiResult', err)
wx.showToast({ title: '计算结果失败,请重试', icon: 'none' })
this.setData({ isSubmitting: false })
this.startTimer()
return
}
const resultData = {
...result,
answers,
testDuration: (this.data._initialSeconds || SBTI_TIME_SEC) - this.data.timeRemaining,
completedAt: new Date().toISOString(),
timestamp: new Date().toISOString()
}
wx.setStorageSync('sbtiResult', resultData)
app.saveTestResult('sbti', resultData)
try {
require('../../utils/analytics').track('test_complete', {
type: 'sbti',
result: result.sbtiType,
duration: resultData.testDuration
})
} catch (e) {}
wx.redirectTo({
url: '/pages/result/sbti'
})
},
onShareAppMessage() {
const { getSharePath } = require('../../utils/share')
return {
title: '来测测你的 SBTI 类型',
path: getSharePath('/pages/test/sbti')
}
},
onShareTimeline() {
const { buildShareQuery } = require('../../utils/share')
return {
title: '来测测你的 SBTI 类型',
query: buildShareQuery()
}
}
})