Add candidate account archiving and password reset

This commit is contained in:
2026-07-20 16:30:06 +08:00 Unverified
parent 9f850fa045
commit 7023493f08
12 changed files with 207 additions and 41 deletions
+15 -8
View File
@@ -20,15 +20,17 @@ export function createCandidateViews(context) {
const candidateNav = [
['dashboard', '总览', 'home'], ['profile', '个人资料', 'user'], ['exams', '考试报名', 'exam'],
['registrations', '我的报名', 'check'], ['admit', '准考证', 'ticket'], ['results', '成绩查询', 'chart'], ['notices', '通知公告', 'bell']
['registrations', '我的报名', 'check'], ['admit', '准考证', 'ticket'], ['results', '成绩查询', 'chart'], ['notices', '通知公告', 'bell'],
['security', '账户安全', 'user']
];
function adminNavForUser() {
const level = state.user?.adminLevel || 'super';
const core = [['dashboard', '工作台', 'home'], ['candidates', level === 'class' ? '本班考生' : '考生信息', 'users'], ['registrations', level === 'class' ? '报名状态' : '报名审核', 'check'], ['results', level === 'super' ? '成绩发布' : '成绩查看', 'chart']];
if (level === 'class') return [core[0], core[1], core[2], ['admit', '本班准考证', 'ticket'], ['flows', '流程中心', 'check'], core[3]];
const security = ['security', '账户安全', 'user'];
if (level === 'class') return [core[0], core[1], core[2], ['admit', '本班准考证', 'ticket'], ['flows', '流程中心', 'check'], core[3], security];
const operations = [['flows', '流程中心', 'check'], ['centers', '考场信息', 'exam']];
if (level === 'school') return [core[0], ['organization', '本校组织', 'users'], ['account-batches', '批量建号', 'ticket'], core[1], core[2], ['admit', '校内准考证', 'ticket'], ...operations, core[3]];
return [core[0], ['admins', '管理员', 'users'], core[1], core[2], ['flows', '流程监督', 'check'], ['flow-design', '流程设计', 'exam'], ['number-rules', '报名号规则', 'ticket'], ['centers', '考场信息', 'exam'], ['exams', '考试与科目', 'exam'], ['notices', '通知发布', 'bell'], ['admit', '准考证编排', 'ticket'], core[3]];
if (level === 'school') return [core[0], ['organization', '本校组织', 'users'], ['account-batches', '批量建号', 'ticket'], core[1], core[2], ['admit', '校内准考证', 'ticket'], ...operations, core[3], security];
return [core[0], ['admins', '管理员', 'users'], core[1], core[2], ['flows', '流程监督', 'check'], ['flow-design', '流程设计', 'exam'], ['number-rules', '报名号规则', 'ticket'], ['centers', '考场信息', 'exam'], ['exams', '考试与科目', 'exam'], ['notices', '通知发布', 'bell'], ['admit', '准考证编排', 'ticket'], core[3], security];
}
function portalShell(role, page, content, title, description) {
@@ -83,19 +85,20 @@ export function createCandidateViews(context) {
registrations: ['我的报名', '查看已提交的考试、科目与审核进度。'],
admit: ['准考证', '管理员生成后,可在规定下载时间内保存准考证。'],
results: ['成绩查询', '仅显示考试中心已经正式发布的成绩。'],
notices: ['通知公告', '查看与报名、考试和成绩相关的最新消息。']
notices: ['通知公告', '查看与报名、考试和成绩相关的最新消息。'],
security: ['账户安全', '使用当前密码设置新的登录密码。']
};
if (!meta[page]) page = 'dashboard';
app.innerHTML = portalShell('candidate', page, loadingPanel(), ...meta[page]);
try {
const endpoint = page === 'dashboard' ? 'dashboard' : page === 'profile' ? 'profile' : page === 'exams' ? 'exams' : page === 'results' ? 'results' : 'registrations';
const data = page === 'notices' ? { notices: state.publicData.notices } : await api(`/api/candidate/${endpoint}`);
const data = page === 'notices' ? { notices: state.publicData.notices } : page === 'security' ? {} : await api(`/api/candidate/${endpoint}`);
state.pageData = data;
if (data.profile) state.profile = data.profile;
const content = {
dashboard: () => candidateDashboard(data), profile: () => candidateProfile(data), exams: () => candidateExams(data),
registrations: () => candidateRegistrations(data.registrations), admit: () => candidateAdmit(data.registrations),
results: () => candidateResults(data), notices: () => candidateNotices(data.notices)
results: () => candidateResults(data), notices: () => candidateNotices(data.notices), security: () => accountSecurity()
}[page]();
app.innerHTML = portalShell('candidate', page, content, ...meta[page]);
if (page === 'profile') mountRegionSelects(app, data.profile, { className: 'region-selects wide-field' });
@@ -172,5 +175,9 @@ export function createCandidateViews(context) {
return `<section class="panel notice-center"><div class="notice-center-list">${notices.map(notice => `<button data-action="open-notice" data-id="${h(notice.id)}"><time><strong>${new Date(notice.publishAt).getDate()}</strong><span>${new Date(notice.publishAt).toLocaleString('zh-CN',{month:'short'})}</span></time><span><em>${h(notice.category)}</em><strong>${h(notice.title)}</strong><small>${h(notice.summary)}</small></span>${notice.pinned ? '<i>置顶</i>' : ''}${icons.arrow}</button>`).join('')}</div></section>`;
}
return { adminNavForUser, portalShell, loadingPanel, renderCandidate };
function accountSecurity() {
return `<section class="panel account-security-panel"><div class="account-security-copy"><span>LOGIN PASSWORD</span><h2>修改登录密码</h2><p>密码修改成功后立即生效。请使用至少 8 位、且与当前密码不同的新密码。</p><dl><div><dt>当前账号</dt><dd class="mono">${h(state.user?.candidateNumber || state.user?.username)}</dd></div><div><dt>账户类型</dt><dd>${state.user?.role === 'candidate' ? '考生账户' : statusLabels[state.user?.adminLevel] || '管理员账户'}</dd></div></dl></div><form class="stack-form account-password-form" data-form="account-password"><label><span>当前密码</span><input name="currentPassword" type="password" autocomplete="current-password" required></label><label><span>新密码</span><input name="newPassword" type="password" autocomplete="new-password" minlength="8" required></label><label><span>再次输入新密码</span><input name="confirmPassword" type="password" autocomplete="new-password" minlength="8" required></label><button class="solid-button large" type="submit">保存新密码</button></form></section>`;
}
return { adminNavForUser, portalShell, loadingPanel, renderCandidate, accountSecurity };
}