This commit is contained in:
2026-07-21 15:07:19 +08:00 Unverified
parent 6be1ff227a
commit 0b423e565b
8 changed files with 222 additions and 36 deletions
+62 -5
View File
@@ -59,7 +59,7 @@ function renderError(error) {
}
const baseViewContext = { state, app, h, formatDate, dateRange, badge, money, passPolicyText, statusLabels, icons, api, renderError, emptyState };
const { brand, renderHome, renderAnnouncements, renderAuth } = createPublicViews(baseViewContext);
const { brand, renderHome, renderNoticeCenter, renderAuth } = createPublicViews(baseViewContext);
const { adminNavForUser, portalShell, loadingPanel, renderCandidate, accountSecurity } = createCandidateViews({ ...baseViewContext, brand });
const { renderAdmin, workflowStepEditor } = createAdminViews({ ...baseViewContext, brand, portalShell, loadingPanel, adminNavForUser, accountSecurity });
const { renderAdmission } = createAdmissionViews({ ...baseViewContext, brand });
@@ -75,7 +75,8 @@ async function renderRoute() {
const route = location.hash.slice(1) || 'home';
const [section, page = 'dashboard'] = route.split('/');
if (section === 'home') renderHome();
else if (section === 'announcements') { state.publicAnnouncements = await api('/api/public/announcements'); renderAnnouncements(state.publicAnnouncements); }
else if (section === 'notices' || section === 'announcements') { state.publicAnnouncements = await api('/api/public/announcements'); renderNoticeCenter(state.publicAnnouncements); }
else if (section === 'notice') { state.publicAnnouncements = await api('/api/public/announcements'); renderNoticeCenter(state.publicAnnouncements, page); }
else if (section === 'login' || section === 'register') renderAuth(section);
else if (section === 'candidate') await renderCandidate(page);
else if (section === 'admin') await renderAdmin(page);
@@ -103,6 +104,19 @@ function updateRegistrationSelection() {
document.querySelectorAll('[data-action="bulk-registration-review"]').forEach(button => { button.disabled = selected.length === 0; });
}
function updateQualificationSelection(container) {
if (!container) return;
const rows = [...container.querySelectorAll('[data-qualification-select]')];
const selected = rows.filter(input => input.checked);
const selectAll = container.querySelector('[data-action="qualification-select-all"]');
if (selectAll) {
selectAll.checked = rows.length > 0 && selected.length === rows.length;
selectAll.indeterminate = selected.length > 0 && selected.length < rows.length;
}
const count = container.querySelector('[data-qualification-selected-count]');
if (count) count.textContent = `已选 ${selected.length}`;
}
function applyTableFilters(tableId) {
const table = document.getElementById(tableId);
if (!table) return;
@@ -182,9 +196,20 @@ document.addEventListener('click', async event => {
await refreshPublic(); navigate('home'); toast('已安全退出', '期待下次见面'); return;
}
if (action === 'open-notice') {
const notice = state.publicData.notices.find(item => item.id === target.dataset.id) || (await api(`/api/public/notices/${target.dataset.id}`)).notice;
const contentHtml = notice.contentHtml || `<p>${h(notice.content).replace(/\r?\n/g, '</p><p>')}</p>`;
setModal(`<div class="modal-head"><div><span>${h(notice.category)}</span><h2>${h(notice.title)}</h2><p>${formatDate(notice.publishAt,true)} · ${h(notice.author)}</p></div><button data-action="close-modal">×</button></div><article class="notice-content">${contentHtml}</article><div class="modal-foot"><button class="ghost-button" data-action="close-modal">关闭</button></div>`); return;
return navigate(`notice/${target.dataset.id}`);
}
if (action === 'notice-category') {
state.noticeCategory = target.dataset.category;
state.noticePage = 1;
renderNoticeCenter(state.publicAnnouncements);
return;
}
if (action === 'notice-page') {
if (target.disabled) return;
state.noticePage = Number(target.dataset.page || 1);
renderNoticeCenter(state.publicAnnouncements);
window.scrollTo({ top: 0, behavior: 'smooth' });
return;
}
if (action === 'download-admit') { window.location.href = `/api/candidate/registrations/${target.dataset.id}/admit-card`; return; }
if (action === 'download-admitted-candidates') {
@@ -230,6 +255,37 @@ document.addEventListener('click', async event => {
toast(data.published ? '资格已确认并自动公示' : '资格已确认', data.published ? '本校全部考生已确认完成' : '继续核对其他考生');
return renderRoute();
}
if (action === 'qualification-select-all') {
const container = target.closest('[data-qualification-bulk]')?.closest('.qualification-ledger');
container?.querySelectorAll('[data-qualification-select]').forEach(input => { input.checked = target.checked; });
updateQualificationSelection(container);
return;
}
if (action === 'bulk-indicator-qualification') {
const toolbar = target.closest('[data-qualification-bulk]');
const ledger = toolbar?.closest('.qualification-ledger');
const userIds = [...ledger.querySelectorAll('[data-qualification-select]:checked')].map(input => input.closest('tr').dataset.userId);
const value = toolbar.querySelector('[data-bulk-eligible]').value;
if (!userIds.length) return toast('请先选择考生', '可使用左侧复选框或全选');
if (!value) return toast('请选择批量资格结论');
if (!window.confirm(`确认将所选 ${userIds.length} 名考生批量设为“${value === 'true' ? '有' : '无'}指标分配资格”?`)) return;
const data = await api(`/api/admin/indicator-qualifications/${toolbar.dataset.examId}/bulk`, { method: 'PUT', body: { userIds, eligible: value === 'true' } });
toast(data.published ? '批量确认完成并自动公示' : '批量确认完成', `已更新 ${data.count} 名考生`);
return renderRoute();
}
if (action === 'toggle-admin-account') {
const active = target.dataset.active === 'true';
if (!active && !window.confirm('确认停用该管理员账户?其现有登录会话会立即失效,历史审批记录将保留。')) return;
await api(`/api/admin/admins/${target.dataset.id}`, { method: 'PATCH', body: { active } });
toast(active ? '管理员账户已启用' : '管理员账户已停用', active ? '该账号可以重新登录' : '现有会话已结束,历史记录仍保留');
return renderRoute();
}
if (action === 'reset-admin-password') {
if (!window.confirm('确认重置该管理员密码?其现有登录会话会立即失效。')) return;
const data = await api(`/api/admin/admins/${target.dataset.id}/reset-password`, { method: 'POST' });
setModal(`<div class="modal-head"><div><span>ADMIN PASSWORD RESET</span><h2>管理员临时密码已生成</h2><p>请通过线下安全渠道交给管理员,本页面关闭后不再展示。</p></div><button data-action="close-modal">×</button></div><div class="issued-number"><span>登录账号</span><strong>${h(data.username)}</strong><span>临时密码</span><strong>${h(data.temporaryPassword)}</strong><p>旧密码和现有登录会话均已失效。</p></div><div class="modal-foot"><button class="solid-button" data-action="close-modal">我已保存</button></div>`);
return;
}
if (action === 'add-admission-category') {
const sources = state.pageData?.sourceSchools || [];
target.closest('form')?.querySelector('[data-admission-categories]')?.insertAdjacentHTML('beforeend', admissionCategoryEditor(h, sources));
@@ -451,6 +507,7 @@ document.addEventListener('input', event => {
});
document.addEventListener('change', event => {
if (event.target.matches('[data-qualification-select]')) updateQualificationSelection(event.target.closest('.qualification-ledger'));
if (event.target.matches('[data-table-filter]')) applyTableFilters(event.target.dataset.target);
if (event.target.matches('[data-registration-select]')) updateRegistrationSelection();
if (event.target.matches('[data-registration-select-all]')) {