fix: explain import blockers and recompute selected identities
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
const fields = ['student_no', 'name', 'topic'] as const;
|
||||
type IdentityField = typeof fields[number];
|
||||
const labels = { student_no: '学号', name: '姓名', topic: '选题' };
|
||||
const categories: Record<string, string> = { stage1: '阶段报告1', stage2: '阶段报告2', stage3: '阶段报告3',
|
||||
final_report: '最终报告', presentation: '汇报PPT/PDF' };
|
||||
const text = (value: unknown) => String(value ?? '').trim();
|
||||
const fileName = (file: any) => text(file.relative_path).split('/').pop() || '文件';
|
||||
|
||||
export function reviewImport(project: any) {
|
||||
const selected = (project.files || []).filter((file: any) => file.status !== 'ignored');
|
||||
const metadata = (project.files || []).some((file: any) => file.detected_identity != null);
|
||||
const identity: Record<IdentityField, string> = { student_no: '', name: '', topic: '' };
|
||||
const warnings: string[] = [];
|
||||
for (const field of fields) {
|
||||
const sources = new Map<string, string[]>();
|
||||
for (const file of selected) {
|
||||
const value = text(file.detected_identity?.[field]);
|
||||
if (value) sources.set(value, [...(sources.get(value) || []), fileName(file)]);
|
||||
}
|
||||
const folderValue = text(project.folder_identity?.[field]);
|
||||
const values = [...sources.keys()];
|
||||
identity[field] = folderValue || (values.length === 1 ? values[0] : '');
|
||||
if (values.length > 1) {
|
||||
warnings.push(`${labels[field]}冲突:${values.map(value => `${value}(${sources.get(value)!.join('、')})`).join(';')}`);
|
||||
} else if (folderValue && values.length === 1 && folderValue !== values[0]) {
|
||||
warnings.push(`${labels[field]}与目录不一致:封面为${values[0]}(${sources.get(values[0])!.join('、')}),目录为${folderValue}`);
|
||||
}
|
||||
}
|
||||
if (!metadata) warnings.push(...(project.warnings || []));
|
||||
|
||||
const errors: string[] = [];
|
||||
if (!/^[0-9]{5,20}$/.test(text(project.student_no))) errors.push('学号必须为5–20位数字');
|
||||
if (!text(project.name)) errors.push('请填写姓名');
|
||||
if (!text(project.topic)) errors.push('请填写选题');
|
||||
if (!selected.length) errors.push('至少选择1份资料');
|
||||
const counts = new Map<string, number>();
|
||||
for (const file of selected) {
|
||||
if (!categories[file.category]) errors.push(`${fileName(file)}:请选择材料类型`);
|
||||
else counts.set(file.category, (counts.get(file.category) || 0) + 1);
|
||||
if (file.error) errors.push(`${fileName(file)}:无法读取,请修复文件或设为忽略`);
|
||||
}
|
||||
for (const [category, count] of counts) {
|
||||
if (count > 1) errors.push(`${categories[category]}选了${count}份,请只保留1份,其余设为忽略`);
|
||||
}
|
||||
const missing = Object.keys(categories).filter(category => !selected.some((file: any) => file.category === category));
|
||||
return { selected, metadata, identity, warnings, errors, missing };
|
||||
}
|
||||
|
||||
export function fillImportIdentity(project: any, force = false) {
|
||||
const review = reviewImport(project);
|
||||
if (!review.metadata) return project;
|
||||
for (const field of fields) {
|
||||
if (force || !project.identity_edits?.[field]) project[field] = review.identity[field];
|
||||
}
|
||||
if (force) project.identity_edits = {};
|
||||
return project;
|
||||
}
|
||||
|
||||
export function importErrors(projects: any[] | undefined, versionId: number | undefined) {
|
||||
const errors = versionId ? [] : ['请选择已发布课程版本'];
|
||||
if (!projects?.length) return [...errors, '当前目录没有PDF、DOCX或PPTX资料,请重新扫描资料目录'];
|
||||
for (const project of projects) {
|
||||
errors.push(...reviewImport(project).errors.map(error => `${project.relative_dir}:${error}`));
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
Reference in New Issue
Block a user