优化2 patch2

This commit is contained in:
2026-07-21 16:22:29 +08:00 Unverified
parent 4929858ec2
commit 722bb6f016
13 changed files with 381 additions and 65 deletions
+52 -2
View File
@@ -276,6 +276,7 @@ export function createAdminRoutes(context) {
const plan = existing || { id: uid('plan'), kind: 'plan', examId: exam.id, schoolId: school.id, createdAt: now };
Object.assign(plan, { userId: user.id, status: 'approved', updatedAt: now, payload: { categories, note: cleanText(body.note, 500), submittedBy: user.displayName, reviewedBy: user.displayName, reviewedAt: now, reviewNote: '超级管理员代上传并审核通过' } });
await database.saveAdmissionRecord(plan, logAction(db, user, '代上传招生计划', `${school.name} · ${exam.name}`));
await cache.invalidate('public');
return sendJson(response, existing ? 200 : 201, { ok: true, plan });
}
const planReviewMatch = pathname.match(/^\/api\/admin\/admission-plans\/([^/]+)$/);
@@ -289,6 +290,7 @@ export function createAdminRoutes(context) {
plan.updatedAt = nowIso();
plan.payload = { ...plan.payload, reviewNote: cleanText(body.reviewNote, 500), reviewedBy: user.displayName, reviewedAt: plan.updatedAt };
await database.saveAdmissionRecord(plan, logAction(db, user, body.status === 'approved' ? '审核通过招生计划' : '退回招生计划', plan.id));
await cache.invalidate('public');
return sendJson(response, 200, { ok: true, plan });
}
const actionMatch = pathname.match(/^\/api\/admin\/admissions\/([^/]+)\/(match|finalize|supplementary)$/);
@@ -350,7 +352,7 @@ export function createAdminRoutes(context) {
if (resource === 'results' && !hasPermission(user, 'results.read')) return sendError(response, 403, '当前账号不能导出成绩');
const requestUrl = new URL(request.url, `http://${request.headers.host || '127.0.0.1'}`);
const template = requestUrl.searchParams.get('template') === '1';
const rows = template ? [] : excelRowsForResource(db, user, resource, requestUrl.searchParams);
const rows = template && resource !== 'results' ? [] : excelRowsForResource(db, user, resource, requestUrl.searchParams);
const subtitle = user.adminLevel === 'super' ? '全部数据范围' : adminScopeLabel(db, user);
const buffer = Buffer.from(await buildWorkbook(resource, rows, { template, subtitle }));
return sendWorkbook(response, buffer, `${excelResourceNames[resource]}-${template ? '导入模板' : '导出'}-${new Date().toISOString().slice(0, 10)}.xlsx`);
@@ -1389,7 +1391,8 @@ export function createAdminRoutes(context) {
const view = examRegistrationView(db, item);
const profile = db.candidateProfiles.find(profileItem => profileItem.userId === item.userId);
const account = db.users.find(accountItem => accountItem.id === item.userId);
return { ...view, candidateName: profile?.name || account?.displayName || '', candidateNumber: account?.candidateNumber || item.registrationNumber || '', schoolName: profile?.school || '', className: db.classes.find(classItem => classItem.id === profile?.classId)?.name || profile?.grade || '' };
const specialty = resolveProfileSpecialty(profile || {});
return { ...view, candidateName: profile?.name || account?.displayName || '', candidateNumber: account?.candidateNumber || item.registrationNumber || '', schoolName: profile?.school || '', className: db.classes.find(classItem => classItem.id === profile?.classId)?.name || profile?.grade || '', specialtyCategory: specialty.category, specialtyType: specialty.type, specialtyLabel: specialtyLabel(specialty.category, specialty.type) || '普通生' };
}) : [];
return sendJson(response, 200, { ok: true, results, appeals, registrations, exams, resultCache: { enabled: cache.enabled, status: cache.status } });
}
@@ -1399,6 +1402,53 @@ export function createAdminRoutes(context) {
const result = await commitResultImport(db, user, body.rows);
return sendJson(response, 200, { ok: true, ...result });
}
if (request.method === 'POST' && pathname === '/api/admin/results/bulk') {
if (!requirePermission(user, response, '*')) return true;
const body = await readJson(request);
const exam = db.exams.find(item => item.id === cleanText(body.examId, 64) && !item.archivedAt);
const subject = exam?.subjects.find(item => item.id === cleanText(body.subjectId, 64));
if (!exam || !subject) return sendError(response, 400, '请选择有效且未归档的考试科目');
const sourceRows = [];
const seen = new Set();
for (const [index, row] of (Array.isArray(body.rows) ? body.rows : []).entries()) {
const registration = db.registrations.find(item => item.id === row.registrationId && item.examId === exam.id && item.status === 'approved' && item.subjectIds.includes(subject.id));
if (!registration || seen.has(registration.id)) return sendError(response, 400, `${index + 1} 条考生成绩无效或重复`);
seen.add(registration.id);
const account = db.users.find(item => item.id === registration.userId);
sourceRows.push({
__row: index + 3,
candidateNumber: account?.candidateNumber || registration.registrationNumber || '',
examCode: exam.code,
subjectName: subject.name,
score: row.score,
published: body.published === true
});
}
if (!sourceRows.length) return sendError(response, 400, '没有需要保存的成绩');
const result = await commitResultImport(db, user, sourceRows);
return sendJson(response, 200, { ok: true, published: body.published === true, ...result });
}
if (request.method === 'POST' && pathname === '/api/admin/feature-scores/bulk') {
if (!requirePermission(user, response, '*')) return true;
const body = await readJson(request);
const exam = db.exams.find(item => item.id === cleanText(body.examId, 64) && !item.archivedAt);
if (!exam) return sendError(response, 400, '请选择有效且未归档的考试');
const entries = [];
const seen = new Set();
for (const [index, row] of (Array.isArray(body.rows) ? body.rows : []).entries()) {
const registration = db.registrations.find(item => item.id === row.registrationId && item.examId === exam.id && item.status === 'approved');
const featureScore = Number(row.featureScore);
if (!registration || seen.has(registration.id)) return sendError(response, 400, `${index + 1} 条考生记录无效或重复`);
if (!Number.isFinite(featureScore) || featureScore < 0 || featureScore > 1000) return sendError(response, 400, `${index + 1} 条特征分必须在 0—1000 之间`);
seen.add(registration.id);
registration.featureScore = Number(featureScore.toFixed(2));
const profile = db.candidateProfiles.find(item => item.userId === registration.userId);
entries.push({ registration, log: logAction(db, user, '批量登记特征分', `${profile?.name || registration.userId} · ${exam.name} · ${registration.featureScore}`) });
}
if (!entries.length) return sendError(response, 400, '没有需要保存的特征分');
await database.updateFeatureScores(entries);
return sendJson(response, 200, { ok: true, count: entries.length });
}
const featureScoreMatch = pathname.match(/^\/api\/admin\/registrations\/([^/]+)\/feature-score$/);
if (request.method === 'PATCH' && featureScoreMatch) {
if (!requirePermission(user, response, '*')) return true;