Refactor exam information workflow

This commit is contained in:
2026-07-20 10:07:14 +08:00 Unverified
parent d4e085b6c5
commit 7094e9b916
22 changed files with 3233 additions and 2793 deletions
+71
View File
@@ -0,0 +1,71 @@
export function createPublicViews(context) {
const {
state,
app,
h,
formatDate,
dateRange,
badge,
money,
statusLabels,
icons,
api,
renderError,
emptyState
} = context;
function brand() {
return `<a class="brand" href="#home" data-route="home"><span class="brand-symbol"><i></i><i></i><i></i></span><span><strong>衡准</strong><small>EXAM SERVICE</small></span></a>`;
}
function publicHeader() {
return `<header class="public-header"><div class="public-nav">${brand()}<nav><a href="#home" data-route="home">首页</a><a href="#home-exams" data-action="scroll-to" data-target="home-exams">考试报名</a><a href="#home-notices" data-action="scroll-to" data-target="home-notices">通知公告</a><a href="#service-flow" data-action="scroll-to" data-target="service-flow">办事指南</a></nav><div class="nav-actions">${state.user ? `<button class="text-button" data-route="${state.user.role}/dashboard">进入${state.user.role === 'admin' ? '管理后台' : '考生中心'}</button><button class="solid-button" data-action="logout">退出</button>` : `<button class="text-button" data-route="login">登录</button><button class="solid-button" data-route="register">考生注册</button>`}<button class="mobile-menu" data-action="toggle-public-nav" aria-label="打开导航">${icons.menu}</button></div></div></header>`;
}
function renderHome() {
const { notices, exams, stats, organization } = state.publicData;
const featured = exams.find(exam => exam.registrationState === 'open') || exams[0];
const topNotice = notices[0];
app.innerHTML = `${publicHeader()}<main class="public-main">
<section class="hero">
<div class="hero-grid">
<div class="hero-copy"><div class="notice-ticker"><span>最新</span><button data-action="open-notice" data-id="${h(topNotice?.id)}">${h(topNotice?.title || '欢迎使用衡准考试服务平台')}</button></div><p class="overline">HAIZHOU EXAMINATION SERVICE</p><h1>一个报名号,<br><em>贯穿每一次考试。</em></h1><p class="hero-lead">使用学校下发的报名号登录,完成密码更新和个人信息核验后,即可办理所有考试事项。</p><div class="hero-actions">${state.user?.role === 'candidate' ? `<button class="solid-button large" data-route="candidate/dashboard">进入考生中心 ${icons.arrow}</button>` : state.publicData.selfRegistrationEnabled ? `<button class="solid-button large" data-route="register">申请报名号 ${icons.arrow}</button>` : `<button class="solid-button large" data-route="login">使用报名号登录 ${icons.arrow}</button>`}<button class="ghost-button large" data-action="scroll-to" data-target="home-exams">查看开放考试</button></div><div class="hero-stats"><div><strong>${h(stats.candidates || 0)}</strong><span>在册考生</span></div><div><strong>${h(stats.registrations || 0)}</strong><span>报名记录</span></div><div><strong>${h(stats.exams || 0)}</strong><span>开放考试</span></div></div></div>
${featured ? renderHeroTicket(featured) : '<div class="hero-ticket empty-state">暂无开放考试</div>'}
</div>
</section>
<section class="content-section" id="home-notices"><div class="section-heading"><div><p class="overline">NOTICE BOARD</p><h2>通知公告</h2></div><p>报名、考试、准考证与成绩发布的重要时间,请以平台通知为准。</p></div><div class="notice-layout"><article class="featured-notice">${topNotice ? `<span>${h(topNotice.category)}</span><h3>${h(topNotice.title)}</h3><p>${h(topNotice.summary)}</p><footer><time>${formatDate(topNotice.publishAt)}</time><button data-action="open-notice" data-id="${h(topNotice.id)}">阅读通知 ${icons.arrow}</button></footer>` : '<p>暂无通知</p>'}</article><div class="notice-list">${notices.slice(1, 5).map(renderNoticeRow).join('') || '<div class="empty-state">暂无更多通知</div>'}</div></div></section>
<section class="content-section exam-section" id="home-exams"><div class="section-heading"><div><p class="overline">OPEN EXAMINATIONS</p><h2>考试报名</h2></div><p>登录后选择考试,并按实际需要勾选报考科目。</p></div><div class="public-exam-grid">${exams.map(renderPublicExam).join('') || '<div class="empty-state">当前没有已发布的考试</div>'}</div></section>
<section class="service-flow" id="service-flow"><div class="section-heading light"><div><p class="overline">SERVICE FLOW</p><h2>报名号是唯一账户</h2></div><p>报名号不会随考试改变,每场考试只新增一条报名记录。</p></div><div class="flow-track">${[['01','领取报名号','学校创建账户并下发初始密码。'],['02','修改初始密码','首次登录必须设置自己的新密码。'],['03','补全个人信息','填写籍贯、住址、手机、邮箱和班级等资料。'],['04','选择考试科目','资料审核通过后自主选择考试。'],['05','下载准考证与查分','继续使用同一报名号办理后续事项。']].map(item => `<article><span>${item[0]}</span><h3>${item[1]}</h3><p>${item[2]}</p></article>`).join('')}</div></section>
</main><footer class="public-footer"><div>${brand()}<p>${h(organization.name || '海州市教育考试中心')} · ${h(organization.phone || '')}</p></div><span>本平台展示数据仅用于系统演示</span></footer>`;
}
function renderHeroTicket(exam) {
const status = exam.registrationState;
return `<article class="hero-ticket"><div class="ticket-main"><header><span>${badge(status)}</span><small>${h(exam.code)}</small></header><p>UPCOMING EXAM</p><h2>${h(exam.name)}</h2><dl><div><dt>报名时间</dt><dd>${dateRange(exam.registrationStart, exam.registrationEnd)}</dd></div><div><dt>考试时间</dt><dd>${dateRange(exam.examStart, exam.examEnd)}</dd></div><div><dt>考试地点</dt><dd>${h(exam.location)}</dd></div></dl><div class="subject-chips">${exam.subjects.slice(0, 5).map(subject => `<span>${h(subject.name)}</span>`).join('')}${exam.subjects.length > 5 ? `<span>+${exam.subjects.length - 5}</span>` : ''}</div></div><div class="ticket-stub"><span>报名人数</span><strong>${h(exam.registrationCount || 0)}</strong><i></i><button data-route="${state.user?.role === 'candidate' ? 'candidate/exams' : 'login'}">${status === 'open' ? '立即报名' : '查看详情'}</button></div></article>`;
}
function renderNoticeRow(notice) {
return `<button class="notice-row" data-action="open-notice" data-id="${h(notice.id)}"><time>${formatDate(notice.publishAt)}</time><span><em>${h(notice.category)}</em><strong>${h(notice.title)}</strong><small>${h(notice.summary)}</small></span>${icons.arrow}</button>`;
}
function renderPublicExam(exam) {
return `<article class="public-exam-card"><header><span class="exam-code">${h(exam.code)}</span>${badge(exam.registrationState)}</header><h3>${h(exam.name)}</h3><p>${h(exam.description)}</p><div class="exam-meta"><span><b>报名</b>${dateRange(exam.registrationStart, exam.registrationEnd)}</span><span><b>考试</b>${dateRange(exam.examStart, exam.examEnd)}</span></div><footer><span>${exam.subjects.length} 个科目 · ${exam.registrationCount || 0} 人已报名</span><button data-route="${state.user?.role === 'candidate' ? 'candidate/exams' : 'login'}">${exam.registrationState === 'open' ? '选择科目' : '查看考试'} ${icons.arrow}</button></footer></article>`;
}
function renderAuth(kind) {
const login = kind === 'login';
const selfRegistration = state.publicData.selfRegistrationEnabled;
app.innerHTML = `<main class="auth-page"><section class="auth-story"><div>${brand()}<p class="overline">CANDIDATE SERVICE</p><h1>${login ? '凭一个号码,' : '自主申请,'}<br><em>${login ? '办理每一次考试。' : '领取固定报名号。'}</em></h1><p>报名号就是考生账户,不因考试、科目或年度报名而改变。</p></div><div class="auth-quote"><span>首次登录顺序</span><p>修改初始密码 → 补全个人信息 → 等待资料审核。</p></div></section><section class="auth-panel"><button class="back-link" data-route="home">← 返回首页</button><div class="auth-card"><p class="overline">${login ? 'ACCOUNT LOGIN' : 'CANDIDATE NUMBER'}</p><h2>${login ? '报名号登录' : '自主申请报名号'}</h2><p>${login ? '考生填写报名号和密码;管理员继续使用管理账号。' : selfRegistration ? '提交基础学籍范围后,系统生成一个长期使用的报名号。' : '当前未开放自主注册,请联系学校领取报名号和初始密码。'}</p>${login ? loginForm() : selfRegistration ? registerForm() : '<div class="registration-closed"><strong>自主注册已关闭</strong><span>学校管理员会为考生创建账户并下发初始密码。</span><button class="solid-button" data-route="login">返回报名号登录</button></div>'}${login && selfRegistration ? `<div class="auth-switch">还没有报名号?<button data-route="register">自主申请</button></div>` : !login ? '<div class="auth-switch">已经有报名号?<button data-route="login">返回登录</button></div>' : ''}${login ? `<div class="demo-accounts"><strong>演示账号</strong><button data-action="fill-demo" data-type="candidate">考生:2026-HZ01-F-0001 / Candidate123!</button><button data-action="fill-demo" data-type="admin">超级管理员:admin / Admin123!</button><button data-action="fill-demo" data-type="school">校级管理员:school_admin / School123!</button><button data-action="fill-demo" data-type="class">班级管理员:class_admin / Class123!</button></div>` : ''}</div></section></main>`;
}
function loginForm() {
return `<form class="stack-form" data-form="login"><label><span>报名号 / 管理员账号</span><input name="username" autocomplete="username" required placeholder="例如 2026-HZ01-F-0001"></label><label><span>密码</span><input name="password" type="password" autocomplete="current-password" required placeholder="首次登录请输入学校下发的初始密码"></label><button class="solid-button large" type="submit">登录系统 ${icons.arrow}</button></form>`;
}
function registerForm() {
const schools = state.publicData.schools || [];
return `<form class="stack-form register-form" data-form="register"><div class="field-row"><label><span>考生姓名 *</span><input name="name" required placeholder="与证件一致"></label><label><span>性别 *</span><select name="gender" required><option value="">请选择</option><option>男</option><option>女</option></select></label></div><div class="field-row"><label><span>就读学校 *</span><select name="schoolId" data-action="school-select" required><option value="">请选择学校</option>${schools.map(item => `<option value="${h(item.id)}">${h(item.name)}</option>`).join('')}</select></label><label><span>班级 *</span><select name="classId" required><option value="">请先选择学校</option></select></label></div><label><span>设置登录密码 *</span><input name="password" type="password" required minlength="8" placeholder="至少 8 位字符"></label><label class="agreement"><input type="checkbox" required><span>我会妥善保存系统生成的报名号,并在登录后补全真实个人信息。</span></label><button class="solid-button large" type="submit">生成我的报名号 ${icons.arrow}</button></form>`;
}
return { brand, renderHome, renderAuth };
}