Files
MBTI_wang/miniprogram/utils/questionBank.js

168 lines
4.9 KiB
JavaScript
Raw Permalink 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.

/**
* 仅从服务端拉取启用题库(/api/test/questions无本地题目保底。
* enterpriseId未传时与 test/submit 一致,见 enterpriseContext.getEnterpriseIdForApiPayload()
*/
const { requestPromise } = require('./request')
function getAppSafe() {
try {
return getApp()
} catch (e) {
return null
}
}
/**
* Fisher-Yates打乱题目顺序并随机每题选项顺序深拷贝
* @param {Array} questions
* @returns {Array}
*/
function shuffleQuestions(questions) {
const arr = (questions || []).map(q => ({
...q,
options: (q.options || []).slice().sort(() => Math.random() - 0.5)
}))
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[arr[i], arr[j]] = [arr[j], arr[i]]
}
return arr
}
/**
* @param {{ enterpriseId?: number|null }} opts
* @returns {number|null}
*/
function resolveEnterpriseIdForQuestionBank(opts) {
const o = opts || {}
if (Object.prototype.hasOwnProperty.call(o, 'enterpriseId')) {
const v = o.enterpriseId
if (v == null || v === '') {
return null
}
const n = Number(v)
return Number.isFinite(n) && n > 0 ? n : null
}
try {
const { getEnterpriseIdForApiPayload } = require('./enterpriseContext')
return getEnterpriseIdForApiPayload()
} catch (e) {
return null
}
}
function getTestQuestionDrawCount() {
const app = getAppSafe()
const n = app && app.globalData && app.globalData.testQuestionDrawCount
const num = parseInt(String(n == null || n === '' ? '0' : n), 10)
if (!Number.isFinite(num) || num <= 0) {
return 0
}
return Math.min(500, num)
}
function applyDrawCountAfterShuffle(questions) {
const n = getTestQuestionDrawCount()
if (!n || questions.length <= n) {
return questions
}
return questions.slice(0, n)
}
/**
* @param {'mbti'|'sbti'|'disc'|'pdp'} type
* @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) {
q.push(`enterpriseId=${Number(enterpriseId)}`)
}
return requestPromise({
url: `/api/test/questions?${q.join('&')}`,
method: 'GET',
needAuth: true
}).then((res) => {
const body = parseJsonBody(res.data)
if (body._parseError) {
throw new Error('接口返回非 JSON请检查小程序 apiBase 与后台域名(可设 Storage key: apiBaseOverride')
}
if (body.code !== 200 || body.data == null) {
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)) {
throw new Error('题库格式错误')
}
return list
})
}
/**
* 仅 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, 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))
})
}
module.exports = {
fetchQuestionBank,
loadQuestions,
shuffleQuestions
}