import { formatRegionAddress } from './region-select.js'; import { admissionCategoriesEditor } from './admission-plan-editor.js'; import { specialtyLabel } from '../data/specialty-types.js'; import { filterTableItems, getTableControl } from './table-state.js'; 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(); if (page === 'admissions') page = 'admission-settings'; 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': ['报名号规则', '设计审批通过后生成的新账户号码组成。'], 'admission-settings': ['录取设置', '设置志愿窗口并控制投档、签发通知书和报到阶段。'], 'admission-accounts': ['招生学校账户', '创建、停用和维护招生学校工作台账户。'], 'admission-plans': ['招生计划', '代上传、审核招生计划并实时查看计划完成率。'], 'admission-reporting': ['报到与补录', '审核学校报到统计和是否开展补充录取的决定。'], 'admission-supervision': ['投档与退档监督', '搜索、筛选投档记录并审批特殊退档;考生志愿保持只读。'], '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.startsWith('admission-') ? 'admissions' : page; let data = page === 'results' && !state.resultExamFilter ? { ok: true, selectedExamId: '', results: [], appeals: [], registrations: [], exams: state.resultExamCatalog || state.publicData.exams || [], resultCache: { enabled: false, status: 'not-loaded' } } : page === 'security' ? await api('/api/auth/totp') : page === 'results' ? await api(`/api/admin/results/summary?examId=${encodeURIComponent(state.resultExamFilter)}`) : await api(`/api/admin/${endpoint}`); if (page === 'results' && state.resultExamFilter) { const activeExam = data.exams.find(item => item.id === state.resultExamFilter); const subjectId = state.resultSubjectFilter || activeExam?.subjects?.[0]?.id || ''; state.resultSubjectFilter = subjectId; const requestQuery = (key, extra = {}) => { const current = state.tablePages[key] || {}; const control = getTableControl(state, key); const params = new URLSearchParams({ examId: state.resultExamFilter, page: String(current.page || 1), pageSize: String(current.pageSize || 50), query: control.query || '', status: control.status || 'all', ...extra }); return params.toString(); }; const resultPromise = api(`/api/admin/results?${requestQuery('resultTable')}`); const entryPromise = state.user.adminLevel === 'super' && subjectId ? api(`/api/admin/results/roster?${requestQuery('resultEntryTable', { subjectId })}`) : Promise.resolve({ items: [], pagination: { page: 1, pageSize: 50, total: 0, totalPages: 1, key: 'resultEntryTable' } }); const featurePromise = state.user.adminLevel === 'super' ? api(`/api/admin/results/roster?${requestQuery('featureScoreTable', { mode: 'feature' })}`) : Promise.resolve({ items: [], pagination: { page: 1, pageSize: 50, total: 0, totalPages: 1, key: 'featureScoreTable' } }); const [resultData, entryData, featureData] = await Promise.all([resultPromise, entryPromise, featurePromise]); data = { ...data, results: resultData.items || [], resultPage: { ...(resultData.pagination || {}), items: resultData.items || [] }, entryPage: { ...(entryData.pagination || {}), items: entryData.items || [] }, featurePage: { ...(featureData.pagination || {}), items: featureData.items || [] }, entryCounts: entryData.counts || {}, featureCounts: featureData.counts || {} }; } if (page === 'results' && data.selectedExamId) state.resultExamFilter = data.selectedExamId; if (page === 'results' && data.exams?.length) state.resultExamCatalog = data.exams; 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), 'admission-settings': () => adminAdmissionsV2(data, 'settings'), 'admission-accounts': () => adminAdmissionsV2(data, 'accounts'), 'admission-plans': () => adminAdmissionsV2(data, 'plans'), 'admission-reporting': () => adminAdmissionsV2(data, 'reporting'), 'admission-supervision': () => adminAdmissionsV2(data, 'supervision'), 'indicator-qualifications': () => adminIndicatorQualifications(data), security: () => accountSecurity(data) }[page](); app.innerHTML = portalShell('admin', page, content, ...meta[page]); if (page === 'admission-supervision') mountPreferenceLedger(data); } catch (error) { renderError(error); } } function mountPreferenceLedger(data) { const container = app.querySelector('.portal-content'); if (!container) return; const rows = data.preferenceRows || []; const preferencePage = paged(rows, 'adminPreferenceTable'); const statusClass = { unfilled: 'pending', submitted: 'approved', locked: 'completed', unavailable: 'closed', ineligible: 'rejected' }; container.insertAdjacentHTML('beforeend', `

考生志愿实时台账

同时显示未填报、已填报和已锁定考生;页面仅供监督,不提供代改入口。

当前筛选 ${preferencePage.total} / 共 ${rows.length} 人
${preferencePage.items.map(item => ``).join('') || ''}
报名号 / 考生考试 / 生源校轮次 / 状态志愿顺序提交与锁定
${h(item.candidate.name)}${h(item.candidate.registrationNumber)}${h(item.specialty)}${h(item.examName)}${h(item.sourceSchoolName)}${item.className ? ` · ${h(item.className)}` : ''}第 ${h(item.round)} 轮${h(item.fillStatus)}
${item.choices.map((choice, index) => `${choice.preferenceType === 'indicator' ? '指标' : index + 1}${h(choice.schoolName || choice.schoolId)}${h([choice.schoolCode, choice.categoryName || choice.categoryCode].filter(Boolean).join(' · '))}`).join('') || '尚未填报志愿'}
${h(item.submissionCount)} / ${h(item.maxSubmissions)} 次${item.submittedAt ? formatDate(item.submittedAt, true) : '暂无提交时间'} · ${h(item.lockStatus)}
没有符合条件的考生填报记录
${pagination(preferencePage)}
`); } 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 schoolPage = paged(schools, 'schoolTable', 20); 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)}
${schoolPage.items.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')}
没有符合条件的学校。
${pagination(schoolPage)}
`; } 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 allCandidates = data.candidates || []; const candidatePage = paged(allCandidates, 'candidateTable'); const candidates = candidatePage.items; 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(allCandidates.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(allCandidates.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 || ''}
报名号 / 考生证件号码学校 / 年级 / 班级关联考试 / 科目账户状态资料状态操作
当前范围暂无考生
${pagination(candidatePage)}
`; } function paged(items, key, defaultPageSize = 50) { items = filterTableItems(state, items, key); const current = state.tablePages[key] || {}; const pageSize = [20, 50, 100].includes(Number(current.pageSize)) ? Number(current.pageSize) : defaultPageSize; const total = items.length; const totalPages = Math.max(1, Math.ceil(total / pageSize)); const page = Math.min(Math.max(1, Number(current.page) || 1), totalPages); state.tablePages[key] = { page, pageSize }; return { items: items.slice((page - 1) * pageSize, page * pageSize), page, pageSize, total, totalPages, key }; } function pagination(meta) { if (!meta || meta.total <= meta.pageSize) return ''; const start = (meta.page - 1) * meta.pageSize + 1; const end = Math.min(meta.total, meta.page * meta.pageSize); const pages = [...new Set([1, meta.page - 1, meta.page, meta.page + 1, meta.totalPages])].filter(page => page >= 1 && page <= meta.totalPages); return ``; } 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 allCurrent = registrations.filter(reg => !reg.exam.archivedAt); const allArchived = registrations.filter(reg => reg.exam.archivedAt); const currentPage = paged(allCurrent, 'registrationTable'); const archivedPage = paged(allArchived, 'archivedRegistrationTable'); const current = currentPage.items; const archived = archivedPage.items; 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))}${pagination(currentPage)}
${allArchived.length ? `
归档考试报名记录${allArchived.length} 条 · 流程与报名信息已冻结${allArchived.length}
${table(archived, 'archivedRegistrationTable')}${pagination(archivedPage)}
` : ''}`; } function adminPayments(data) { const allRegistrations = data.registrations || []; const paymentPage = paged(allRegistrations, 'paymentTable'); const registrations = paymentPage.items; const paid = allRegistrations.filter(item => item.paymentStatus === 'paid'); const unpaid = allRegistrations.filter(item => item.paymentStatus === 'unpaid'); const totalDue = allRegistrations.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(allRegistrations.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: '缴费名单' })}
报名人数${allRegistrations.length}
待确认${unpaid.length}
已缴费${paid.length}
应缴合计${money(totalDue)}
已缴合计${money(totalPaid)}
${bulkToolbar}
${filters}
${data.canUpdatePayment ? '' : ''}${rows || ``}
考生 / 报名号学校 / 年级 / 班级考试 / 科目应缴金额缴费状态确认记录操作
当前范围暂无已审核通过的报名
${pagination(paymentPage)}
`; } 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, section = 'settings') { 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: '学校审核中', reporting: '考生报到中', supplementary: '补录填报中', completed: '录取完成' }; const accountPage = paged(data.schoolAccounts, 'admissionAccountTable', 20); const planPage = paged(data.plans, 'adminAdmissionPlanTable', 20); const placementPage = paged(data.placements, 'adminPlacementTable'); const preferenceSnapshots = data.preferenceRows || []; const preferenceSnapshotPanel = `

志愿填报实时快照

不受填报阶段限制,可随时导出当前轮次全部考生的填报、未填报与锁定情况。

${preferenceSnapshots.length} 人
${data.settings.filter(item => item.payload?.enabled).map(setting => { const exam = data.exams.find(item => item.id === setting.examId) || {}; const round = Number(setting.payload?.round || 1); const rows = preferenceSnapshots.filter(item => item.examId === setting.examId && Number(item.round) === round); return `
${h(exam.code || 'EXAM')}

