优化2 patch2

This commit is contained in:
biss committed 2026-07-21 16:22:29 +08:00
1 parent 4929858ec2
commit 722bb6f016
13 files changed
+377 -61

No files matched your search

+90 -3
View File
@@ -177,6 +177,39 @@ function applyTableFilters(tableId) {
if (table.closest('.qualification-ledger')) updateQualificationSelection(table.closest('.qualification-ledger')); 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() { async function refreshPublic() {
state.publicData = await api('/api/public/home'); 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; const reviewNote = window.prompt(target.dataset.status === 'approved' ? '填写审核意见(可留空)' : '请填写退回原因', '') ?? null;
if (reviewNote == null) return; if (reviewNote == null) return;
await api(`/api/admin/admission-plans/${target.dataset.id}`, { method: 'PATCH', body: { status: target.dataset.status, reviewNote } }); 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 (action === 'admission-match') {
if (!window.confirm('确认按“分数优先、遵循志愿”执行本轮投档?填报顺序将锁定。')) return; 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.template === '1') query.set('template', '1');
if (target.dataset.batchId) query.set('batchId', target.dataset.batchId); if (target.dataset.batchId) query.set('batchId', target.dataset.batchId);
if (target.dataset.examId) query.set('examId', target.dataset.examId); 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}`; window.location.href = `/api/admin/excel/${target.dataset.resource}?${query}`;
return; return;
} }
@@ -526,6 +560,7 @@ document.addEventListener('click', async event => {
} }
if (action === 'result-exam-filter') { if (action === 'result-exam-filter') {
state.resultExamFilter = target.dataset.id; state.resultExamFilter = target.dataset.id;
state.resultSubjectFilter = '';
return renderRoute(); return renderRoute();
} }
if (action === 'cancel-result-import') { if (action === 'cancel-result-import') {
@@ -585,6 +620,8 @@ document.addEventListener('input', event => {
if (event.target.matches('[data-action="table-search"]')) { if (event.target.matches('[data-action="table-search"]')) {
applyTableFilters(event.target.dataset.target); 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')) { if (event.target.matches('.subject-options input')) {
const form = event.target.closest('form'); const form = event.target.closest('form');
const checked = [...form.querySelectorAll('.subject-options input:checked')]; const checked = [...form.querySelectorAll('.subject-options input:checked')];
@@ -598,6 +635,15 @@ document.addEventListener('input', event => {
}); });
document.addEventListener('change', 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-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-table-filter]')) applyTableFilters(event.target.dataset.target);
if (event.target.matches('[data-registration-select]')) updateRegistrationSelection(); 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]'); const form = event.target.closest('form[data-form]');
if (!form) return; if (!form) return;
event.preventDefault(); event.preventDefault();
const submit = form.querySelector('button[type="submit"]'); const submit = event.submitter || form.querySelector('button[type="submit"]');
const original = submit?.innerHTML; const original = submit?.innerHTML;
if (submit) { submit.disabled = true; submit.textContent = '正在处理…'; } if (submit) { submit.disabled = true; submit.textContent = '正在处理…'; }
try { try {
@@ -943,10 +989,42 @@ document.addEventListener('submit', async event => {
const body = formObject(form); body.published = form.published.checked; const body = formObject(form); body.published = form.published.checked;
await api('/api/admin/results', { method: 'POST', body }); await api('/api/admin/results', { method: 'POST', body });
toast(body.published ? '成绩已发布' : '成绩已保存', '考生端可见状态已更新'); renderRoute(); 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') { } else if (kind === 'feature-score-entry') {
const body = formObject(form); body.featureScore = Number(body.featureScore || 0); const body = formObject(form); body.featureScore = Number(body.featureScore || 0);
await api(`/api/admin/registrations/${body.registrationId}/feature-score`, { method: 'PATCH', body }); await api(`/api/admin/registrations/${body.registrationId}/feature-score`, { method: 'PATCH', body });
toast('特征分已登记', '该分数独立于考试科目,默认值为 0'); renderRoute(); 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') { } else if (kind === 'result-import-commit') {
const rows = state.resultImportPreview?.rows || []; const rows = state.resultImportPreview?.rows || [];
if (!rows.length) throw new Error('没有可提交的成绩预览'); if (!rows.length) throw new Error('没有可提交的成绩预览');
@@ -1184,7 +1262,16 @@ function openAdmitPreview(reg) {
} }
window.addEventListener('hashchange', renderRoute); 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 { try {
await Promise.all([refreshPublic(), refreshSession()]); await Promise.all([refreshPublic(), refreshSession()]);
+8
View File
@@ -1246,6 +1246,14 @@ function createRepository({ client, location, read, transaction, close }) {
auditOperation(log) auditOperation(log)
]); ]);
}, },
async updateFeatureScores(entries) {
const operations = [];
for (const { registration, log } of entries) {
operations.push(operation('UPDATE registrations SET feature_score = ? WHERE id = ?', Number(registration.featureScore || 0), registration.id));
operations.push(auditOperation(log));
}
await transaction(operations);
},
async saveAdmissionRecord(record, log = null) { async saveAdmissionRecord(record, log = null) {
const operations = [operation('DELETE FROM admission_records WHERE id = ?', record.id), operation( const operations = [operation('DELETE FROM admission_records WHERE id = ?', record.id), operation(
`INSERT INTO admission_records ( `INSERT INTO admission_records (
+2 -1
View File
@@ -85,7 +85,8 @@ const resourceSpecs = {
results: { results: {
title: '考试成绩台账', sheet: '成绩', title: '考试成绩台账', sheet: '成绩',
columns: [ columns: [
['candidateNumber', '报名号*', 26, '2026-HZ01-X-0001'], ['candidateName', '姓名(只读参考)', 16, ''], ['candidateNumber', '报名号*', 26, '2026-HZ01-X-0001'], ['cardNumber', '准考证号(只读参考)', 24, ''],
['candidateName', '姓名(只读参考)', 16, ''], ['schoolName', '学校(只读参考)', 24, ''], ['className', '班级(只读参考)', 20, ''],
['examCode', '考试代码*', 20, 'EX-2026-AUT'], ['examName', '考试名称(只读参考)', 28, ''], ['examCode', '考试代码*', 20, 'EX-2026-AUT'], ['examName', '考试名称(只读参考)', 28, ''],
['subjectName', '科目*', 16, '语文'], ['fullScore', '科目满分(只读参考)', 18, ''], ['subjectName', '科目*', 16, '语文'], ['fullScore', '科目满分(只读参考)', 18, ''],
['passRule', '单科及格规则(只读参考)', 26, ''], ['passScore', '实际及格分(只读参考)', 20, ''], ['passRule', '单科及格规则(只读参考)', 26, ''], ['passScore', '实际及格分(只读参考)', 20, ''],
Loaded 3 of 13 files, more files were not shown because too many files have changed in this diff. Show more