完成报到确认页完善:
增加勾选、全选本页、批量设置状态和统一备注;应用后点击“暂存当前页”保存。 修复 Excel 文件选择事件被错误放在键盘监听中的问题。 导入后显示读取行数、实际更新人数、未变化人数及状态变化明细;重复导入会明确提示“没有产生变化”。 “拍摄二维码”改为调用 getUserMedia 实时相机,图片选择仅作为备用方式。 扫码后先展示考生确认页,默认选择“确认报到”,点击“暂存”后才修改数据。 后端新增扫码预览接口,预览过程不会提前修改报到状态。
This commit is contained in:
@@ -61,6 +61,18 @@ export function createAdmissionRoutes(context) {
|
||||
return { setting, record };
|
||||
}
|
||||
|
||||
function reportingScanTarget(db, school, rawCode) {
|
||||
const match = String(rawCode || '').toUpperCase().match(/AN-[A-F0-9]{24}/);
|
||||
if (!match) return { error: [400, '未识别到有效的录取通知书防伪码'] };
|
||||
const code = match[0];
|
||||
const placement = admissionRecords(db, 'placement').find(item => item.schoolId === school.id && item.status === 'final' && safeCodeEqual(code, admissionNoticeCode(documentVerificationSecret, item, db.exams.find(exam => exam.id === item.examId) || {})));
|
||||
if (!placement) return { error: [404, '该二维码不属于本校有效录取通知书'] };
|
||||
const plan = approvedPlans(db, placement.examId).find(item => item.schoolId === school.id);
|
||||
const { record } = editableReportingRecord(db, placement.examId, school.id);
|
||||
if (!plan || !record || !['draft', 'rejected'].includes(record.status) || !(record.payload?.rows || []).some(item => item.placementId === placement.id)) return { error: [409, '该考生不在当前可维护的报到批次'] };
|
||||
return { code, placement, plan, record };
|
||||
}
|
||||
|
||||
async function handleAdmission(request, response, pathname) {
|
||||
if (!pathname.startsWith('/api/admission/')) return false;
|
||||
const user = await requireUser(request, response, 'admission_school');
|
||||
@@ -154,6 +166,9 @@ export function createAdmissionRoutes(context) {
|
||||
const byCandidate = new Map(available.map(item => [item.candidateNumber, item]));
|
||||
const seen = new Set();
|
||||
const updates = [];
|
||||
const changes = [];
|
||||
let unchangedCount = 0;
|
||||
const importedAt = nowIso();
|
||||
for (const item of imported) {
|
||||
const noticeNumber = cleanText(item.noticeNumber, 100);
|
||||
const candidateNumber = cleanText(item.candidateNumber, 100);
|
||||
@@ -163,13 +178,20 @@ export function createAdmissionRoutes(context) {
|
||||
const code = String(item.reportingStatusCode || '').trim().toUpperCase();
|
||||
if (!reportingStatusByCode[code]) return sendError(response, 400, `Excel 第 ${item.__row} 行报到状态码只能填写 Y、N 或 P`);
|
||||
seen.add(target.placementId);
|
||||
updates.push({ placementId: target.placementId, status: reportingStatusByCode[code], note: cleanText(item.reportingNote, 300), updatedAt: nowIso(), source: 'excel' });
|
||||
const status = reportingStatusByCode[code];
|
||||
const note = cleanText(item.reportingNote, 300);
|
||||
if (target.status === status && target.note === note) { unchangedCount += 1; continue; }
|
||||
updates.push({ placementId: target.placementId, status, note, updatedAt: importedAt, source: 'excel' });
|
||||
changes.push({ placementId: target.placementId, name: target.name, candidateNumber: target.candidateNumber, noticeNumber: target.noticeNumber, from: target.status, to: status, fromCode: target.statusCode, toCode: code, noteChanged: target.note !== note });
|
||||
}
|
||||
const merged = new Map((record.payload?.rows || []).map(item => [item.placementId, item]));
|
||||
updates.forEach(item => merged.set(item.placementId, item));
|
||||
record.status = 'draft'; record.updatedAt = nowIso(); record.payload = { ...record.payload, rows: [...merged.values()], lastImportedAt: record.updatedAt, lastImportedBy: user.displayName };
|
||||
await database.saveAdmissionRecord(record, logAction(db, user, 'Excel 暂存考生报到状态', `${school.name} · ${updates.length} 人`));
|
||||
return sendJson(response, 200, { ok: true, count: updates.length, batch: reportingBatch({ ...db, admissionRecords: db.admissionRecords.map(item => item.id === record.id ? record : item) }, plan, record) });
|
||||
if (updates.length) {
|
||||
record.status = 'draft'; record.updatedAt = importedAt; record.payload = { ...record.payload, rows: [...merged.values()], lastImportedAt: record.updatedAt, lastImportedBy: user.displayName };
|
||||
await database.saveAdmissionRecord(record, logAction(db, user, 'Excel 暂存考生报到状态', `${school.name} · 实际更新 ${updates.length} 人`));
|
||||
}
|
||||
const nextDb = updates.length ? { ...db, admissionRecords: db.admissionRecords.map(item => item.id === record.id ? record : item) } : db;
|
||||
return sendJson(response, 200, { ok: true, count: imported.length, changedCount: updates.length, unchangedCount, changes, batch: reportingBatch(nextDb, plan, record) });
|
||||
}
|
||||
if (request.method === 'PUT' && pathname === '/api/admission/reporting/draft') {
|
||||
const body = await readJson(request);
|
||||
@@ -186,20 +208,28 @@ export function createAdmissionRoutes(context) {
|
||||
await database.saveAdmissionRecord(record, logAction(db, user, '暂存考生报到状态', `${school.name} · ${updates.length} 人`));
|
||||
return sendJson(response, 200, { ok: true, batch: reportingBatch({ ...db, admissionRecords: db.admissionRecords.map(item => item.id === record.id ? record : item) }, plan, record) });
|
||||
}
|
||||
if (request.method === 'POST' && pathname === '/api/admission/reporting/scan-preview') {
|
||||
const body = await readJson(request);
|
||||
const target = reportingScanTarget(db, school, body.code);
|
||||
if (target.error) return sendError(response, ...target.error);
|
||||
const { code, placement, plan, record } = target;
|
||||
if (body.examId && cleanText(body.examId, 64) !== placement.examId) return sendError(response, 400, '二维码不属于当前考试报到批次');
|
||||
const row = reportingRows(db, plan, record).find(item => item.placementId === placement.id);
|
||||
return sendJson(response, 200, { ok: true, code, examId: placement.examId, row });
|
||||
}
|
||||
if (request.method === 'POST' && pathname === '/api/admission/reporting/scan') {
|
||||
const body = await readJson(request);
|
||||
const match = String(body.code || '').toUpperCase().match(/AN-[A-F0-9]{24}/);
|
||||
if (!match) return sendError(response, 400, '未识别到有效的录取通知书防伪码');
|
||||
const code = match[0];
|
||||
const placement = admissionRecords(db, 'placement').find(item => item.schoolId === school.id && item.status === 'final' && safeCodeEqual(code, admissionNoticeCode(documentVerificationSecret, item, db.exams.find(exam => exam.id === item.examId) || {})));
|
||||
if (!placement) return sendError(response, 404, '该二维码不属于本校有效录取通知书');
|
||||
const plan = approvedPlans(db, placement.examId).find(item => item.schoolId === school.id);
|
||||
const { record } = editableReportingRecord(db, placement.examId, school.id);
|
||||
if (!plan || !record || !['draft', 'rejected'].includes(record.status) || !(record.payload?.rows || []).some(item => item.placementId === placement.id)) return sendError(response, 409, '该考生不在当前可维护的报到批次');
|
||||
const target = reportingScanTarget(db, school, body.code);
|
||||
if (target.error) return sendError(response, ...target.error);
|
||||
const { placement, plan, record } = target;
|
||||
if (body.examId && cleanText(body.examId, 64) !== placement.examId) return sendError(response, 400, '二维码不属于当前考试报到批次');
|
||||
const status = cleanText(body.status, 30);
|
||||
if (!['reported', 'not_reported', 'pending'].includes(status)) return sendError(response, 400, '请选择有效的报到确认状态');
|
||||
const merged = new Map((record.payload?.rows || []).map(item => [item.placementId, item]));
|
||||
merged.set(placement.id, { placementId: placement.id, status: 'reported', note: cleanText(body.note, 300) || '扫描录取通知书二维码确认', updatedAt: nowIso(), source: 'qr_scan' });
|
||||
const fallbackNote = status === 'reported' ? '扫描录取通知书二维码确认报到' : status === 'not_reported' ? '扫描录取通知书二维码确认未报到' : '扫描录取通知书二维码后暂待确认';
|
||||
merged.set(placement.id, { placementId: placement.id, status, note: cleanText(body.note, 300) || fallbackNote, updatedAt: nowIso(), source: 'qr_scan' });
|
||||
record.status = 'draft'; record.updatedAt = nowIso(); record.payload = { ...record.payload, rows: [...merged.values()], savedAt: record.updatedAt, savedBy: user.displayName };
|
||||
await database.saveAdmissionRecord(record, logAction(db, user, '扫码暂存考生报到', `${school.name} · ${placement.payload?.noticeNumber || placement.id}`));
|
||||
await database.saveAdmissionRecord(record, logAction(db, user, '扫码确认并暂存考生报到', `${school.name} · ${placement.payload?.noticeNumber || placement.id} · ${reportingCodeByStatus[status]}`));
|
||||
const nextDb = { ...db, admissionRecords: db.admissionRecords.map(item => item.id === record.id ? record : item) };
|
||||
return sendJson(response, 200, { ok: true, row: reportingRows(nextDb, plan, record).find(item => item.placementId === placement.id), batch: reportingBatch(nextDb, plan, record) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user