${h(exam.name || setting.examId)}

第 ${h(round)} 轮 · ${h(phaseLabels[setting.status] || setting.status)}

全部考生
${rows.length}
尚未填报
${rows.filter(item => item.status === 'unfilled').length}
已填报
${rows.filter(item => ['submitted', 'locked'].includes(item.status)).length}
已锁定
${rows.filter(item => item.status === 'locked').length}
`; }).join('') || '

启用志愿填报后,可在这里随时导出实时台账。

'}
`; const settings = `

考试志愿设置

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

${selected ? `
补录不能手工开启,必须由招生学校提交报到情况和补录决定,并经超级管理员审批。` : ''}
`; const accountRows = accountPage.items.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 || ''}
账户登录账号绑定招生学校状态创建时间账户操作
还没有招生学校账户
${pagination(accountPage)}
`; const planForm = `

代招生校上传计划

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

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

招生计划审核与完成率

实时核对计划、正式录取和实际报到完成情况。

${pendingPlans.length} 份待审
${planPage.items.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('')}${h(plan.progress?.admissionRate || 0)}%正式录取 ${h(plan.progress?.finalCount || 0)} / ${h(plan.progress?.totalQuota || 0)}实际报到 ${h(plan.progress?.reportingRate || 0)}%${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 || '')}
暂无招生计划
${pagination(planPage)}
`; const reportingPage = paged(data.reportingRequests || [], 'adminReportingApprovalTable', 20); const reportingApprovals = `

