通知修正

This commit is contained in:
2026-07-21 16:56:58 +08:00 Unverified
parent 48bef02904
commit fe5fcc9dc2
7 changed files with 119 additions and 21 deletions
+72 -5
View File
@@ -120,6 +120,56 @@ export function createAdminRoutes(context) {
})).filter(item => item.code && item.name && item.quota > 0);
}
function systemPublications(db) {
const examName = examId => db.exams.find(item => item.id === examId)?.name || '未知考试';
const schoolName = schoolId => db.schools.find(item => item.id === schoolId)?.name || '未知学校';
const view = (record, sourceType, category, title, publishedAt, summary) => ({
id: record.id,
sourceType,
category,
title,
summary,
author: '系统自动发布',
publishedAt,
visible: record.payload?.publicVisible !== false,
status: record.payload?.publicVisible === false ? 'hidden' : 'visible'
});
const plans = admissionRecords(db, 'plan').filter(item => item.status === 'approved').map(item => view(
item,
'plan',
'招生计划',
`${examName(item.examId)} · ${schoolName(item.schoolId)}招生计划公示`,
item.payload?.reviewedAt || item.updatedAt,
'审核通过后由系统生成,当前页面仅控制是否在公开通知目录显示。'
));
const qualifications = admissionRecords(db, 'qualification_publication').filter(item => item.status === 'published').map(item => view(
item,
'qualification',
'指标资格',
`${examName(item.examId)} · ${schoolName(item.schoolId)}指标分配资格公示`,
item.payload?.publishedAt || item.updatedAt,
'资格确认完成后由系统生成,内容随资格确认结果更新。'
));
const admissions = admissionRecords(db, 'setting').filter(item => item.status === 'completed' && item.payload?.autoPublish !== false).map(item => view(
item,
'admission',
'录取名单',
`${examName(item.examId)}最终录取名单`,
item.payload?.completedAt || item.updatedAt,
'录取结束后由系统生成,内容取自最终录取结果。'
));
const cutoffs = admissionRecords(db, 'cutoff_publication').filter(item => item.status === 'published' && admissionSetting(db, item.examId)?.payload?.autoPublish !== false).map(item => view(
item,
'cutoff',
'录取分数线',
`${examName(item.examId)}录取分数线`,
item.payload?.publishedAt || item.updatedAt,
'录取结束后由系统生成,内容取自各招生类别最低录取分数。'
));
return [...plans, ...qualifications, ...admissions, ...cutoffs]
.sort((left, right) => new Date(right.publishedAt) - new Date(left.publishedAt));
}
async function handleAdmin(request, response, pathname) {
if (!pathname.startsWith('/api/admin/')) return false;
const user = await requireUser(request, response, 'admin');
@@ -176,7 +226,7 @@ export function createAdminRoutes(context) {
if (status.complete) {
const published = admissionRecords(nextDb, 'qualification_publication', setting.examId).find(item => item.schoolId === user.schoolId);
const publication = published || { id: uid('qualification_publication'), kind: 'qualification_publication', examId: setting.examId, userId: user.id, schoolId: user.schoolId, createdAt: now };
Object.assign(publication, { status: 'published', updatedAt: now, payload: { publishedAt: now, rows: status.rows } });
Object.assign(publication, { status: 'published', updatedAt: now, payload: { ...publication.payload, publishedAt: now, rows: status.rows } });
records.push(publication);
}
await database.saveAdmissionRecords(records, logAction(db, user, '批量确认指标分配资格', `${qualifications.length} 人 · ${body.eligible ? '有资格' : '无资格'}${status.complete ? ' · 全校已自动公示' : ''}`));
@@ -203,7 +253,7 @@ export function createAdminRoutes(context) {
if (status.complete) {
const published = admissionRecords(nextDb, 'qualification_publication', setting.examId).find(item => item.schoolId === user.schoolId);
const publication = published || { id: uid('qualification_publication'), kind: 'qualification_publication', examId: setting.examId, userId: user.id, schoolId: user.schoolId, createdAt: now };
Object.assign(publication, { status: 'published', updatedAt: now, payload: { publishedAt: now, rows: status.rows } });
Object.assign(publication, { status: 'published', updatedAt: now, payload: { ...publication.payload, publishedAt: now, rows: status.rows } });
records.push(publication);
}
await database.saveAdmissionRecords(records, logAction(db, user, '确认指标分配资格', `${account.candidateNumber} · ${body.eligible ? '有资格' : '无资格'}${status.complete ? ' · 全校已自动公示' : ''}`));
@@ -301,7 +351,7 @@ export function createAdminRoutes(context) {
if (admissionRecords(db, 'placement', exam.id).some(item => item.schoolId === school.id && item.status !== 'withdrawn')) return sendError(response, 409, '已经产生投档记录,不能再修改该校本轮招生计划');
const now = nowIso();
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: '超级管理员代上传并审核通过' } });
Object.assign(plan, { userId: user.id, status: 'approved', updatedAt: now, payload: { ...plan.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 });
@@ -344,7 +394,7 @@ export function createAdminRoutes(context) {
const cutoffRows = admissionCutoffRows(completedDb, setting.examId);
const existingCutoff = admissionRecords(db, 'cutoff_publication', setting.examId)[0];
const cutoffPublication = existingCutoff || { id: uid('cutoff_publication'), kind: 'cutoff_publication', examId: setting.examId, userId: user.id, schoolId: null, createdAt: now };
Object.assign(cutoffPublication, { status: 'published', updatedAt: now, payload: { publishedAt: now, rows: cutoffRows } });
Object.assign(cutoffPublication, { status: 'published', updatedAt: now, payload: { ...cutoffPublication.payload, publishedAt: now, rows: cutoffRows } });
await database.saveAdmissionRecords([setting, ...admitted, ...notifications, cutoffPublication], logAction(db, user, '结束录取并发布结果与分数线', `${setting.examId} · ${admitted.length}`));
return sendJson(response, 200, { ok: true, admittedCount: admitted.length, publicRows: publicAdmissionRows(completedDb, setting.examId), cutoffRows });
}
@@ -1327,7 +1377,7 @@ export function createAdminRoutes(context) {
const notices = db.notices
.sort((a, b) => new Date(b.publishAt || b.createdAt) - new Date(a.publishAt || a.createdAt))
.map(noticeForClient);
return sendJson(response, 200, { ok: true, notices });
return sendJson(response, 200, { ok: true, notices, publications: systemPublications(db) });
}
if (request.method === 'POST' && pathname === '/api/admin/notices') {
if (!requirePermission(user, response, '*')) return true;
@@ -1339,8 +1389,24 @@ export function createAdminRoutes(context) {
const notice = { id: uid('notice'), title, summary: cleanText(body.summary, 260) || contentText.slice(0, 80), content, category: cleanText(body.category, 30) || '通知公告', pinned: Boolean(body.pinned), status: body.status === 'draft' ? 'draft' : 'published', publishAt: body.status === 'draft' ? null : nowIso(), createdAt: nowIso(), author: user.displayName };
const log = logAction(db, user, notice.status === 'published' ? '发布通知' : '保存通知草稿', notice.title);
await database.createNotice(notice, log);
await cache.invalidate('public');
return sendJson(response, 201, { ok: true, notice: noticeForClient(notice) });
}
const publicationMatch = pathname.match(/^\/api\/admin\/publications\/(plan|qualification|admission|cutoff)\/([^/]+)$/);
if (request.method === 'PATCH' && publicationMatch) {
if (!requirePermission(user, response, '*')) return true;
const kindByType = { plan: 'plan', qualification: 'qualification_publication', admission: 'setting', cutoff: 'cutoff_publication' };
const sourceType = publicationMatch[1];
const record = admissionRecords(db, kindByType[sourceType]).find(item => item.id === publicationMatch[2]);
if (!record) return sendError(response, 404, '系统公示不存在');
const body = await readJson(request);
if (typeof body.visible !== 'boolean') return sendError(response, 400, '请明确设置是否显示');
record.payload = { ...record.payload, publicVisible: body.visible };
record.updatedAt = nowIso();
await database.saveAdmissionRecord(record, logAction(db, user, body.visible ? '显示系统公示' : '隐藏系统公示', `${sourceType} · ${record.id}`));
await cache.invalidate('public');
return sendJson(response, 200, { ok: true, publication: systemPublications({ ...db, admissionRecords: db.admissionRecords.map(item => item.id === record.id ? record : item) }).find(item => item.id === record.id && item.sourceType === sourceType) });
}
const noticeMatch = pathname.match(/^\/api\/admin\/notices\/([^/]+)$/);
if (request.method === 'PATCH' && noticeMatch) {
if (!requirePermission(user, response, '*')) return true;
@@ -1360,6 +1426,7 @@ export function createAdminRoutes(context) {
}
const log = logAction(db, user, '更新通知', `${notice.title} · ${notice.status}`);
await database.updateNotice(notice, log);
await cache.invalidate('public');
return sendJson(response, 200, { ok: true, notice: noticeForClient(notice) });
}
if (request.method === 'POST' && pathname === '/api/admin/results/cache/refresh') {