全站大数据台账补齐跨页搜索、筛选和分页,包括投档监督、报到审批、成绩复议、公开录取名单等。
后台重构为一级业务域,并将招生录取拆为 5 个二级菜单。 修复成绩等级排名遮挡科目。 成绩单、录取通知书均支持带防伪码和二维码的 PDF 下载。 录取通知书移除“录取专用章”,支持学校自定义模板。 通知书编号采用“学校代码 + 考试代码 + 学校独立流水号”。 完成招生学校报到台账:Y/N/P 状态、暂存、Excel 导入导出、二维码扫描确认。 完成报到提交、计划完成率、补录决定、超级管理员审批和自动公开闭环。 自动公告现已进入公开首页、考生通知及招生学校工作台。 验证结果: 完整 npm test 全部通过。 浏览器端三个角色工作台验收通过。 Excel 模板已实际导出并渲染验证。 两类 PDF 已实际生成、转图检查,二维码、编号和无印章版式正常。
This commit is contained in:
@@ -345,13 +345,13 @@ document.addEventListener('click', async event => {
|
||||
const results = (state.pageData?.results || []).filter(item => item.examId === examId);
|
||||
const summary = (state.pageData?.summaries || []).find(item => item.examId === examId);
|
||||
if (!results.length || !summary?.verificationCode) return toast('成绩单暂不可下载', '请刷新页面后重试');
|
||||
downloadScoreReport({ organization: state.publicData.organization, candidate: state.pageData.candidate || { name: state.user.displayName, candidateNumber: state.user.candidateNumber }, exam: { id: examId, name: results[0].examName, code: results[0].examCode }, results, summary: { ...summary, publishedAt: [...results].sort((a,b) => new Date(b.publishedAt) - new Date(a.publishedAt))[0]?.publishedAt }, verificationCode: summary.verificationCode, verificationUrl: `${location.origin}/#verify/${summary.verificationCode}` });
|
||||
await downloadScoreReport({ organization: state.publicData.organization, candidate: state.pageData.candidate || { name: state.user.displayName, candidateNumber: state.user.candidateNumber }, exam: { id: examId, name: results[0].examName, code: results[0].examCode }, results, summary: { ...summary, publishedAt: [...results].sort((a,b) => new Date(b.publishedAt) - new Date(a.publishedAt))[0]?.publishedAt }, verificationCode: summary.verificationCode, verificationQr: summary.verificationQr, verificationUrl: `${location.origin}/#verify/${summary.verificationCode}` });
|
||||
return toast('PDF 成绩单已生成', '文件包含防伪查询码');
|
||||
}
|
||||
if (action === 'download-admission-notice') {
|
||||
const item = (state.pageData?.admissions || []).find(entry => entry.examId === target.dataset.examId);
|
||||
if (!item?.placement || item.placement.status !== 'final' || !item.noticeVerificationCode) return toast('录取通知书暂不可下载', '只有正式录取后才能生成');
|
||||
downloadAdmissionNotice({ organization: state.publicData.organization, candidate: { name: state.profile?.name || state.user.displayName }, exam: item.exam, placement: item.placement, school: item.placementSchool || { name: item.placement.schoolName || '招生学校' }, template: item.noticeTemplate || {}, verificationCode: item.noticeVerificationCode, verificationUrl: `${location.origin}/#verify/${item.noticeVerificationCode}` });
|
||||
await downloadAdmissionNotice({ organization: state.publicData.organization, candidate: { name: state.profile?.name || state.user.displayName }, exam: item.exam, placement: item.placement, school: item.placementSchool || { name: item.placement.schoolName || '招生学校' }, template: item.noticeTemplate || {}, verificationCode: item.noticeVerificationCode, verificationQr: item.noticeVerificationQr, noticeNumber: item.noticeNumber, verificationUrl: `${location.origin}/#verify/${item.noticeVerificationCode}` });
|
||||
return toast('录取通知书已生成', '请核对学校和录取类别');
|
||||
}
|
||||
if (action === 'download-admitted-candidates') {
|
||||
@@ -405,6 +405,30 @@ document.addEventListener('click', async event => {
|
||||
updateQualificationSelection(container);
|
||||
return;
|
||||
}
|
||||
if (action === 'download-admission-reporting') {
|
||||
window.location.href = `/api/admission/reporting/export?examId=${encodeURIComponent(target.dataset.examId)}`;
|
||||
return;
|
||||
}
|
||||
if (action === 'submit-admission-reporting') {
|
||||
if (!window.confirm('确认提交本轮全部报到情况?提交后需先完成补录决定,才能交由超级管理员审批。')) return;
|
||||
await api('/api/admission/reporting/submit', { method: 'POST', body: { examId: target.dataset.examId } });
|
||||
toast('报到情况已提交', '请根据计划完成率确认是否申请补录');
|
||||
return renderRoute();
|
||||
}
|
||||
if (action === 'review-admission-reporting') {
|
||||
const approved = target.dataset.approved === 'true';
|
||||
const supplement = target.dataset.supplement === 'true';
|
||||
const approvalNote = window.prompt(approved ? '请输入审批意见(可选)' : '请输入退回原因', '') ?? '';
|
||||
if (!approved && approvalNote.trim().length < 2) return toast('需要退回原因', '请说明招生学校应修改的内容');
|
||||
let preferenceEnd = '';
|
||||
if (approved && supplement) {
|
||||
preferenceEnd = window.prompt('请输入补录志愿截止时间(例如 2026-07-30T18:00)', '') ?? '';
|
||||
if (!preferenceEnd) return;
|
||||
}
|
||||
await api(`/api/admin/admission-reporting/${encodeURIComponent(target.dataset.id)}`, { method: 'PATCH', body: { approved, approvalNote, preferenceEnd } });
|
||||
toast(approved ? '审批完成并自动公开' : '已退回招生学校', approved ? (supplement ? '全部学校审批完成后将自动开启补录' : '报到统计已进入公开通知') : '招生学校可修改暂存数据后重新提交');
|
||||
return renderRoute();
|
||||
}
|
||||
if (action === 'clear-table-filters') {
|
||||
const tableId = target.dataset.target;
|
||||
state.tableFilters[tableId] = { query: '', status: 'all', filters: {} };
|
||||
@@ -1014,6 +1038,24 @@ document.addEventListener('submit', async event => {
|
||||
} else if (kind === 'admission-notice-template') {
|
||||
await api('/api/admission/notice-template', { method: 'PUT', body: formObject(form) });
|
||||
toast('录取通知书模板已保存', '正式录取考生将使用该模板生成 PDF'); renderRoute();
|
||||
} else if (kind === 'admission-reporting-draft') {
|
||||
const rows = [...form.querySelectorAll('[data-reporting-status]')].map(select => ({
|
||||
placementId: select.dataset.placementId,
|
||||
status: select.value,
|
||||
note: form.querySelector(`[data-reporting-note][data-placement-id="${CSS.escape(select.dataset.placementId)}"]`)?.value || ''
|
||||
}));
|
||||
if (!rows.length) return toast('当前页没有可暂存记录');
|
||||
await api('/api/admission/reporting/draft', { method: 'PUT', body: { examId: form.dataset.examId, rows } });
|
||||
toast('当前页已暂存', `${rows.length} 名考生的状态已保存,尚未正式提交`); renderRoute();
|
||||
} else if (kind === 'admission-reporting-scan') {
|
||||
const body = formObject(form);
|
||||
const result = await api('/api/admission/reporting/scan', { method: 'POST', body });
|
||||
toast('防伪二维码核验通过', `${result.row.name}已暂存为“已报到”`); renderRoute();
|
||||
} else if (kind === 'admission-reporting-decision') {
|
||||
const body = formObject(form);
|
||||
body.supplement = body.supplement === 'true';
|
||||
await api('/api/admission/reporting/decision', { method: 'POST', body });
|
||||
toast('学校决定已提交', '等待超级管理员审批;审批后报到统计将自动公开'); renderRoute();
|
||||
} else if (kind === 'admission-plan' || kind === 'school-admission-plan') {
|
||||
const body = formObject(form);
|
||||
body.categories = [...form.querySelectorAll('.admission-category-editor')].map((editor, index) => {
|
||||
@@ -1422,6 +1464,39 @@ window.addEventListener('keydown', event => {
|
||||
form.requestSubmit(form.querySelector('[data-result-mode="draft"]'));
|
||||
}
|
||||
}
|
||||
if (event.target.matches('[data-admission-reporting-file]')) {
|
||||
const input = event.target;
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
const examId = input.dataset.examId;
|
||||
input.value = '';
|
||||
(async () => {
|
||||
try {
|
||||
toast('正在导入报到状态', `${file.name} · 导入结果只会暂存`);
|
||||
const result = await api(`/api/admission/reporting/import?examId=${encodeURIComponent(examId)}`, { method: 'POST', headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, body: await file.arrayBuffer() });
|
||||
toast('Excel 已导入并暂存', `已更新 ${result.count} 名考生,提交前仍可修改`);
|
||||
renderRoute();
|
||||
} catch (error) { toast('Excel 导入失败', error.message); }
|
||||
})();
|
||||
}
|
||||
if (event.target.matches('[data-reporting-qr-file]')) {
|
||||
const input = event.target;
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
input.value = '';
|
||||
(async () => {
|
||||
try {
|
||||
if (!('BarcodeDetector' in window)) throw new Error('当前浏览器不支持图片二维码识别,请粘贴二维码中的核验链接');
|
||||
const detector = new BarcodeDetector({ formats: ['qr_code'] });
|
||||
const codes = await detector.detect(file);
|
||||
const code = codes[0]?.rawValue || '';
|
||||
if (!code) throw new Error('图片中没有识别到二维码');
|
||||
const result = await api('/api/admission/reporting/scan', { method: 'POST', body: { code } });
|
||||
toast('二维码核验通过', `${result.row.name}已暂存为“已报到”`);
|
||||
renderRoute();
|
||||
} catch (error) { toast('二维码识别失败', error.message); }
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user