报到情况与补录审批

学校决定审批通过后,统计数据和说明将自动进入公开通知。

${(data.reportingRequests || []).filter(item => item.status === 'pending_approval').length} 项待审批
${reportingPage.items.map(item => ``).join('') || ''}
考试 / 学校报到统计学校决定状态审批
${h(item.examName)}${h(item.schoolName)} · 第 ${h(item.payload?.round || 1)} 轮计划 ${h(item.progress.totalQuota)} 人 · 已报到 ${h(item.progress.reportedCount)} 人完成率 ${h(item.progress.reportingRate)}% · 缺额 ${h(item.progress.reportingGap)} 人${item.payload?.supplementDecision === 'supplement' ? '申请补录' : '不进行补录'}${h(item.payload?.decisionNote || '')}${badge(item.status)}${item.status === 'pending_approval' ? `` : h(item.payload?.approvalNote || '—')}
暂无报到与补录审批记录
${pagination(reportingPage)}
`; const placements = `

录取情况台账

支持跨页搜索、考试/学校/状态筛选,并按当前筛选结果导出 Excel。

当前筛选 ${placementPage.total} / 共 ${data.placements.length} 条
${placementPage.items.map(item => ``).join('') || ''}
考生 / 生源校考试 / 成绩录取学校类别录取 / 报到状态退档审核
${h(item.candidate.name)}${h(item.candidate.registrationNumber)}${h(item.sourceSchoolName || '生源校未登记')}${item.className ? ` · ${h(item.className)}` : ''}${h(item.examName || item.examId)}${h(item.payload.totalScore)} 分 · 特征分 ${h(item.payload.featureScore || 0)} · 第 ${h(item.payload.preferenceOrder)} 志愿${h(item.schoolName)}${h(item.schoolCode || '')}${h(item.payload.categoryName)}${h(item.candidate.specialtyLabel || '普通生')}${badge(item.status)}报到:${h(item.reportingStatusLabel || '—')}${item.status === 'withdrawal_pending' ? `${h(item.payload.withdrawalReason)}` : '—'}
没有符合条件的录取记录
${pagination(placementPage)}
`; const banner = `
ADMISSION COMMAND

