feat: 小程序与管理端迭代(神仙AI、了解自己CRM、AI测试入口、报表与分润等)

Made-with: Cursor
This commit is contained in:
卡若
2026-04-17 19:46:48 +08:00
parent cf835ea585
commit 7108f28280
239 changed files with 21061 additions and 2175 deletions

View File

@@ -9,6 +9,9 @@ const MBTI_TIME_SEC = 30 * 60 // 30 分钟
Page({
data: {
loading: true,
loadError: false,
loadErrorMsg: '',
usingLocalFallback: false,
questions: [],
currentIndex: 0,
currentQuestion: null,
@@ -30,26 +33,37 @@ Page({
try {
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
} catch (e) {}
// 分享直达本页时 silentLogin 可能尚未完成,须先 ensureLogin 再拉题,否则 401 → 白屏
app.ensureLogin()
.then((ok) => {
if (!ok) {
this.setData({ loading: false })
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
return Promise.reject(new Error('login'))
}
return loadQuestions('mbti', {})
})
this._loadOptions = options || {}
this._startLoadQuestions()
},
retryLoad() {
this._startLoadQuestions()
},
/**
* 登录与拉题解耦:即使登录失败,仍尝试拉题(线上失败会走 401/network 降级本地)
*/
_startLoadQuestions() {
this.setData({ loading: true, loadError: false, loadErrorMsg: '', usingLocalFallback: false })
// 先尝试登录,但不阻塞题库;拉题失败会降级本地 fallback
const ensure = (typeof app.ensureLogin === 'function')
? app.ensureLogin().catch(() => false)
: Promise.resolve(false)
ensure
.then(() => loadQuestions('mbti', { allowLocalFallback: true }))
.then((questions) => {
if (!questions) return
const total = questions.length
if (!total) {
wx.showToast({ title: '暂无题目', icon: 'none' })
this.setData({ loading: false })
if (!questions || !questions.length) {
this.setData({ loading: false, loadError: true, loadErrorMsg: '暂无题目,请稍后再试' })
return
}
const total = questions.length
// 判断是否为本地题id 以 'L-' 前缀)
const isLocal = !!(questions[0] && typeof questions[0].id === 'string' && questions[0].id.indexOf('L-') === 0)
this.setData({
loading: false,
loadError: false,
usingLocalFallback: isLocal,
questions,
currentQuestion: questions[0],
canAccess: true,
@@ -60,7 +74,7 @@ Page({
formatTime: '30:00'
})
try {
require('../../utils/analytics').track('test_start', { type: 'mbti', total })
require('../../utils/analytics').track('test_start', { type: 'mbti', total, source: isLocal ? 'local' : 'remote' })
} catch (e) {}
try {
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
@@ -68,9 +82,8 @@ Page({
this.startTimer()
})
.catch((err) => {
if (err && err.message === 'login') return
this.setData({ loading: false })
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
const msg = (err && err.message) || '加载失败,请检查网络后重试'
this.setData({ loading: false, loadError: true, loadErrorMsg: msg })
})
},

View File

@@ -3,7 +3,18 @@
<view wx:if="{{loading}}" class="test-loading">
<text class="test-loading-text">加载题目…</text>
</view>
<view wx:elif="{{loadError}}" class="test-error-state">
<view class="test-error-ic">⚠️</view>
<text class="test-error-title">加载失败</text>
<text class="test-error-msg">{{loadErrorMsg || '网络异常,请检查后重试'}}</text>
<view class="test-error-btn" bindtap="retryLoad">重新加载</view>
<text class="test-error-hint">若持续失败,请在微信中关闭小程序后再次打开</text>
</view>
<block wx:elif="{{currentQuestion}}">
<view wx:if="{{usingLocalFallback}}" class="fallback-banner">
<text class="fallback-banner-ic">📶</text>
<text class="fallback-banner-text">当前使用本地题库(网络异常自动切换),结果仍会在本机完成</text>
</view>
<view class="progress-section">
<view class="progress-info">
<text class="question-count">问题 {{currentIndex + 1}}/{{total}}</text>

View File

@@ -20,6 +20,74 @@
color: #666;
}
/* 错误态:加载失败 + 重试按钮 */
.test-error-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 60rpx;
text-align: center;
}
.test-error-ic {
font-size: 84rpx;
line-height: 1;
margin-bottom: 20rpx;
}
.test-error-title {
font-size: 36rpx;
font-weight: 700;
color: #111827;
margin-bottom: 12rpx;
}
.test-error-msg {
font-size: 26rpx;
color: #6b7280;
line-height: 1.6;
margin-bottom: 40rpx;
max-width: 520rpx;
}
.test-error-btn {
padding: 20rpx 56rpx;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border-radius: 48rpx;
box-shadow: 0 10rpx 24rpx rgba(99, 102, 241, 0.3);
margin-bottom: 28rpx;
}
.test-error-btn:active {
transform: scale(0.97);
opacity: 0.9;
}
.test-error-hint {
font-size: 22rpx;
color: #9ca3af;
}
/* 本地 fallback 题库提示条 */
.fallback-banner {
margin: 16rpx 24rpx 0;
padding: 14rpx 20rpx;
background: #fef3c7;
border: 1rpx solid #fcd34d;
border-radius: 12rpx;
display: flex;
align-items: center;
gap: 10rpx;
}
.fallback-banner-ic {
font-size: 24rpx;
}
.fallback-banner-text {
font-size: 22rpx;
color: #92400e;
line-height: 1.4;
flex: 1;
}
.progress-section {
padding: 32rpx;
border-bottom: 1rpx solid #e5e5e5;