修正
This commit is contained in:
1 parent
ae528b20e9
commit
1b6e860013
5 files changed
+35
-8
No files matched your search
@@ -61,8 +61,15 @@ export function createAdminViews(context) {
|
||||
'flow-design': () => adminFlowDesign(data.workflows), 'number-rules': () => adminNumberRules(data), admissions: () => adminAdmissions(data), security: () => accountSecurity(data)
|
||||
}[page]();
|
||||
app.innerHTML = portalShell('admin', page, content, ...meta[page]);
|
||||
if (page === 'admissions') mountPreferenceLedger(data);
|
||||
} catch (error) { renderError(error); }
|
||||
}
|
||||
|
||||
function mountPreferenceLedger(data) {
|
||||
const container = app.querySelector('.portal-content');
|
||||
if (!container || !data.preferences?.length) return;
|
||||
container.insertAdjacentHTML('beforeend', `<section class="panel data-panel admission-preference-ledger"><div class="panel-title"><div><h2>考生志愿只读台账</h2><p>仅超级管理员可查看;系统不提供任何管理员修改入口。</p></div><span>${data.preferences.length} 份</span></div><div class="table-scroll"><table><thead><tr><th>报名号 / 考生</th><th>考试</th><th>轮次</th><th>志愿顺序</th><th>本人提交时间</th></tr></thead><tbody>${data.preferences.map(item => `<tr><td><strong>${h(item.candidate.name)}</strong><small class="mono">${h(item.candidate.registrationNumber)}</small></td><td>${h(data.exams.find(exam => exam.id === item.examId)?.name || item.examId)}</td><td>第 ${h(item.payload.round || 1)} 轮</td><td>${item.choices.map((choice, index) => `${index + 1}. ${h(choice.schoolName)} · ${h(choice.categoryCode)}`).join('<br>')}</td><td>${formatDate(item.payload.submittedAt, true)}</td></tr>`).join('')}</tbody></table></div></section>`);
|
||||
}
|
||||
|
||||
function adminDashboard(data) {
|
||||
const m = data.metrics;
|
||||
@@ -139,7 +146,8 @@ export function createAdminViews(context) {
|
||||
const selected = data.settings.find(item => item.status !== 'completed') || data.settings[0];
|
||||
const pendingPlans = data.plans.filter(item => item.status === 'pending');
|
||||
const withdrawals = data.placements.filter(item => item.status === 'withdrawal_pending');
|
||||
const phaseLabels = { draft: '草稿', filling: '志愿填报中', closed: '填报已截止', matching: '投档中', school_review: '学校审核中', supplementary: '补录填报中', completed: '录取完成' };
|
||||
const phaseLabels = { draft: '草稿', filling: '志愿填报中', closed: '填报已截止' };
|
||||
if (selected && !phaseLabels[selected.status]) phaseLabels[selected.status] = { matching: '投档中', school_review: '学校审核中', supplementary: '补录填报中', completed: '录取完成' }[selected.status] || selected.status;
|
||||
return `<section class="admission-command-banner"><div><span>ADMISSION COMMAND</span><h2>中考招生录取控制台</h2><p>志愿内容仅超级管理员可见且不可代改;投档和录取状态变更均进入审计日志。</p></div><dl><div><dt>待审计划</dt><dd>${pendingPlans.length}</dd></div><div><dt>学校审核中</dt><dd>${data.placements.filter(item => item.status === 'school_review').length}</dd></div><div><dt>退档待审</dt><dd>${withdrawals.length}</dd></div><div><dt>正式录取</dt><dd>${data.placements.filter(item => item.status === 'final').length}</dd></div></dl></section><div class="admission-admin-grid"><section class="panel admission-settings-panel"><div class="panel-title"><div><h2>考试志愿设置</h2><p>不是所有考试都需要开启。</p></div></div><form data-form="admission-setting"><label><span>考试 *</span><select name="examId" required>${data.exams.map(exam => `<option value="${h(exam.id)}" ${exam.id === selected?.examId ? 'selected' : ''}>${h(exam.name)}</option>`).join('')}</select></label><div class="field-row"><label><span>填报开始</span><input name="preferenceStart" type="datetime-local" value="${h(selected?.payload?.preferenceStart?.slice(0,16))}"></label><label><span>填报结束</span><input name="preferenceEnd" type="datetime-local" value="${h(selected?.payload?.preferenceEnd?.slice(0,16))}"></label></div><div class="field-row"><label><span>当前阶段</span><select name="status">${Object.entries(phaseLabels).map(([value,label]) => `<option value="${value}" ${value === (selected?.status || 'draft') ? 'selected' : ''}>${label}</option>`).join('')}</select></label><label><span>最多志愿数</span><input name="maxChoices" type="number" min="1" max="20" value="${h(selected?.payload?.maxChoices || 5)}"></label></div><label><span>考生进度说明</span><input name="progress" value="${h(selected?.payload?.progress || '')}" placeholder="例如:招生学校正在审核投档名单"></label><label class="agreement"><input type="checkbox" name="enabled" ${selected?.payload?.enabled ? 'checked' : ''}><span>为本场考试启用志愿填报</span></label><label class="agreement"><input type="checkbox" name="autoPublish" ${selected?.payload?.autoPublish === false ? '' : 'checked'}><span>录取结束后自动脱敏公示</span></label><button class="solid-button" type="submit">保存志愿设置</button></form>${selected ? `<div class="admission-control-actions"><button class="solid-button" data-action="admission-match" data-exam-id="${h(selected.examId)}">按规则投档</button><button class="ghost-button" data-action="admission-finalize" data-exam-id="${h(selected.examId)}">结束录取并通知</button><button class="ghost-button" data-action="admission-supplementary" data-exam-id="${h(selected.examId)}">开启补录</button></div>` : ''}</section><section class="panel admission-account-panel"><div class="panel-title"><div><h2>招生学校账号</h2><p>账号只能查看本校投档名单和本校计划。</p></div><span>${data.schoolAccounts.length} 个</span></div><form data-form="admission-account"><label><span>招生学校 *</span><select name="schoolId" required>${data.schools.map(school => `<option value="${h(school.id)}">${h(school.name)}</option>`).join('')}</select></label><div class="field-row"><label><span>登录账号 *</span><input name="username" required></label><label><span>初始密码 *</span><input name="password" type="password" minlength="8" required></label></div><label><span>显示名称</span><input name="displayName" placeholder="学校招生办公室"></label><button class="solid-button" type="submit">创建招生学校账号</button></form></section></div><section class="panel admission-plan-console"><div class="panel-title"><div><h2>代上传招生计划</h2><p>每行格式:类别名称 | 计划人数 | 特长类型(普通生留空)。指标分配可由招生学校提交后审核。</p></div></div><form data-form="admission-plan"><div class="field-row"><label><span>考试 *</span><select name="examId">${data.exams.map(exam => `<option value="${h(exam.id)}">${h(exam.name)}</option>`).join('')}</select></label><label><span>招生学校 *</span><select name="schoolId">${data.schools.map(school => `<option value="${h(school.id)}">${h(school.name)}</option>`).join('')}</select></label></div><label><span>招生类别与计划 *</span><textarea name="categoriesText" rows="4" required placeholder="普通生 | 120 | 体育特长生 | 8 | 田径"></textarea></label><button class="solid-button" type="submit">代上传并审核通过</button></form></section><section class="panel data-panel"><div class="panel-title"><h2>招生计划审核</h2><span>${pendingPlans.length} 份待审</span></div><div class="table-scroll"><table><thead><tr><th>考试 / 学校</th><th>计划构成</th><th>提交人</th><th>状态</th><th>操作</th></tr></thead><tbody>${data.plans.map(plan => `<tr><td><strong>${h(plan.examName)}</strong><small
Line truncated
|
||||
}
|
||||
|
||||
|
||||
@@ -175,9 +175,11 @@ export function createAdminRoutes(context) {
|
||||
const exam = db.exams.find(item => item.id === settingMatch[1] && !item.archivedAt);
|
||||
if (!exam) return sendError(response, 404, '考试不存在或已经归档');
|
||||
const body = await readJson(request);
|
||||
const status = admissionPhases.has(body.status) ? body.status : 'draft';
|
||||
const now = nowIso();
|
||||
const setting = admissionSetting(db, exam.id) || { id: uid('admission_setting'), kind: 'setting', examId: exam.id, userId: user.id, schoolId: null, createdAt: now };
|
||||
const requestedStatus = admissionPhases.has(body.status) ? body.status : 'draft';
|
||||
const manualPhases = ['draft', 'filling', 'closed'];
|
||||
const status = setting.status && !manualPhases.includes(setting.status) ? setting.status : manualPhases.includes(requestedStatus) ? requestedStatus : (setting.status || 'draft');
|
||||
setting.status = status;
|
||||
setting.updatedAt = now;
|
||||
setting.payload = { ...setting.payload, enabled: body.enabled === true, preferenceStart: cleanText(body.preferenceStart, 35), preferenceEnd: cleanText(body.preferenceEnd, 35), maxChoices: Math.min(20, Math.max(1, Math.trunc(Number(body.maxChoices || 5)))), round: Math.max(1, Math.trunc(Number(body.round || setting.payload?.round || 1))), autoPublish: body.autoPublish !== false, progress: cleanText(body.progress, 200) || '等待志愿填报开始' };
|
||||
@@ -195,6 +197,7 @@ export function createAdminRoutes(context) {
|
||||
if (categories.some(item => item.indicatorAllocations.reduce((sum, allocation) => sum + allocation.quota, 0) > item.quota)) return sendError(response, 400, '指标分配合计不能超过类别计划人数');
|
||||
if (categories.some(item => item.indicatorAllocations.some(allocation => !db.schools.some(entry => entry.id === allocation.sourceSchoolId)))) return sendError(response, 400, '指标分配中包含无效的生源学校 ID');
|
||||
const existing = admissionRecords(db, 'plan', exam.id).find(item => item.schoolId === school.id);
|
||||
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: '超级管理员代上传并审核通过' } });
|
||||
@@ -239,6 +242,7 @@ export function createAdminRoutes(context) {
|
||||
}
|
||||
const body = await readJson(request);
|
||||
const now = nowIso();
|
||||
if (admissionRecords(db, 'placement', setting.examId).some(item => ['school_review', 'withdrawal_pending'].includes(item.status))) return sendError(response, 409, '仍有学校审核或退档申请待处理,暂不能开启补录');
|
||||
setting.status = 'supplementary'; setting.updatedAt = now; setting.payload = { ...setting.payload, round: Number(setting.payload.round || 1) + 1, preferenceStart: cleanText(body.preferenceStart, 35) || now, preferenceEnd: cleanText(body.preferenceEnd, 35), progress: '招生计划未满,补录志愿填报进行中' };
|
||||
await database.saveAdmissionRecord(setting, logAction(db, user, '开启补录', `${setting.examId} · 第 ${setting.payload.round} 轮`));
|
||||
return sendJson(response, 200, { ok: true, setting });
|
||||
|
||||
Reference in new issue
Block a user