中考招生录取控制台

计划完成率、考生报到与补录审批在同一条可审计链路中实时更新。

待审计划
${pendingPlans.length}
补录待审批
${(data.reportingRequests || []).filter(item => item.status === 'pending_approval').length}
正式录取
${data.placements.filter(item => item.status === 'final').length}
已报到
${(data.reportingRequests || []).reduce((sum, item) => sum + Number(item.progress?.reportedCount || 0), 0)}
`; const sections = { settings: `
${settings}${preferenceSnapshotPanel}
`, accounts, plans: `${planForm}${plans}`, reporting: reportingApprovals, supervision: placements }; return `${banner}${sections[section] || sections.settings}`; } 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 qualificationPage = paged(status.rows, tableId); 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 = qualificationPage.items.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 || ''}
选择报名号 / 姓名特长类型指标分配资格确认时间保存
本校暂无资料已完善的在册考生
${pagination(qualificationPage)}
`; }).join('')}`; } function adminNotices(notices, publications = []) { const entries = [ ...notices.map(data => ({ kind: 'notice', status: `ordinary ${data.status} ${data.status === 'published' ? 'visible' : 'hidden'}`, data })), ...publications.map(data => ({ kind: 'publication', status: `system ${data.status} ${data.visible ? 'visible' : 'hidden'}`, data })) ]; const noticePage = paged(entries, 'noticeTable', 20); const rows = noticePage.items.map(entry => { const item = entry.data; if (entry.kind === 'notice') return `${h(item.title)}${h(item.summary)}手动通知${h(item.category)}${h(item.author)}${formatDate(item.publishAt || item.createdAt,true)}${item.pinned ? '首页置顶' : item.status === 'published' ? '通知目录' : '尚未展示'}${badge(item.status)}
${item.status === 'draft' ? `` : ''}
`; return `${h(item.title)}${h(item.summary)}自动公示${h(item.category)}${h(item.author)}${formatDate(item.publishedAt,true)}通知目录${badge(item.status)}`; }).join(''); return `

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

