Files
xingque/pages/dashou-exam/dashou-exam.js
2026-07-09 00:17:03 +08:00

284 lines
8.3 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.
import { createPage, request } from '../../utils/base-page.js';
import { refreshDashouMembership } from '../../utils/dashou-profile.js';
import { isApiSuccess, getApiMsg } from '../../utils/api-helper.js';
const LABELS = ['A', 'B', 'C', 'D', 'E', 'F'];
function toSlotNum(slot) {
const n = Number(slot);
return Number.isFinite(n) ? n : slot;
}
function normalizeSlots(slots) {
return (slots || []).map(toSlotNum);
}
function slotsEqual(a, b) {
const sa = normalizeSlots(a).sort((x, y) => x - y);
const sb = normalizeSlots(b).sort((x, y) => x - y);
return sa.length === sb.length && sa.every((v, i) => v == sb[i]);
}
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
Page(createPage({
data: {
loading: true,
alreadyPassed: false,
finished: false,
blocked: false,
blockTitle: '',
blockReason: '',
needRecharge: false,
questions: [],
currentIndex: 0,
currentQuestion: null,
questionType: 1,
total: 0,
wrongCount: 0,
maxWrong: 0,
correctCount: 0,
selectedSlots: [],
showExplanation: false,
},
onLoad() {
this.initExam();
},
showBlocked(title, reason, needRecharge = false) {
this.setData({
loading: false,
blocked: true,
blockTitle: title,
blockReason: reason,
needRecharge: !!needRecharge,
});
},
/** 进入页先查 status不满足条件留在本页提示不自动退出 */
async initExam() {
this.setData({ loading: true, blocked: false, needRecharge: false });
try {
await refreshDashouMembership(getApp());
const res = await request({ url: '/jituan/dashou-exam/status', method: 'POST' });
const body = res?.data;
if (!isApiSuccess(body)) {
this.showBlocked('无法开始考试', getApiMsg(body) || '无法获取考试状态,请稍后重试');
return;
}
const d = body.data || {};
if (!d.exam_enabled) {
this.showBlocked(
'未开启考试',
'本俱乐部尚未开启接单考试。若后台已开启,请确认配置的是当前小程序对应俱乐部。'
);
return;
}
if (d.exam_passed) {
this.setData({ loading: false, alreadyPassed: true });
return;
}
const cfg = d.config || {};
if (cfg.require_member && !d.has_member) {
this.showBlocked('须先开通会员', '本俱乐部要求开通会员后才能参加考试', true);
return;
}
if ((d.pool_count || 0) < 1) {
this.showBlocked(
'暂无可用考题',
'考试已开启,但题库没有「已上架」的题目。请在后台确认题目已上架,且每题至少两个选项并标记正确答案。'
);
return;
}
await this.loadBundle();
} catch (e) {
this.showBlocked('网络错误', '请检查网络后下拉重新进入本页');
}
},
async loadBundle() {
this.setData({ loading: true, finished: false, showExplanation: false, blocked: false });
try {
const res = await request({ url: '/jituan/dashou-exam/bundle', method: 'POST' });
const body = res?.data;
if (!isApiSuccess(body)) {
this.showBlocked('加载考题失败', getApiMsg(body) || '请稍后重试或联系管理员');
return;
}
const d = body.data || {};
if (d.already_passed) {
this.setData({ alreadyPassed: true, loading: false });
return;
}
const cfg = d.config || {};
const prepared = (d.questions || []).map((q) => this.prepareQuestion(q)).filter((q) => (q.displayOptions || []).length > 0);
if (!prepared.length) {
this.showBlocked(
'题目配置不完整',
'已抽到的题目缺少有效选项,请在后台检查题目是否至少两个选项并标记正确答案。'
);
return;
}
this.setData({
loading: false,
questions: prepared,
total: prepared.length,
currentIndex: 0,
wrongCount: 0,
correctCount: 0,
maxWrong: cfg.max_wrong || 0,
currentQuestion: prepared[0],
questionType: prepared[0].question_type || 1,
selectedSlots: [],
});
} catch (e) {
this.showBlocked('网络错误', '拉取考题失败,请稍后重试');
}
},
prepareQuestion(q) {
const oss = getApp().globalData.ossImageUrl || '';
const displayImages = (q.images || []).map((u) => (u.startsWith('http') ? u : oss + u));
const shuffled = shuffle(q.options || []).map((opt, idx) => ({
slot: toSlotNum(opt.slot),
text: opt.text,
label: LABELS[idx] || String(idx + 1),
selected: false,
}));
return {
...q,
question_type: Number(q.question_type) || 1,
displayImages,
displayOptions: shuffled,
correctSlots: normalizeSlots(q.correct_slots || []),
};
},
applySelectedToOptions(selectedSlots) {
const q = this.data.currentQuestion;
if (!q) return;
const set = normalizeSlots(selectedSlots);
const displayOptions = (q.displayOptions || []).map((opt) => ({
...opt,
selected: set.some((s) => s == opt.slot),
}));
this.setData({
selectedSlots: set,
currentQuestion: { ...q, displayOptions },
});
},
onSelectOption(e) {
const slot = toSlotNum(e.currentTarget.dataset.slot);
const q = this.data.currentQuestion;
if (!q || slot === undefined || slot === null || slot === '') return;
if (q.question_type === 2) {
const set = [...this.data.selectedSlots];
const i = set.findIndex((s) => s == slot);
if (i >= 0) set.splice(i, 1);
else set.push(slot);
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
}
this.applySelectedToOptions(set);
return;
}
this.applySelectedToOptions([slot]);
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
}
setTimeout(() => this.gradeAnswer([slot]), 120);
},
confirmMulti() {
if (this.data.showExplanation) {
this.setData({ showExplanation: false });
this.applySelectedToOptions([]);
return;
}
if (!this.data.selectedSlots.length) {
wx.showToast({ title: '请选择答案', icon: 'none' });
return;
}
this.gradeAnswer([...this.data.selectedSlots]);
},
/** 前端本地判分bundle 已含正确答案与解析) */
gradeAnswer(selected) {
const q = this.data.currentQuestion;
const correct = q.correctSlots || [];
const isRight = slotsEqual(selected, correct);
if (!isRight) {
const wrongCount = this.data.wrongCount + 1;
this.setData({ wrongCount, showExplanation: true });
if (wrongCount > this.data.maxWrong) {
wx.showModal({
title: '答题失败',
content: `${q.explanation || '本题答错了'}。错题过多,将重新开始本轮考试。`,
showCancel: false,
success: () => this.loadBundle(),
});
} else {
wx.showToast({ title: '答错了,请查看解析', icon: 'none' });
}
return;
}
const correctCount = this.data.correctCount + 1;
const next = this.data.currentIndex + 1;
if (next >= this.data.total) {
this.submitPassed(correctCount);
return;
}
const nextQ = this.data.questions[next];
this.setData({
correctCount,
currentIndex: next,
currentQuestion: nextQ,
questionType: nextQ.question_type,
selectedSlots: [],
showExplanation: false,
});
},
async submitPassed(correctCount) {
try {
const res = await request({ url: '/jituan/dashou-exam/mark-passed', method: 'POST' });
const body = res?.data;
if (isApiSuccess(body)) {
this.setData({ finished: true, correctCount });
} else {
wx.showToast({ title: getApiMsg(body) || '提交失败', icon: 'none' });
}
} catch (e) {
wx.showToast({ title: '提交失败', icon: 'none' });
}
},
goRecharge() {
wx.navigateTo({ url: '/pages/fighter-recharge/fighter-recharge' });
},
retryInit() {
this.initExam();
},
goBack() {
const pages = getCurrentPages();
if (pages.length > 1) {
wx.navigateBack();
} else {
wx.switchTab({ url: '/pages/accept-order/accept-order' });
}
},
}));