import { formatRegionAddress } from './region-select.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, emptyState, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity } = context; async function renderAdmin(page) { if (state.user?.role !== 'admin') return navigate('login'); 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: ['招生录取', '设置志愿窗口、审核招生计划,并按分数优先、遵循志愿执行投档、退档审核和补录。'], 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), admit: () => adminAdmit(data), results: () => adminResults(data), schools: () => adminSchools(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: () => adminAdmissions(data), security: () => accountSecurity(data) }[page](); app.innerHTML = portalShell('admin', page, content, ...meta[page]); } catch (error) { renderError(error); } } 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 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 readOnly = false; const archiveConsole = state.user.adminLevel === 'school' ? (() => { const classes = data.classes || []; const grades = [...new Set(classes.map(item => item.grade))]; return `
SCHOOL ACCOUNT ARCHIVE

按班级或年级归档账户

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

`; })() : ''; return `${archiveConsole}${excelToolbar('candidates', { importable: !readOnly, label: '考生资料' })}
${candidates.map(item => ``).join('')}
报名号 / 考生证件号码学校 / 班级账户状态更新时间资料状态操作
${h(item.name.slice(0,1))}
${h(item.name)}${h(item.candidateNumber || '待分配')}
${h(item.idNumberMasked)}${h(item.school || '未填写')}${h(item.grade || '')}${item.accountArchived ? '已归档' : item.mustChangePassword ? '待首次改密' : item.profileCompleted ? '正常' : '待补全资料'}${item.accountArchived ? `${formatDate(item.archivedAt, true)} · ${h(item.archivedByName || '校方')}` : h(item.workflow?.assignee?.displayName || '')}${formatDate(item.updatedAt,true)}${item.profileCompleted ? badge(item.status) : '未完成'}
`; } function adminRegistrations(registrations) { const isSuper = state.user.adminLevel === 'super'; 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'; 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.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 bulkToolbar = isSuper ? `
已选择 0 条待审核报名
` : ''; return `
${bulkToolbar}
${table(current, 'registrationTable', isSuper)}
${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 => `
${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 = `
`; return `${excelToolbar('payments', { importable: false, template: false, label: '缴费名单' })}
报名人数${registrations.length}
待确认${unpaid.length}
已缴费${paid.length}
应缴合计${money(totalDue)}
已缴合计${money(totalPaid)}
${filters}
${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: '填报已截止', matching: '投档中', school_review: '学校审核中', supplementary: '补录填报中', completed: '录取完成' }; 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 adminNotices(notices) { return `

发布状态会实时同步到公开首页和考生中心。

${notices.map(notice => ``).join('')}
通知标题分类作者发布时间展示状态操作
${h(notice.title)}${h(notice.summary)}${h(notice.category)}${h(notice.author)}${formatDate(notice.publishAt || notice.createdAt,true)}${notice.pinned ? '首页置顶' : '普通'}${badge(notice.status)}
`; } 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 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 entry = state.user.adminLevel === 'super' && !activeExam?.archivedAt ? `

单科成绩录入

按考试和科目缩小候选范围,已有成绩会自动回填。

实时校验

选择科目后显示其独立及格规则。

` : ''; 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}${ledger}${appealLedger}`; } function adminUsers(data) { return `
SELF REGISTRATION

考生自主注册

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

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

管理员层级决定可见范围和可执行操作;同一级可创建多名账号。

${data.admins.map(item => ``).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: '考生成绩复议' }; return `
${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 }; }