const fields = ['student_no', 'name', 'topic'] as const; type IdentityField = typeof fields[number]; const labels = { student_no: '学号', name: '姓名', topic: '选题' }; const categories: Record = { 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 = { student_no: '', name: '', topic: '' }; const warnings: string[] = []; for (const field of fields) { const sources = new Map(); 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(); 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; }