${rows || ''}
标题来源 / 分类发布人发布时间展示位置状态操作
暂无符合条件的通知或系统公示
${pagination(noticePage)}
`; } function adminAdmit(data) { const approved = data.registrations || []; const admitPage = paged(approved, 'admitTable'); 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 = admitPage.items.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 || ''}
考生考试准考证号固定考点 / 地址分科考试考场 / 物理场地 / 座位操作
暂无已通过的考试报名
${pagination(admitPage)}
`; } function adminResults(data) { const preview = state.resultImportPreview; const activeExam = state.resultExamFilter ? data.exams.find(item => item.id === state.resultExamFilter) : null; if (!activeExam) { const examButton = exam => ``; const currentExams = data.exams.filter(exam => !exam.archivedAt); const archivedExams = data.exams.filter(exam => exam.archivedAt); return `
${currentExams.map(examButton).join('')}
${archivedExams.length ? `
历史归档考试 ${archivedExams.length} 场 · 选择后读取
${archivedExams.map(examButton).join('')}
` : ''}${emptyState('请先选择考试', '未选择考试时不会读取成绩、考生录分名单或成绩复议数据。')}`; } const activeSubject = activeExam?.subjects.find(item => item.id === state.resultSubjectFilter) || activeExam?.subjects[0]; if (activeSubject) state.resultSubjectFilter = activeSubject.id; const examResults = data.results || []; const resultPage = data.resultPage || { items: examResults, page: 1, pageSize: 50, total: examResults.length, totalPages: 1, key: 'resultTable' }; const examAppeals = activeExam ? (data.appeals || []).filter(item => item.result?.examId === activeExam.id) : (data.appeals || []); const appealPage = paged(examAppeals, 'resultAppealTable'); const passRate = activeExam?.complete ? Math.round(activeExam.qualified / activeExam.complete * 100) : null; const resultEntryPage = data.entryPage || { items: [], page: 1, pageSize: 50, total: 0, totalPages: 1, key: 'resultEntryTable' }; const subjectRegistrations = resultEntryPage.items; const recordedCount = Number(data.entryCounts?.recorded || 0); const publishedCount = Number(data.entryCounts?.published || 0); const entryRows = resultEntryPage.items.map((registration, index) => { const result = registration.result; const status = result?.published ? 'published' : result ? 'draft' : 'missing'; return `${(resultEntryPage.page - 1) * resultEntryPage.pageSize + 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

按名单录入单科成绩

选定考试和科目后,按服务端分页录分;暂存不会向考生发布。

应录人数${Number(data.entryCounts?.total || 0)}已录入${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 featurePage = data.featurePage || { items: [], page: 1, pageSize: 50, total: 0, totalPages: 1, key: 'featureScoreTable' }; const featureRegistrations = featurePage.items; const featureModified = Number(data.featureCounts?.modified || 0); const featureRows = featurePage.items.map((registration, index) => { const featureScore = Number(registration.featureScore || 0); const status = `${featureScore ? 'modified' : 'zero'} ${registration.specialtyType ? 'specialty' : 'general'}`; return `${(featurePage.page - 1) * featurePage.pageSize + 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 分;只有录取到特长生招生类别时,特征分才加入文化课总分参与该类别投档。

分类计分规则普通类别:文化课总分特长类别:文化课总分 + 特征分
本场考生${Number(data.featureCounts?.total || 0)}默认 0 分${Math.max(0, Number(data.featureCounts?.total || 0) - featureModified)}已修改${featureModified}具有特长资格${Number(data.featureCounts?.specialty || 0)}

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

${featureRows || ''}
序号考生报名号准考证号特长资格 / 计分范围特征分(0—1000)
本场暂无符合条件的考生
` : ''; const importPreviewPage = preview ? paged(preview.rows, 'resultImportPreviewTable') : null; 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}
${importPreviewPage.items.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' ? '覆盖已有成绩' : '新增成绩'}校验通过`}
${pagination(importPreviewPage)}

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

` : ''; const appealLedger = `

本场成绩复议

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

${appealPage.items.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)}
本场暂无成绩复议申请
${pagination(appealPage)}
`; 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 = `
${resultPage.items.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)}
本场考试还没有成绩记录
${pagination(resultPage)}
`; const archiveLock = activeExam?.archivedAt ? `
${icons.check}
本场考试已归档${formatDate(activeExam.archivedAt, true)} 起,手工录入、Excel 导入和成绩复议改分均已永久关闭。
` : ''; return `${examStrip}${archiveLock}${metrics}${toolbar}${activeExam?.archivedAt ? '' : importPreview}${entry}${pagination(resultEntryPage)}${featureEntry}${pagination(featurePage)}${ledger}${appealLedger}`; } function adminUsers(data) { const adminPage = paged(data.admins || [], 'adminAccountTable', 20); return `
SELF REGISTRATION

考生自主注册

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

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

账户不物理删除,停用后历史审批记录仍保留。

${adminPage.items.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')}
没有符合条件的管理员
${pagination(adminPage)}
`; } 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 centerPage = paged(data.centers, 'centerDossierGrid', 20); const cards = centerPage.items.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 || []; const requestPage = paged(requests, 'centerChangeTable', 20); 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('没有符合条件的考点', '可清除搜索后查看全部正式考点。')}
${pagination(centerPage)}

考点变更台账

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

${requestPage.items.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 || '流程已结束')}
没有符合条件的考点变更申请
${pagination(requestPage)}
`; } function adminAccountBatches(data) { const classes = data.classes || []; const batches = data.batches || []; const batchPage = paged(batches, 'accountBatchLedger', 20); const form = `
SCHOOL ACCOUNT REQUEST

按班级申领报名号

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

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

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

`; const ledger = batchPage.items.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('没有符合条件的申领批次', '可清除筛选后查看全部记录。')}
${pagination(batchPage)}
`; } 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 workflowPage = paged(data.instances, 'workflowBoard', 20); const toolbar = `
`; return `${toolbar}
${workflowPage.items.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('暂无审批流程', '考生资料、考试报名、考点档案或批量建号提交后,流程会显示在这里。')}
${pagination(workflowPage)}`; } return { renderAdmin, workflowStepEditor }; }