import { formatRegionAddress } from './region-select.mjs'; import { admissionCategoriesEditor } from './admission-plan-editor.mjs'; import { specialtyLabel } from '../data/specialty-types.mjs'; export const numberSegmentMeta = { year: ['年份', '4 位考试年份'], school_code: ['学校代码', '使用学校档案代码'], gender: ['考生性别', '男 M / 女 F / 未知 X'], sequence: ['流水号', '按规则前缀连续编号'], literal: ['固定值', '自定义固定字母或数字'] }; export function createAdminViews(context) { const { state, app, h, formatDate, dateRange, badge, money, passPolicyText, statusLabels, icons, api, renderError, requireLogin, emptyState, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity } = context; async function renderAdmin(page) { if (state.user?.role !== 'admin') return requireLogin(); app.classList.add('admin-readable'); const meta = { dashboard: ['考务工作台', '掌握当前报名、审核和发布任务。'], candidates: ['考生资料审核', '核验考生实名、学籍与联系信息。'], schools: ['学校管理', '创建和维护学校档案,控制学校在考生公开入口中的可选状态。'], registrations: ['考试报名审核', '确认考生所报考试、科目与缴费状态。'], exams: ['考试与科目', '创建考试、配置报名时间与考试科目。'], payments: [state.user.adminLevel === 'class' ? '考生缴费确认' : '缴费名单', '查看、导出并修改当前管理范围内考生的考试缴费状态。'], notices: ['通知发布', '编辑通知草稿,并统一控制手动通知与系统公示是否公开显示。'], admit: [state.user.adminLevel === 'super' ? '准考证编排' : state.user.adminLevel === 'school' ? '校内准考证' : '本班准考证', state.user.adminLevel === 'super' ? '按整场考试预检容量,并批量分配固定考点、分科考场与准考证号。' : '按当前管理范围批量下载准考证,并导出逐科准考证信息。'], results: [state.user.adminLevel === 'super' ? '成绩管理中心' : '成绩分析', state.user.adminLevel === 'super' ? '按考试录入、预览导入、发布并跟踪各科达线情况。' : '按考试、科目和发布状态分析管理范围内成绩。'], admins: ['分级管理员', '同一级可以配置多名管理员,并分别绑定学校或班级。'], centers: ['考务场所档案', state.user.adminLevel === 'school' ? '查看本校考点与结构化考场,所有变更提交后进入审批。' : '管理各校考点、考场容量与变更审批台账。'], organization: ['本校组织与权限', '维护本校班级,并为每个班级配置一个或多个班级管理员。'], 'account-batches': ['批量报名号申领', '按班级填写申领人数;审批通过后系统生成固定报名号和初始密码。'], flows: [state.user.adminLevel === 'super' ? '流程监督' : '流程中心', state.user.adminLevel === 'super' ? '查看全部流程,监督转交、修改和退回节点。' : '处理分配给你的流程,并可转交给本校同级管理员。'], 'flow-design': ['流程设计', '配置考生信息、报名审核、考点考场变更与批量建号的审批步骤。'], 'number-rules': ['报名号规则', '设计审批通过后生成的新账户号码组成。'], admissions: ['招生录取', '设置志愿窗口、审核招生计划,并按分数优先、遵循志愿执行投档、退档审核和补录。'], 'indicator-qualifications': ['指标分配资格确认', '由生源校逐人确认;本校全部考生确认后,系统自动发布资格公示。'], security: ['账户安全', '使用当前密码设置新的登录密码。'] }; const allowedPages = adminNavForUser().map(item => item[0]); if (!meta[page] || !allowedPages.includes(page)) page = 'dashboard'; app.innerHTML = portalShell('admin', page, loadingPanel(), ...meta[page]); try { const endpoint = page === 'admit' ? 'admission-arrangements' : page === 'flows' ? 'workflow-instances' : page === 'flow-design' ? 'workflows' : page === 'account-batches' ? 'candidate-account-batches' : page === 'organization' ? 'school-organization' : page; const data = page === 'security' ? await api('/api/auth/totp') : await api(`/api/admin/${endpoint}`); state.pageData = data; const content = { dashboard: () => adminDashboard(data), candidates: () => adminCandidates(data), registrations: () => adminRegistrations(data.registrations), payments: () => adminPayments(data), exams: () => adminExams(data.exams), notices: () => adminNotices(data.notices, data.publications), admit: () => adminAdmit(data), results: () => adminResults(data), schools: () => adminSchoolsV2(data), admins: () => adminUsers(data), centers: () => adminCenters(data), flows: () => adminFlows(data), organization: () => adminSchoolOrganization(data), 'account-batches': () => adminAccountBatches(data), 'flow-design': () => adminFlowDesign(data.workflows), 'number-rules': () => adminNumberRules(data), admissions: () => adminAdmissionsV2(data), 'indicator-qualifications': () => adminIndicatorQualifications(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', `

考生志愿只读台账

仅超级管理员可查看;系统不提供任何管理员修改入口。

${data.preferences.length} 份
${data.preferences.map(item => ``).join('')}
报名号 / 考生考试轮次志愿顺序本人提交时间
${h(item.candidate.name)}${h(item.candidate.registrationNumber)}${h(data.exams.find(exam => exam.id === item.examId)?.name || item.examId)}第 ${h(item.payload.round || 1)} 轮${item.choices.map((choice, index) => `${index + 1}. ${h(choice.schoolName)} · ${h(choice.categoryCode)}`).join('
')}
${formatDate(item.payload.submittedAt, true)}
`); } function adminDashboard(data) { const m = data.metrics; const canFlow = true; return `
${statusLabels[state.user.adminLevel]}
${h(data.scopeLabel)}所有指标均已按当前管理员的数据范围过滤
${icons.users}
范围内考生${m.candidates}${m.pendingCandidates} 人待审核
${icons.check}
考试报名${m.registrations}${m.pendingRegistrations} 条待审核
${icons.ticket}
待确认缴费${m.pendingPayments ?? 0}可按当前管理范围办理
${icons.exam}
待处理流程${m.pendingFlows ?? 0}进入流程中心办理
${icons.chart}
已发布考试${m.publishedExams}全平台考试计划

${canFlow ? '当前工作入口' : '本班查询入口'}

${h(data.scopeLabel)}
${canFlow ? `` : ''}

最近操作

系统审计日志
${data.logs.map(log => `
${h((log.actorName || '系').slice(0,1))}

${h(log.actorName || '系统')} · ${h(log.action)}${h(log.detail)}

`).join('') || '

当前账号暂无操作记录

'}
`; } function excelToolbar(resource, { importable = true, template = true, label = '数据' } = {}) { return `
${h(label)} Excel使用系统模板可获得逐行校验
${template ? `` : ''}${importable ? `` : ''}
`; } function adminSchools(data) { const schools = data.schools || []; const activeCount = schools.filter(item => item.active).length; return `
学校总数${schools.length}
启用学校${activeCount}
在册考生${schools.reduce((sum, item) => sum + item.candidateCount, 0)}
${schools.map(item => ``).join('') || ''}
学校学校代码地址班级管理员考生考点状态操作
${h(item.name)}${h(item.id)}${h(item.code)}${h(item.address || '未填写')}${item.classCount}${item.adminCount}${item.candidateCount}${item.centerCount}${badge(item.active ? 'approved' : 'closed')}
还没有学校,请先创建学校档案。
`; } function adminSchoolsV2(data) { const schools = data.schools || []; const sourceCount = schools.filter(item => item.active && item.isSourceSchool).length; const admissionCount = schools.filter(item => item.active && item.isAdmissionSchool).length; const roles = item => `${item.isSourceSchool ? '生源校' : ''}${item.isAdmissionSchool ? '招生校' : ''}`; return `
学校总数${schools.length}
启用生源校${sourceCount}
启用招生校${admissionCount}
在册考生${schools.reduce((sum, item) => sum + item.candidateCount, 0)}
${schools.map(item => ``).join('') || ''}
学校 / 代码学校类型地址班级管理员考生考点状态操作
${h(item.name)}${h(item.code)}
${roles(item)}
${h(item.address || '未填写')}${item.classCount}${item.adminCount}${item.candidateCount}${item.centerCount}${badge(item.active ? 'approved' : 'closed')}
还没有学校,请先创建学校档案。
`; } function adminSchoolOrganization(data) { const classes = data.classes || []; const activeAdmins = classes.reduce((sum, item) => sum + item.admins.filter(admin => admin.active).length, 0); return `
SCHOOL ORGANIZATION

${h(data.school?.name)}

班级决定考生、报名与成绩的可见范围;一个班级可以配置多名班级管理员。

班级
${classes.length}
班级管理员
${activeAdmins}
在册考生
${classes.reduce((sum, item) => sum + item.candidateCount, 0)}
${excelToolbar('classes', { label: '班级台账' })}${excelToolbar('class_admins', { label: '班级管理员' })}
${classes.map(item => `
${h(item.grade)}

${h(item.name)}

${badge(item.active ? 'approved' : 'closed')}
${item.candidateCount}在册考生${item.admins.length}管理员
班级管理员
${item.admins.map(admin => ``).join('') || '

尚未配置班级管理员

'}
`).join('') || emptyState('还没有班级', '点击“新增班级”建立本校组织范围。')}
`; } function adminCandidates(data) { const candidates = data.candidates || []; const canReview = state.user.adminLevel === 'super' || state.permissions?.includes('candidates.review'); const classById = new Map((data.classes || []).map(item => [item.id, item])); const optionList = getter => [...new Set(candidates.map(getter).filter(Boolean))] .sort((left, right) => left.localeCompare(right, 'zh-CN')) .map(value => ``).join(''); const archiveConsole = state.user.adminLevel === 'school' ? (() => { const classes = data.classes || []; const grades = [...new Set(classes.map(item => item.grade))]; return `
SCHOOL ACCOUNT ARCHIVE

按班级或年级归档账户

归档只冻结登录,不删除考生、报名、准考证、成绩和审计记录;可随时按相同范围恢复。

`; })() : ''; const rows = candidates.map(item => { const schoolClass = classById.get(item.classId); const grade = schoolClass?.grade || ''; const className = schoolClass?.name || item.grade || ''; const exams = item.registrations || []; const canSelect = canReview && item.profileCompleted && !item.accountArchived && item.status === 'pending' && item.workflow?.status === 'pending' && (state.user.adminLevel === 'super' || item.workflow?.assignee?.id === state.user.id); const examNames = exams.map(registration => registration.exam?.name).filter(Boolean); return `
${h(item.name.slice(0,1))}
${h(item.name)}${h(item.candidateNumber || '待分配')}
${h(item.idNumberMasked)}${h(item.school || '未填写')}${h([grade, className].filter(Boolean).join(' · '))}
${exams.length ? `${h(examNames.slice(0, 2).join('、'))}${exams.length > 2 ? ` 等 ${exams.length} 场` : ''}${h(exams.flatMap(registration => registration.subjects || []).map(subject => subject.name).slice(0, 5).join('、') || '尚未选择科目')}` : '暂无考试报名'}
${item.accountArchived ? '已归档' : item.mustChangePassword ? '待首次改密' : item.profileCompleted ? '正常' : '待补全资料'}${item.accountArchived ? `${formatDate(item.archivedAt, true)} · ${h(item.archivedByName || '校方')}` : h(item.workflow?.assignee?.displayName || '')}${item.profileCompleted ? badge(item.status) : '未完成'}
${state.user.adminLevel === 'super' ? `` : ''}
`; }).join(''); const bulkToolbar = canReview ? `
已选择 0 名当前可处理考生
` : ''; const examOptions = [...new Set(candidates.flatMap(item => (item.registrations || []).map(registration => registration.exam?.name).filter(Boolean)))] .sort((left, right) => left.localeCompare(right, 'zh-CN')) .map(value => ``).join(''); const filters = `
`; return `${archiveConsole}${excelToolbar('candidates', { importable: true, label: '考生资料' })}
${bulkToolbar}
${filters}
${rows || ''}
报名号 / 考生证件号码学校 / 年级 / 班级关联考试 / 科目账户状态资料状态操作
当前范围暂无考生
`; } function adminRegistrations(registrations) { const isSuper = state.user.adminLevel === 'super'; const canReview = state.user.adminLevel === 'super' || state.permissions?.includes('registrations.review'); const optionList = (getter) => [...new Set(registrations.map(getter).filter(Boolean))] .sort((left, right) => left.localeCompare(right, 'zh-CN')) .map(value => ``).join(''); const rows = (items, selectable = false) => items.map(reg => { const canSelect = selectable && reg.status === 'pending' && reg.workflow?.status === 'pending' && (isSuper || reg.workflow?.assignee?.id === state.user.id); return `${selectable ? `` : ''}
${h((reg.candidate?.name || '?').slice(0,1))}
${h(reg.candidate?.name)}${h([reg.schoolName, reg.gradeName, reg.className].filter(Boolean).join(' · '))}
${h(reg.exam.name)}
${reg.subjects.map(subject => `${h(subject.name)}`).join('') || '未选择科目'}
${h(reg.subjects.length)} 科 · ${money(reg.amountDue || 0)}
${h(reg.registrationNumber || '待同步账户号码')}各次考试保持一致${h(reg.workflow?.currentStepDetail?.name || '流程已结束')}${h(reg.workflow?.assignee?.displayName || '')}${badge(reg.paymentStatus)}${badge(reg.exam.archivedAt ? 'archived' : reg.status)}${reg.exam.archivedAt ? '只读封存' : ``}`; }).join(''); const table = (items, id, selectable = false) => `
${selectable ? '' : ''}${rows(items, selectable) || ``}
考生考试 / 科目账户报名号当前流程缴费状态操作
当前范围暂无报名记录
`; const current = registrations.filter(reg => !reg.exam.archivedAt); const archived = registrations.filter(reg => reg.exam.archivedAt); const subjectOptions = [...new Set(registrations.flatMap(item => item.subjects.map(subject => subject.name)))].sort((left, right) => left.localeCompare(right, 'zh-CN')).map(value => ``).join(''); const bulkToolbar = canReview ? `
已选择 0 条当前可处理报名
` : ''; return `
${bulkToolbar}
${table(current, 'registrationTable', Boolean(canReview))}
${archived.length ? `
归档考试报名记录${archived.length} 条 · 流程与报名信息已冻结${archived.length}${table(archived, 'archivedRegistrationTable')}
` : ''}`; } function adminPayments(data) { const registrations = data.registrations || []; const paid = registrations.filter(item => item.paymentStatus === 'paid'); const unpaid = registrations.filter(item => item.paymentStatus === 'unpaid'); const totalDue = registrations.reduce((sum, item) => sum + Number(item.amountDue || 0), 0); const totalPaid = paid.reduce((sum, item) => sum + Number(item.amountDue || 0), 0); const optionList = (getter) => [...new Set(registrations.map(getter).filter(Boolean))] .sort((left, right) => left.localeCompare(right, 'zh-CN')) .map(value => ``).join(''); const rows = registrations.map(item => `${data.canUpdatePayment ? `` : ''}
${h((item.candidate?.name || '?').slice(0, 1))}
${h(item.candidate?.name || '未知考生')}${h(item.registrationNumber || '')}
${h(item.schoolName)}${h([item.gradeName, item.className].filter(Boolean).join(' · '))}${h(item.exam?.name || '')}
${item.subjects.map(subject => `${h(subject.name)}`).join('')}
${money(item.amountDue || 0)}${badge(item.paymentStatus)}${item.paidAt ? `${formatDate(item.paidAt, true)}${h(item.paidByName || '管理员')}` : ''}${data.canUpdatePayment && !item.exam?.archivedAt ? `` : item.exam?.archivedAt ? '只读封存' : ''}`).join(''); const filters = `
`; const bulkToolbar = data.canUpdatePayment ? `
已选择 0 条缴费记录
` : ''; return `${excelToolbar('payments', { importable: false, template: false, label: '缴费名单' })}
报名人数${registrations.length}
待确认${unpaid.length}
已缴费${paid.length}
应缴合计${money(totalDue)}
已缴合计${money(totalPaid)}
${bulkToolbar}
${filters}
${data.canUpdatePayment ? '' : ''}${rows || ``}
考生 / 报名号学校 / 年级 / 班级考试 / 科目应缴金额缴费状态确认记录操作
当前范围暂无已审核通过的报名
`; } function adminExams(exams) { const active = exams.filter(exam => !exam.archivedAt); const archived = exams.filter(exam => exam.archivedAt).sort((left, right) => new Date(right.archivedAt) - new Date(left.archivedAt)); const card = exam => `
${h(exam.code)}${badge(exam.archivedAt ? 'archived' : exam.status)}

${h(exam.name)}

${h(exam.description)}

考试总分${h(exam.totalScore)}合格规则${h(passPolicyText(exam))}
报名时间
${dateRange(exam.registrationStart, exam.registrationEnd)}
考试时间
${dateRange(exam.examStart, exam.examEnd)}
考点
${h(exam.location)}
${exam.subjects.map(subject => `${h(subject.name)}${h(subject.date)} ${h(subject.start)} · 满分 ${h(subject.fullScore)} · ${subject.passRule === 'none' ? '不设单科线' : subject.passRule === 'rank_percent' ? `本科排名前 ${h(subject.passValue)}%` : `固定 ${h(subject.passScore)} 分`}`).join('') || '科目待配置'}
`; const archiveShelf = archived.length ? `
历史考试归档${archived.length} 场 · 成绩永久锁定,点击按需查阅${archived.length}
${archived.map(card).join('')}
` : ''; return `
${active.map(card).join('') || emptyState('没有进行中的考试', '新建考试,或在下方查阅已归档历史。')}
${archiveShelf}`; } function adminAdmissions(data) { 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: '填报已截止' }; if (selected && !phaseLabels[selected.status]) phaseLabels[selected.status] = { matching: '投档中', school_review: '学校审核中', supplementary: '补录填报中', completed: '录取完成' }[selected.status] || selected.status; return `
ADMISSION COMMAND

中考招生录取控制台

志愿内容仅超级管理员可见且不可代改;投档和录取状态变更均进入审计日志。

待审计划
${pendingPlans.length}
学校审核中
${data.placements.filter(item => item.status === 'school_review').length}
退档待审
${withdrawals.length}
正式录取
${data.placements.filter(item => item.status === 'final').length}

考试志愿设置

不是所有考试都需要开启。

${selected ? `
` : ''}

招生学校账号

账号只能查看本校投档名单和本校计划。

${data.schoolAccounts.length} 个

代上传招生计划

每行格式:类别名称 | 计划人数 | 特长类型(普通生留空)。指标分配可由招生学校提交后审核。

招生计划审核

${pendingPlans.length} 份待审
${data.plans.map(plan => ``).join('') || ''}
考试 / 学校计划构成提交人状态操作
${h(plan.examName)}${h(plan.schoolName)}${plan.payload.categories.map(category => `${h(category.name)} ${h(category.quota)} 人${category.specialtyType ? ` · ${h(category.specialtyType)}` : ''}`).join('
')}
${h(plan.payload.submittedBy || '超级管理员')}${badge(plan.status)}${plan.status === 'pending' ? `` : h(plan.payload.reviewNote || '')}
暂无招生计划

投档与退档监督

超级管理员可见,任何管理员均不能修改考生志愿
${data.placements.map(item => ``).join('') || ''}
考生成绩 / 志愿投档学校类别状态退档审核
${h(item.candidate.name)}${h(item.candidate.registrationNumber)}${h(item.payload.totalScore)} 分 · 第 ${h(item.payload.preferenceOrder)} 志愿${h(item.schoolName)}${h(item.payload.categoryName)}${badge(item.status)}${item.status === 'withdrawal_pending' ? `${h(item.payload.withdrawalReason)}` : '—'}
尚未产生投档记录
`; } function adminAdmissionsV2(data) { 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 settings = `

考试志愿设置

成绩发布后按场次开放,并持续向考生展示录取进度。

${selected ? `
` : ''}
`; const accountRows = data.schoolAccounts.map(account => `
${h((account.displayName || '招').slice(0, 1))}
${h(account.displayName)}${h(account.id)}
${h(account.username)}${h(account.schoolName || '未绑定')}${h(account.schoolCode || '')}${account.active ? '已启用' : '已停用'}${formatDate(account.createdAt, true)}
`).join(''); const accounts = `

招生学校账户管理

创建并维护各招生学校登录账户;停用会立即结束该账户现有会话。

${data.schoolAccounts.filter(item => item.active).length} 个启用 / 共 ${data.schoolAccounts.length} 个
已建账户保留历史记录,不提供物理删除
${accountRows || ''}
账户登录账号绑定招生学校状态创建时间账户操作
还没有招生学校账户
`; const planForm = `

代招生校上传计划

每个类别独立设置人数、特长资格和生源校指标,保存后直接审核通过。

${admissionCategoriesEditor(h, data.sourceSchools)}
`; const plans = `

招生计划审核

核对类别资格、计划总数和指标分配。

${pendingPlans.length} 份待审
${data.plans.map(plan => ``).join('') || ''}
考试 / 学校计划构成指标分配状态操作
${h(plan.examName)}${h(plan.schoolName)}${plan.payload.categories.map(category => `${h(category.name)} ${h(category.quota)} 人${h(specialtyLabel(category.specialtyCategory, category.specialtyType) || '普通 / 政策类')}`).join('')}${plan.payload.categories.flatMap(category => (category.indicatorAllocations || []).map(allocation => `${h(data.sourceSchools.find(item => item.id === allocation.sourceSchoolId)?.name || allocation.sourceSchoolId)} ${h(allocation.quota)} 人`)).join('
') || '无定向指标'}
${badge(plan.status)}${plan.status === 'pending' ? `` : h(plan.payload.reviewNote || '')}
暂无招生计划
`; const placements = `

投档与退档监督

志愿只读,管理员均无修改入口
${data.placements.map(item => ``).join('') || ''}
考生成绩 / 志愿投档学校类别状态退档审核
${h(item.candidate.name)}${h(item.candidate.registrationNumber)}${h(item.candidate.specialtyLabel || '普通生')}${h(item.payload.totalScore)} 分 · 特征分 ${h(item.payload.featureScore || 0)} · 第 ${h(item.payload.preferenceOrder)} 志愿${h(item.schoolName)}${h(item.payload.categoryName)}${badge(item.status)}${item.status === 'withdrawal_pending' ? `${h(item.payload.withdrawalReason)}` : '—'}
尚未产生投档记录
`; return `
ADMISSION COMMAND

中考招生录取控制台

学校代码、资格类别、招生计划和指标名额在一条可审计链路中完成。

待审计划
${pendingPlans.length}
学校审核中
${data.placements.filter(item => item.status === 'school_review').length}
退档待审
${withdrawals.length}
正式录取
${data.placements.filter(item => item.status === 'final').length}
${settings}
${accounts}${planForm}${plans}${placements}`; } function adminIndicatorQualifications(data) { if (!data.exams?.length) return emptyState('暂无需要确认的考试', '超级管理员启用中考志愿填报后,本校资格名单会出现在这里。'); return `
SOURCE SCHOOL CERTIFICATION

${h(data.school?.name)}资格确认簿

先按姓名、报名号、确认状态或特长类型筛选,再逐人确认或多选批量设置。每场考试全部确认后立即自动公示。

${data.exams.map((item, index) => { const status = item.qualificationStatus; const tableId = `qualificationTable-${index}`; const specialties = [...new Set(status.rows.map(row => row.specialtyLabel).filter(Boolean))].sort((left, right) => left.localeCompare(right, 'zh-CN')); const filters = `
`; const bulk = `
已选 0 人
`; const rows = status.rows.map(row => `${h(row.name)}${h(row.registrationNumber)}${h(row.specialtyLabel)}${row.confirmedAt ? formatDate(row.confirmedAt, true) : '待确认'}`).join(''); return `
${h(item.exam.code)}

${h(item.exam.name)}

${h(status.confirmed)} / ${h(status.total)} 已确认
${status.complete ? '
✓ 本校资格已全部确认,公开公示已自动发布
' : '
未全部确认前不会公开,请逐项核对。
'}${filters}${bulk}
${rows || ''}
选择报名号 / 姓名特长类型指标分配资格确认时间保存
本校暂无资料已完善的在册考生
`; }).join('')}`; } function adminNotices(notices, publications = []) { const noticeRows = notices.map(notice => `${h(notice.title)}${h(notice.summary)}手动通知${h(notice.category)}${h(notice.author)}${formatDate(notice.publishAt || notice.createdAt,true)}${notice.pinned ? '首页置顶' : notice.status === 'published' ? '通知目录' : '尚未展示'}${badge(notice.status)}
${notice.status === 'draft' ? `` : ''}
`).join(''); const publicationRows = publications.map(item => `${h(item.title)}${h(item.summary)}自动公示${h(item.category)}${h(item.author)}${formatDate(item.publishedAt,true)}通知目录${badge(item.status)}`).join(''); return `

自动公示的内容由招生录取流程生成,这里只控制是否公开显示。

${noticeRows}${publicationRows || (!noticeRows ? '' : '')}
标题来源 / 分类发布人发布时间展示位置状态操作
暂无通知或系统公示
`; } function adminAdmit(data) { const approved = data.registrations || []; const selectedExam = data.exams.find(exam => !exam.archivedAt && exam.approvedCount > 0) || data.exams.find(exam => !exam.archivedAt) || data.exams[0]; const selectedPlan = selectedExam?.plan; const scopeName = code => data.mixingScopes.find(item => item.code === code)?.name || code; const form = data.canArrange ? (selectedExam ? `

整场编排控制台

先预检容量与档案,再以一个事务生成或替换整套编排。

${data.centers.length} 个启用考点 · ${data.centers.reduce((sum, item) => sum + item.capacity, 0)} 个常规席位
` : emptyState('还没有可编排考试', '请先创建考试并完成报名审核。')) : ''; const ruleCards = data.canArrange ? `
${data.rules.map(rule => `
号码规则

${h(rule.name)}

${h(rule.description)}

${h(rule.example)}
`).join('')}
` : ''; const planCards = data.canArrange && data.plans.length ? `
${data.plans.map(plan => `
${h(scopeName(plan.mixingScope))}

${h(plan.examName)}

${plan.candidateCount}名考生${plan.centerCount}个考点${plan.subjectCombinationCount}种科目组合

${h(plan.ruleName)} · 本校考点率 ${h(plan.sameSchoolCenterRate)}%

${plan.warnings.length ? `` : '

容量、档案与时间冲突检查通过

'}
`).join('')}
` : ''; const exportExam = data.exams.find(exam => exam.arrangedCount > 0) || data.exams[0]; const exportPanel = exportExam ? `

${data.canArrange ? '准考证与考务材料' : h(data.scopeLabel)}

批量准考证为 A4 横向打印文件;“准考证信息”是逐考生、逐科目的 Excel 台账。

${approved.filter(item => item.admitCard).length} 份范围内准考证
${data.canExportCenterMaterials ? '' : ''}
` : emptyState('当前范围还没有考试报名', '待报名审核通过并完成整场编排后,可在这里批量下载。'); const rows = approved.map(reg => { const assignments = new Map((reg.admitCard?.assignments || []).map(item => [item.subjectId, item])); const subjects = reg.subjects.map(subject => { const assignment = assignments.get(subject.id); return `${h(subject.name)}${assignment ? `考试考场序号 ${h(assignment.examRoomCode)} · ${h(assignment.roomName || assignment.room)}(场地 ${h(assignment.roomCode || '—')})· ${h(assignment.building || '楼栋待定')} ${h(assignment.floor || '')} · 座位 ${h(assignment.seat)}` : '待编排'}`; }).join(''); return `
${h((reg.candidate?.name || '?').slice(0,1))}
${h(reg.candidate?.name)}${h(reg.candidate?.school || '')} · ${h(reg.candidate?.grade || '')}
${h(reg.exam.name)}${h(reg.exam.code)}${h(reg.admitCard?.number || '待编排')}${h(reg.admitCard?.testCenter || '—')}${h(reg.admitCard?.centerCode || '')} · ${h(reg.admitCard?.centerAddress || '')}
${subjects}
${reg.admitCard ? `` : '—'}`; }).join(''); return `${form}${ruleCards}${planCards}${exportPanel}

考试考场序号来自本次编排;通用名称和场地代码来自考点物理场地档案。

${rows || ''}
考生考试准考证号固定考点 / 地址分科考试考场 / 物理场地 / 座位操作
暂无已通过的考试报名
`; } function adminResults(data) { const preview = state.resultImportPreview; const activeExam = data.exams.find(item => item.id === state.resultExamFilter) || data.exams.find(item => !item.archivedAt && item.registrationCount) || data.exams.find(item => !item.archivedAt) || data.exams[0]; if (activeExam) state.resultExamFilter = activeExam.id; const activeSubject = activeExam?.subjects.find(item => item.id === state.resultSubjectFilter) || activeExam?.subjects[0]; if (activeSubject) state.resultSubjectFilter = activeSubject.id; const examResults = activeExam ? data.results.filter(item => item.examId === activeExam.id) : data.results; const examAppeals = activeExam ? (data.appeals || []).filter(item => item.result?.examId === activeExam.id) : (data.appeals || []); const passRate = activeExam?.complete ? Math.round(activeExam.qualified / activeExam.complete * 100) : null; const subjectRegistrations = (data.registrations || []).filter(item => item.examId === activeExam?.id && item.subjectIds.includes(activeSubject?.id)); const subjectResults = new Map(examResults.filter(item => item.subjectId === activeSubject?.id).map(item => [item.registrationId, item])); const recordedCount = subjectRegistrations.filter(item => subjectResults.has(item.id)).length; const publishedCount = subjectRegistrations.filter(item => subjectResults.get(item.id)?.published).length; const entryRows = subjectRegistrations.map((registration, index) => { const result = subjectResults.get(registration.id); const status = result?.published ? 'published' : result ? 'draft' : 'missing'; return `${index + 1}
${h((registration.candidateName || '?').slice(0, 1))}
${h(registration.candidateName)}${h(registration.schoolName)} · ${h(registration.className)}
${h(registration.candidateNumber)}${h(registration.admitCard?.number || '待编排')}${result ? badge(result.published ? 'published' : 'draft') : '未录入'}${result ? formatDate(result.updatedAt || result.publishedAt, true) : '—'}`; }).join(''); const entry = state.user.adminLevel === 'super' && !activeExam?.archivedAt ? `
SCORE ENTRY ROSTER

按名单录入单科成绩

选定考试和科目后,直接在完整考生名单中录分;暂存不会向考生发布。

应录人数${subjectRegistrations.length}已录入${recordedCount}已暂存${Math.max(0, recordedCount - publishedCount)}已发布${publishedCount}

${activeSubject ? `${h(activeSubject.name)} · 满分 ${h(activeSubject.fullScore)} 分 · ${h(activeSubject.passRule === 'none' ? '不设单科线' : activeSubject.passRule === 'rank_percent' ? `本科排名前 ${activeSubject.passValue}%` : `固定 ${activeSubject.passScore} 分达线`)}` : '请选择科目'}

${entryRows || ''}
序号考生报名号准考证号成绩(0—${h(activeSubject?.fullScore || '—')})状态最近保存
该科目暂无已通过报名的考生
` : ''; const featureRegistrations = (data.registrations || []).filter(item => item.examId === activeExam?.id); const featureModified = featureRegistrations.filter(item => Number(item.featureScore || 0) !== 0).length; const featureRows = featureRegistrations.map((registration, index) => { const featureScore = Number(registration.featureScore || 0); const status = `${featureScore ? 'modified' : 'zero'} ${registration.specialtyType ? 'specialty' : 'general'}`; return `${index + 1}
${h((registration.candidateName || '?').slice(0, 1))}
${h(registration.candidateName)}${h(registration.schoolName)} · ${h(registration.className)}
${h(registration.candidateNumber)}${h(registration.admitCard?.number || '待编排')}${h(registration.specialtyLabel || '普通生')}${registration.specialtyType ? '特长类别投档时计入' : '普通类别不计特征分'}`; }).join(''); const featureEntry = state.user.adminLevel === 'super' && !activeExam?.archivedAt ? `
UNIFIED SPECIALTY TEST

按名单登记特征分

所有考生默认 0 分;只有录取到特长生招生类别时,特征分才加入文化课总分参与该类别投档。

分类计分规则普通类别:文化课总分特长类别:文化课总分 + 特征分
本场考生${featureRegistrations.length}默认 0 分${featureRegistrations.length - featureModified}已修改${featureModified}具有特长资格${featureRegistrations.filter(item => item.specialtyType).length}

特征分按考试、按考生独立保存,不改变文化课成绩。

${featureRows || ''}
序号考生报名号准考证号特长资格 / 计分范围特征分(0—1000)
本场暂无已通过报名的考生
` : ''; const importPreview = preview ? `
EXCEL STAGING AREA

${h(preview.fileName || '成绩导入预览')}

此处数据尚未写入数据库。请检查错误、更新覆盖项和发布状态后再提交。

总行数${preview.summary.total}可提交${preview.summary.valid}有错误${preview.summary.invalid}新增 / 更新${preview.summary.create} / ${preview.summary.update}将发布${preview.summary.publish}
${preview.rows.map(row => ``).join('')}
Excel 行考生考试 / 科目成绩独立及格线发布写入方式 / 校验
${h(row.sourceRow)}${h(row.candidateName || '未匹配')}${h(row.candidateNumber)}${h(row.examName || row.examCode)}${h(row.subjectName)}${Number.isFinite(row.score) ? h(row.score) : '—'}${row.scoreRate == null ? '' : `${h(row.scoreRate)}% · ${h(row.grade)}`}${h(row.passText || '—')}${row.qualified == null ? '不判定' : row.qualified ? '达到单科线' : '未达到单科线'}${badge(row.published ? 'published' : 'draft')}${row.errors.length ? `
    ${row.errors.map(error => `
  • ${h(error)}
  • `).join('')}
` : `${row.mode === 'update' ? '覆盖已有成绩' : '新增成绩'}校验通过`}

${preview.summary.invalid ? `有 ${preview.summary.invalid} 行错误,修正 Excel 后请重新选择文件。` : `确认后将以一个事务写入 ${preview.summary.valid} 条成绩,失败时不会留下部分数据。`}

` : ''; const appealLedger = `

本场成绩复议

复议按考生所属班级和学校自动分配,可在流程中心办理。

${examAppeals.map(appeal => ``).join('') || ''}
考生 / 科目考试原成绩复议理由当前步骤责任人状态
${h(appeal.result?.candidateName)} · ${h(appeal.result?.subjectName)}${h(appeal.result?.examName)}${h(appeal.result?.score)}${h(appeal.reason)}${h(appeal.currentStepDetail?.name || '流程已结束')}${h(appeal.assignee?.displayName || '—')}${badge(appeal.status)}
本场暂无成绩复议申请
`; const examButton = exam => ``; const currentExams = data.exams.filter(exam => !exam.archivedAt); const archivedExams = data.exams.filter(exam => exam.archivedAt); const examStrip = `
${currentExams.map(examButton).join('')}
${archivedExams.length ? `
历史归档考试 ${archivedExams.length} 场 · 成绩永久锁定
${archivedExams.map(examButton).join('')}
` : ''}`; const cacheButton = state.user.adminLevel === 'super' ? `` : ''; const toolbar = `
${activeExam?.archivedAt ? '归档成绩只读区' : '成绩 Excel 工作区'}${activeExam?.archivedAt ? '本场成绩已永久锁定,仅保留导出与查阅能力' : '名单模板已预填本场全部考生;导入后先预览,确认才写入数据库'}
${cacheButton}${activeExam?.archivedAt ? '' : ''}${state.user.adminLevel === 'super' && !activeExam?.archivedAt ? '' : ''}
`; const metrics = `
报名考生${activeExam?.registrationCount ?? 0}本场已通过报名
录入进度${activeExam?.scored ?? 0} / ${activeExam?.enrolledSubjects ?? 0}剩余 ${activeExam?.missing ?? 0} 科次
已发布${activeExam?.published ?? 0}草稿 ${Math.max(0, (activeExam?.scored || 0) - (activeExam?.published || 0))} 条
成绩已出齐${activeExam?.complete ?? 0}
整场合格率${passRate == null ? '—' : `${passRate}%`}按本场排名或所设规则判定
成绩复议${examAppeals.length}当前考试累计
`; const ledger = `
${examResults.map(result => ``).join('') || ''}
考生考试 / 科目成绩排名 / 等级单科及格规则达线发布更新时间
${h((result.candidateName || '?').slice(0,1))}
${h(result.candidateName)}${h(result.candidateNumber)}${h(result.schoolName)} · ${h(result.className)}
${h(result.subjectName)}${h(result.examCode)}${h(result.score)} / ${h(result.fullScore)}第 ${h(result.rank)} / ${h(result.cohortSize)} 名${h(result.grade)} · 前 ${h(result.rankPercent)}%${h(result.passText)}${result.qualified == null ? '不判定' : result.qualified ? '达线' : '未达线'}${badge(result.published ? 'published' : 'draft')}${formatDate(result.updatedAt || result.publishedAt, true)}
本场考试还没有成绩记录
`; const archiveLock = activeExam?.archivedAt ? `
${icons.check}
本场考试已归档${formatDate(activeExam.archivedAt, true)} 起,手工录入、Excel 导入和成绩复议改分均已永久关闭。
` : ''; return `${examStrip}${archiveLock}${metrics}${toolbar}${activeExam?.archivedAt ? '' : importPreview}${entry}${featureEntry}${ledger}${appealLedger}`; } function adminUsers(data) { return `
SELF REGISTRATION

考生自主注册

${data.selfRegistrationEnabled ? '公开入口已开放,考生可以自主申请固定报名号。' : '当前由学校统一创建账户、下发报名号和初始密码。'}

${data.selfRegistrationEnabled ? '已开放' : '已关闭'}

管理员账户不物理删除;停用会立即结束现有会话,并完整保留审批和审计记录。

${data.admins.map(item => { const self = item.id === state.user.id; return ``; }).join('')}
管理员登录账号层级绑定范围状态账户操作
${h(item.displayName.slice(0, 1))}
${h(item.displayName)}${h(item.id)}
${h(item.username)}${h(item.levelName)}${h(item.schoolName || '全局')}${h(item.className || '')}${item.active ? badge('approved') : badge('closed')}
`; } function adminCenters(data) { const roomTypeNames = { standard: '标准考场', computer: '机考考场', accessible: '无障碍考场', spare: '备用考场' }; const detailAddresses = new Map(data.centers.map(center => [center.id, center.address])); data.centers.forEach(center => { center.address = formatRegionAddress(center); }); const cards = data.centers.map(center => `
${h(center.schoolName)} · ${h(center.code)}

${h(center.name)}

${center.pendingChange ? '变更审批中' : ''}${badge(center.status === 'active' ? 'approved' : 'closed')}
结构化考场${center.rooms.length}
启用席位${center.totalCapacity}
开放时间${h(center.gateOpenTime || '未设')}
详细地址
${h(center.address)}
考点负责人
${h(center.managerName || '未填写')} · ${h(center.managerPhone || center.contact || '未填写')}
应急电话
${h(center.emergencyPhone || '未填写')}
交通提示
${h(center.transport || '未填写')}
${center.rooms.map(room => ``).join('')}
考场位置类型容量座位编排状态
${h(room.name)}${h(room.code)}${h(room.building)} · ${h(room.floor || '楼层未填')}${h(roomTypeNames[room.roomType] || room.roomType)}${h(room.capacity)} 席${h(room.seatPlan || '按现场座次表编排')}${badge(room.status === 'active' ? 'approved' : 'closed')}
`).join(''); data.centers.forEach(center => { center.address = detailAddresses.get(center.id); }); const requests = data.changeRequests || []; return `${excelToolbar('centers', { label: '考点考场档案' })}
正式考点${data.centers.length}
结构化考场${data.centers.reduce((sum, item) => sum + item.rooms.length, 0)}
待审批变更${requests.filter(item => item.status === 'pending').length}
${cards || emptyState('还没有正式考点', '提交考点和考场档案,经流程审批后会显示在这里。')}

考点变更台账

新增和修改均保留申请快照,审批通过后才更新正式档案。

${requests.map(item => ``).join('') || ''}
申请类型考点学校考场数提交时间当前状态责任人
${item.requestType === 'create' ? '新增考点' : '修改档案'}${h(item.name)}${h(item.code)}${h(item.schoolName)}${item.rooms.length} 个${formatDate(item.createdAt, true)}${badge(item.status)}${h(item.workflow?.assignee?.displayName || '流程已结束')}
暂无考点变更申请
`; } function adminAccountBatches(data) { const classes = data.classes || []; const batches = data.batches || []; const form = `
SCHOOL ACCOUNT REQUEST

按班级申领报名号

只填写需要的数量。提交后进入审批,最终批准前不会创建任何考生账户。

单批上限500个账户
${classes.map(item => ``).join('')}

审批通过后生成固定报名号、随机初始密码、待补录考生账户

`; const ledger = batches.map(batch => { const resultRows = batch.status === 'approved' ? `
账号下发清单${batch.totalCount} 个账号 · 考生首次登录必须改密
${batch.items.map((item, index) => ``).join('')}
序号班级固定报名号 / 账户初始密码
${index + 1}${h(item.className)}${h(item.candidateNumber)}${h(item.initialPassword)}
` : ''; return `
${h(batch.id)}

${h(batch.schoolName)} · ${batch.totalCount} 个报名号

${formatDate(batch.createdAt, true)} 由 ${h(batch.requesterName)} 提交

${badge(batch.status)}
${batch.quotas.map(item => `${h(item.className)}${item.count} 人`).join('')}
当前进度${h(batch.workflow?.currentStepDetail?.name || statusLabels[batch.status])}${batch.workflow?.assignee ? `责任人:${h(batch.workflow.assignee.displayName)}` : batch.status === 'approved' ? '已生成并返回全部账户凭据' : '流程已结束'}
${batch.reviewNote ? `
审批意见${h(batch.reviewNote)}
` : ''}${resultRows}
`; }).join(''); return `${excelToolbar('account_quotas', { label: '班级申领配额' })}${form}
REQUEST LEDGER

申领批次与返回结果

结果只在最终批准后生成;报名号随后作为考生长期账户。

${ledger || emptyState('还没有申领批次', '在上方按班级填写人数并提交审批。')}
`; } function adminNumberRules(data) { const rule = data.activeRule || { name: '自定义报名号规则', separator: '-', segments: [] }; const byType = Object.fromEntries(rule.segments.map(item => [item.type, item])); const types = Object.keys(numberSegmentMeta); return `
ONE CANDIDATE · ONE NUMBER超级管理员只设计号码规则

校级管理员按班级提交申领,流程最终批准后系统才创建长期考生账户。

账户报名号组成

规则用于最终审批后的批量建号;流水号为必选字段。

当前规则
${types.map((type, index) => { const segment = byType[type]; const checked = Boolean(segment) || type === 'sequence'; return ``; }).join('')}
`; } function adminFlowDesign(workflows) { const codes = { profile_change: 'PROFILE CHANGE', registration_review: 'REGISTRATION', center_change: 'CENTER & ROOM CHANGE', candidate_account_batch: 'ACCOUNT BATCH', score_appeal: 'SCORE APPEAL' }; return `
${workflows.map(workflow => `
${h(codes[workflow.businessType] || workflow.businessType)}

${h(workflow.name)}

${workflow.steps.map(step => workflowStepEditor(step)).join('')}
`).join('')}
`; } function workflowStepEditor(step = {}) { return `
`; } function adminFlows(data) { const actionNames = { submit: '提交', approve: '通过', reject: '退回考生', transfer: '转交', return: '退回节点', supervise: '监督调整' }; const typeNames = { profile_change: '考生信息修改', registration_review: '考试报名', center_change: '考点考场变更', candidate_account_batch: '批量报名号申领', score_appeal: '考生成绩复议' }; const visibleTypes = [...new Set(data.instances.map(instance => instance.businessType))]; const toolbar = `
`; return `${toolbar}
${data.instances.map(instance => { const isCenter = instance.businessType === 'center_change'; const isBatch = instance.businessType === 'candidate_account_batch'; const isAppeal = instance.businessType === 'score_appeal'; const title = isCenter ? instance.centerName : isBatch ? `${instance.schoolName} · ${instance.batchTotalCount} 个账户` : isAppeal ? `${instance.candidateName} · ${instance.appealResult?.subjectName || '成绩复议'}` : instance.candidateName; const sub = isCenter ? `${instance.requestType === 'create' ? '新增考点' : '修改档案'} · ${instance.schoolName}` : isBatch ? (instance.accountBatch?.quotas || []).map(item => `${item.className} ${item.count} 人`).join(' · ') : isAppeal ? `${instance.appealResult?.examName || ''} · 原成绩 ${instance.appealResult?.score ?? '—'}` : `${instance.examName ? `${instance.examName} · ` : ''}${instance.schoolName} · ${instance.className}`; return `
${h(typeNames[instance.businessType] || instance.businessType)}

${h(title)}

${h(sub)}

${badge(instance.status)}
${instance.steps.map(step => `
${step.position < instance.currentStep || instance.status === 'approved' ? '✓' : step.position}${h(step.name)}${h(statusLabels[step.adminLevel])}
`).join('')}
当前责任人${h(instance.assignee?.displayName || '流程已结束')}${h(instance.currentStepDetail?.name || statusLabels[instance.status])}
`; }).join('') || emptyState('暂无审批流程', '考生资料、考试报名、考点档案或批量建号提交后,流程会显示在这里。')}
`; } return { renderAdmin, workflowStepEditor }; }