feat: 管理端与用户端批量更新(订单/邀请双码/tabBar/审核与分销等)

- 管理端:用户详情、企业看板双小程序码、超管设置与用户列表等
- 后端:订单接口、邀请码企业版+个人版、分析与测试相关调整
- 微信小程序/抖音:自定义 tabBar 居中与拍摄钮上移、相机页与结果页、订单页、支付与统计
- 新增 faceResultDetail 工具与 mp 分析迁移脚本

Made-with: Cursor
This commit is contained in:
Ghost
2026-03-28 16:46:00 +08:00
parent 2bb8b979f1
commit 3317c84b5e
65 changed files with 4442 additions and 1839 deletions

View File

@@ -23,10 +23,15 @@ Page({
onLoad() {
const questions = shuffleQuestions(discQuestions)
this.setData({ questions, currentQuestion: questions[0] })
try { require('../../utils/analytics').track('test_start', { type: 'disc', total: questions.length }) } catch (e) {}
this.startTimer()
},
onUnload() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.timer) clearInterval(this.timer)
},
@@ -48,28 +53,48 @@ Page({
},
selectAnswer(e) {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
const value = e.currentTarget.dataset.value
const questionId = this.data.currentQuestion.id
let answers = { ...this.data.answers }
answers[questionId] = value
this.setData({
selectedAnswer: value,
answers: answers,
answeredCount: Object.keys(answers).length,
progress: (Object.keys(answers).length / this.data.total) * 100
})
const cq = this.data.currentQuestion
if (value == null || !cq || cq.id == null) return
setTimeout(() => {
if (this.data.currentIndex < this.data.total - 1) {
this.nextQuestion()
} else {
this.submitTest()
const questionId = cq.id
const idx = this.data.currentIndex
const tot = this.data.total
const answers = { ...this.data.answers }
answers[questionId] = value
const answeredCount = Object.keys(answers).length
this.setData(
{
selectedAnswer: value,
answers,
answeredCount,
progress: tot ? (answeredCount / tot) * 100 : 0
},
() => {
this._advanceTimer = setTimeout(() => {
this._advanceTimer = null
const d = this.data
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
if (idx < tot - 1) {
this.nextQuestion()
} else {
this.submitTest()
}
}, 300)
}
}, 300)
)
},
prevQuestion() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.data.currentIndex > 0) {
const newIndex = this.data.currentIndex - 1
const newQuestion = this.data.questions[newIndex]
@@ -131,6 +156,7 @@ Page({
// 本地缓存 + 全局缓存
wx.setStorageSync('discResult', resultData)
try { require('../../utils/analytics').track('test_complete', { type: 'disc', result: dominantType, duration: resultData.testDuration }) } catch (e) {}
if (app && typeof app.saveTestResult === 'function') {
app.saveTestResult('disc', resultData)
}

View File

@@ -32,6 +32,7 @@ Page({
total,
progress: total ? Math.round((1 / total) * 100) : 0
})
try { require('../../utils/analytics').track('test_start', { type: 'mbti', total }) } catch (e) {}
this.startTimer()
},
@@ -43,6 +44,10 @@ Page({
},
onUnload() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.timer) {
clearInterval(this.timer)
}
@@ -66,36 +71,53 @@ Page({
}, 1000)
},
// 选择答案(用闭包保存题号,避免 setTimeout 与 setData 时序导致最后一题未提交)
// 选择答案:防抖 + 过期回调校验,避免连点导致重复 next 漏题
selectAnswer(e) {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
const value = e.currentTarget.dataset.value
const questionId = this.data.currentQuestion.id
const cq = this.data.currentQuestion
if (value == null || !cq || cq.id == null) return
const questionId = cq.id
const idx = this.data.currentIndex
const tot = this.data.total
let answers = { ...this.data.answers }
const answers = { ...this.data.answers }
answers[questionId] = value
const answeredCount = Object.keys(answers).length
const progress = tot ? Math.round(((idx + 1) / tot) * 100) : 0
this.setData({
selectedAnswer: value,
answers,
answeredCount,
progress
})
setTimeout(() => {
if (idx < tot - 1) {
this.nextQuestion()
} else {
this.submitTest()
this.setData(
{
selectedAnswer: value,
answers,
answeredCount,
progress
},
() => {
this._advanceTimer = setTimeout(() => {
this._advanceTimer = null
const d = this.data
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
if (idx < tot - 1) {
this.nextQuestion()
} else {
this.submitTest()
}
}, 320)
}
}, 320)
)
},
// 上一题
prevQuestion() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.data.currentIndex > 0) {
const newIndex = this.data.currentIndex - 1
const newQuestion = this.data.questions[newIndex]
@@ -181,6 +203,7 @@ Page({
}
wx.setStorageSync('mbtiResult', resultData)
app.saveTestResult('mbti', resultData)
try { require('../../utils/analytics').track('test_complete', { type: 'mbti', result: result.mbtiType, confidence: result.confidence, duration: resultData.testDuration }) } catch (e) {}
wx.redirectTo({
url: '/pages/result/mbti'

View File

@@ -23,10 +23,15 @@ Page({
onLoad() {
const questions = shuffleQuestions(pdpQuestions)
this.setData({ questions, currentQuestion: questions[0] })
try { require('../../utils/analytics').track('test_start', { type: 'pdp', total: questions.length }) } catch (e) {}
this.startTimer()
},
onUnload() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.timer) clearInterval(this.timer)
},
@@ -53,28 +58,48 @@ Page({
},
selectAnswer(e) {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
const value = e.currentTarget.dataset.value
const questionId = this.data.currentQuestion.id
let answers = { ...this.data.answers }
answers[questionId] = value
this.setData({
selectedAnswer: value,
answers: answers,
answeredCount: Object.keys(answers).length,
progress: (Object.keys(answers).length / this.data.total) * 100
})
const cq = this.data.currentQuestion
if (value == null || !cq || cq.id == null) return
setTimeout(() => {
if (this.data.currentIndex < this.data.total - 1) {
this.nextQuestion()
} else {
this.submitTest()
const questionId = cq.id
const idx = this.data.currentIndex
const tot = this.data.total
const answers = { ...this.data.answers }
answers[questionId] = value
const answeredCount = Object.keys(answers).length
this.setData(
{
selectedAnswer: value,
answers,
answeredCount,
progress: tot ? (answeredCount / tot) * 100 : 0
},
() => {
this._advanceTimer = setTimeout(() => {
this._advanceTimer = null
const d = this.data
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
if (idx < tot - 1) {
this.nextQuestion()
} else {
this.submitTest()
}
}, 300)
}
}, 300)
)
},
prevQuestion() {
if (this._advanceTimer) {
clearTimeout(this._advanceTimer)
this._advanceTimer = null
}
if (this.data.currentIndex > 0) {
const newIndex = this.data.currentIndex - 1
const newQuestion = this.data.questions[newIndex]
@@ -134,6 +159,7 @@ Page({
// 本地缓存 + 全局缓存
wx.setStorageSync('pdpResult', resultData)
try { require('../../utils/analytics').track('test_complete', { type: 'pdp', result: dominantType, duration: resultData.testDuration }) } catch (e) {}
if (app && typeof app.saveTestResult === 'function') {
app.saveTestResult('pdp', resultData)
}