优化2 patch2
This commit is contained in:
1 parent
4929858ec2
commit
722bb6f016
13 files changed
+381
-65
No files matched your search
@@ -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()]);
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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
Reference in new issue
Block a user