优化2 patch2

This commit is contained in:
2026-07-21 16:22:29 +08:00 Unverified
parent 4929858ec2
commit 722bb6f016
13 changed files with 381 additions and 65 deletions
+90 -3
View File
@@ -177,6 +177,39 @@ function applyTableFilters(tableId) {
if (table.closest('.qualification-ledger')) updateQualificationSelection(table.closest('.qualification-ledger'));
}
function updateResultScoreInput(input) {
const row = input.closest('tr');
const error = row?.querySelector('[data-score-error]');
const value = input.value.trim();
const score = Number(value);
const max = Number(input.max);
const invalid = value !== '' && (!Number.isFinite(score) || score < 0 || score > max);
input.dataset.dirty = String(value !== input.defaultValue);
input.setAttribute('aria-invalid', String(invalid));
row?.classList.toggle('score-row-invalid', invalid);
if (error) error.textContent = invalid ? `须在 0—${max} 之间` : '';
const form = input.closest('form');
const dirty = form?.querySelectorAll('[data-result-score][data-dirty="true"]').length || 0;
const count = form?.querySelector('[data-result-dirty-count]');
if (count) count.textContent = dirty ? `${dirty} 条成绩尚未暂存` : '尚无未保存修改';
}
function updateFeatureScoreInput(input) {
const row = input.closest('tr');
const error = row?.querySelector('[data-feature-score-error]');
const value = input.value.trim();
const score = Number(value);
const invalid = value === '' || !Number.isFinite(score) || score < 0 || score > 1000;
input.dataset.dirty = String(value !== input.defaultValue);
input.setAttribute('aria-invalid', String(invalid));
row?.classList.toggle('score-row-invalid', invalid);
if (error) error.textContent = invalid ? '须在 0—1000 之间' : '';
const form = input.closest('form');
const dirty = form?.querySelectorAll('[data-feature-score][data-dirty="true"]').length || 0;
const count = form?.querySelector('[data-feature-dirty-count]');
if (count) count.textContent = dirty ? `${dirty} 条特征分尚未保存` : '尚无未保存修改';
}
async function refreshPublic() {
state.publicData = await api('/api/public/home');
}
@@ -266,7 +299,7 @@ document.addEventListener('click', async event => {
const reviewNote = window.prompt(target.dataset.status === 'approved' ? '填写审核意见(可留空)' : '请填写退回原因', '') ?? null;
if (reviewNote == null) return;
await api(`/api/admin/admission-plans/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status, reviewNote } });
toast(target.dataset.status === 'approved' ? '招生计划已通过' : '招生计划已退回'); return renderRoute();
toast(target.dataset.status === 'approved' ? '招生计划已通过并自动公示' : '招生计划已退回', target.dataset.status === 'approved' ? '公开通知公告已同步生成招生计划公示' : '招生学校可以修改后重新提交'); return renderRoute();
}
if (action === 'admission-match') {
if (!window.confirm('确认按“分数优先、遵循志愿”执行本轮投档?填报顺序将锁定。')) return;
@@ -517,6 +550,7 @@ document.addEventListener('click', async event => {
if (target.dataset.template === '1') query.set('template', '1');
if (target.dataset.batchId) query.set('batchId', target.dataset.batchId);
if (target.dataset.examId) query.set('examId', target.dataset.examId);
else if (target.dataset.resource === 'results' && state.resultExamFilter) query.set('examId', state.resultExamFilter);
window.location.href = `/api/admin/excel/${target.dataset.resource}?${query}`;
return;
}
@@ -526,6 +560,7 @@ document.addEventListener('click', async event => {
}
if (action === 'result-exam-filter') {
state.resultExamFilter = target.dataset.id;
state.resultSubjectFilter = '';
return renderRoute();
}
if (action === 'cancel-result-import') {
@@ -585,6 +620,8 @@ document.addEventListener('input', event => {
if (event.target.matches('[data-action="table-search"]')) {
applyTableFilters(event.target.dataset.target);
}
if (event.target.matches('[data-result-score]')) updateResultScoreInput(event.target);
if (event.target.matches('[data-feature-score]')) updateFeatureScoreInput(event.target);
if (event.target.matches('.subject-options input')) {
const form = event.target.closest('form');
const checked = [...form.querySelectorAll('.subject-options input:checked')];
@@ -598,6 +635,15 @@ document.addEventListener('input', event => {
});
document.addEventListener('change', event => {
if (event.target.matches('[data-action="result-bulk-exam"]')) {
state.resultExamFilter = event.target.value;
state.resultSubjectFilter = '';
renderRoute();
}
if (event.target.matches('[data-action="result-bulk-subject"]')) {
state.resultSubjectFilter = event.target.value;
renderRoute();
}
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();
@@ -742,7 +788,7 @@ document.addEventListener('submit', async event => {
const form = event.target.closest('form[data-form]');
if (!form) return;
event.preventDefault();
const submit = form.querySelector('button[type="submit"]');
const submit = event.submitter || form.querySelector('button[type="submit"]');
const original = submit?.innerHTML;
if (submit) { submit.disabled = true; submit.textContent = '正在处理…'; }
try {
@@ -943,10 +989,42 @@ document.addEventListener('submit', async event => {
const body = formObject(form); body.published = form.published.checked;
await api('/api/admin/results', { method: 'POST', body });
toast(body.published ? '成绩已发布' : '成绩已保存', '考生端可见状态已更新'); renderRoute();
} else if (kind === 'result-bulk-entry') {
const mode = event.submitter?.dataset.resultMode || 'draft';
const inputs = [...form.querySelectorAll('[data-result-score]')];
const invalid = inputs.find(input => input.value.trim() !== '' && (!Number.isFinite(Number(input.value)) || Number(input.value) < 0 || Number(input.value) > Number(input.max)));
if (invalid) { invalid.focus(); throw new Error(`成绩须在 0—${invalid.max} 之间`); }
const targets = mode === 'publish'
? inputs.filter(input => input.value.trim() !== '')
: inputs.filter(input => input.dataset.dirty === 'true' && input.value.trim() !== '');
if (mode === 'publish') {
const missing = inputs.filter(input => input.value.trim() === '');
if (missing.length) { missing[0].focus(); throw new Error(`还有 ${missing.length} 名考生未录入成绩,补齐后才能整科发布`); }
if (!window.confirm(`确认发布本科学目 ${targets.length} 名考生的成绩吗?发布后考生可立即查询。`)) return;
}
if (!targets.length) throw new Error(mode === 'publish' ? '当前科目没有可发布的成绩' : '请先修改至少一条成绩');
const body = {
examId: form.examId.value,
subjectId: form.subjectId.value,
published: mode === 'publish',
rows: targets.map(input => ({ registrationId: input.dataset.registrationId, score: Number(input.value) }))
};
const result = await api('/api/admin/results/bulk', { method: 'POST', body });
toast(mode === 'publish' ? '本科学目成绩已发布' : '成绩已暂存', `${result.count} 条成绩已在同一事务中保存`);
renderRoute();
} else if (kind === 'feature-score-entry') {
const body = formObject(form); body.featureScore = Number(body.featureScore || 0);
await api(`/api/admin/registrations/${body.registrationId}/feature-score`, { method: 'PATCH', body });
toast('特征分已登记', '该分数独立于考试科目,默认值为 0'); renderRoute();
} else if (kind === 'feature-score-bulk') {
const inputs = [...form.querySelectorAll('[data-feature-score]')];
const invalid = inputs.find(input => input.value.trim() === '' || !Number.isFinite(Number(input.value)) || Number(input.value) < 0 || Number(input.value) > 1000);
if (invalid) { invalid.focus(); throw new Error('特征分必须在 0—1000 之间;未参加时填写 0'); }
const targets = inputs.filter(input => input.dataset.dirty === 'true');
if (!targets.length) throw new Error('请先修改至少一名考生的特征分');
const result = await api('/api/admin/feature-scores/bulk', { method: 'POST', body: { examId: form.examId.value, rows: targets.map(input => ({ registrationId: input.dataset.registrationId, featureScore: Number(input.value) })) } });
toast('特征分已保存', `${result.count} 名考生已更新;其余考生继续保持 0 分`);
renderRoute();
} else if (kind === 'result-import-commit') {
const rows = state.resultImportPreview?.rows || [];
if (!rows.length) throw new Error('没有可提交的成绩预览');
@@ -1184,7 +1262,16 @@ function openAdmitPreview(reg) {
}
window.addEventListener('hashchange', renderRoute);
window.addEventListener('keydown', event => { if (event.key === 'Escape') closeModal(); });
window.addEventListener('keydown', event => {
if (event.key === 'Escape') closeModal();
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
const form = document.querySelector('form[data-form="result-bulk-entry"]');
if (form) {
event.preventDefault();
form.requestSubmit(form.querySelector('[data-result-mode="draft"]'));
}
}
});
try {
await Promise.all([refreshPublic(), refreshSession()]);