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

@@ -74,6 +74,21 @@ function applyDrawCountAfterShuffle(questions) {
* @param {number|null|undefined} enterpriseId
* @returns {Promise<Array>}
*/
function parseJsonBody(raw) {
if (raw == null) return {}
if (typeof raw === 'object' && !Array.isArray(raw)) return raw
if (typeof raw === 'string') {
const s = raw.trim()
if (!s) return {}
try {
return JSON.parse(s)
} catch (e) {
return { _parseError: true, _rawSnippet: s.slice(0, 120) }
}
}
return {}
}
function fetchQuestionBank(type, enterpriseId) {
const q = [`type=${encodeURIComponent(type)}`]
if (enterpriseId != null && Number(enterpriseId) > 0) {
@@ -84,9 +99,14 @@ function fetchQuestionBank(type, enterpriseId) {
method: 'GET',
needAuth: true
}).then((res) => {
const body = res.data || {}
const body = parseJsonBody(res.data)
if (body._parseError) {
throw new Error('接口返回非 JSON请检查小程序 apiBase 与后台域名(可设 Storage key: apiBaseOverride')
}
if (body.code !== 200 || body.data == null) {
throw new Error(body.message || '拉取题库失败')
const msg = body.message || '拉取题库失败'
const hint = body.code === 401 || body.code === 403 ? `${msg}(请重新进入小程序或下拉刷新)` : msg
throw new Error(hint)
}
const list = body.data.list
if (!Array.isArray(list)) {
@@ -96,18 +116,47 @@ function fetchQuestionBank(type, enterpriseId) {
})
}
/**
* 仅 mbti 类型支持本地 fallback 题库60 题)
* 网络异常isNetworkError或 5xx 且线上拉题失败时启用,保证测试可完成
*/
function loadLocalFallback(type) {
if (type !== 'mbti') return null
try {
const { getMbtiLocalQuestions } = require('./mbtiLocalQuestions.js')
const list = getMbtiLocalQuestions()
return Array.isArray(list) && list.length ? list : null
} catch (e) {
return null
}
}
/**
* @param {'mbti'|'sbti'|'disc'|'pdp'} type
* @param {{ enterpriseId?: number|null }} opts
* @returns {Promise<Array>} 乱序后的题目;接口无题或失败则 reject
* @param {{ enterpriseId?: number|null, allowLocalFallback?: boolean }} opts
* @returns {Promise<Array>} 乱序后的题目;接口无题或失败则 rejectmbti 可降级本地)
*/
function loadQuestions(type, opts = {}) {
const enterpriseId = resolveEnterpriseIdForQuestionBank(opts)
const allowLocal = opts.allowLocalFallback !== false // 默认允许
return fetchQuestionBank(type, enterpriseId).then((list) => {
if (!list.length) {
throw new Error('暂无启用题目')
}
return applyDrawCountAfterShuffle(shuffleQuestions(list))
}).catch((err) => {
// 网络异常 / 5xx / 401登录失败/ 超时均降级4xx 业务错误不降级
const status = err && err.statusCode
const canFallback = err && (
err.isNetworkError ||
(status >= 500 && status < 600) ||
status === 401
)
if (!allowLocal || !canFallback) throw err
const local = loadLocalFallback(type)
if (!local) throw err
console.warn('[questionBank] 后端不可达,降级本地 fallback 题库:', type, err && err.message)
return applyDrawCountAfterShuffle(shuffleQuestions(local))
})
}