import { useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; import { api } from '../services/api'; async function downloadPdf(url: string, options?: { silent?: boolean }) { try { const res = await fetch(url, { credentials: 'include' }); if (!res.ok) { let msg = '下载失败'; try { msg = (await res.json()).error || msg; } catch { /* 非 JSON 响应 */ } alert(msg); return false; } const blob = await res.blob(); // 优先 filename*(RFC 5987);其次 filename(剥离引号);最后兜底 const disposition = res.headers.get('Content-Disposition') || ''; let filename = ''; const star = disposition.match(/filename\*=(?:UTF-8'')?([^;\s]+)/i); if (star) { try { filename = decodeURIComponent(star[1]); } catch { filename = star[1]; } } else { const plain = disposition.match(/filename="?([^";\s]+)"?/i); filename = plain ? plain[1] : ''; } if (!filename) filename = url.includes('summary') ? '汇总报告.pdf' : url.includes('deliverables') ? '成果物清单.csv' : '报告.pdf'; const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(a.href), 1000); if (!options?.silent) alert(`已开始下载:${filename}(保存到浏览器下载目录)`); return true; } catch (e: any) { alert('下载失败: ' + (e?.message || e)); return false; } } type Tab = 'standards' | 'entries' | 'deliverables' | 'summary'; export default function ProjectView() { const { id } = useParams<{ id: string }>(); const [project, setProject] = useState(null); const [projectError, setProjectError] = useState(''); const [tab, setTab] = useState('entries'); const [editingName, setEditingName] = useState(false); const [newName, setNewName] = useState(''); useEffect(() => { if (id) { setProjectError(''); let cancelled = false; api.getProject(id).then(p => { if (!cancelled) setProject(p); }).catch((err: any) => { if (!cancelled) { setProjectError(err.message || '项目不存在'); setProject(null); } }); return () => { cancelled = true; }; } }, [id]); if (projectError) return
{projectError}
; if (!project) return
加载中...
; return (
{editingName ? ( setNewName(e.target.value)} autoFocus style={{ fontSize: 22, fontWeight: 700, padding: '4px 10px', border: '2px solid var(--primary)', borderRadius: 8, outline: 'none', width: 400 }} onKeyDown={async e => { if (e.key === 'Enter' && newName.trim()) { try { await api.request('PUT', `/projects/${id}`, { name: newName.trim() }); setProject({ ...project, name: newName.trim() }); setEditingName(false); } catch (err: any) { alert(err.message || '重命名失败'); } } if (e.key === 'Escape') { setNewName(project.name); setEditingName(false); } }} onBlur={async () => { if (newName.trim() && newName.trim() !== project.name) { try { await api.request('PUT', `/projects/${id}`, { name: newName.trim() }); setProject({ ...project, name: newName.trim() }); } catch (err: any) { alert(err.message || '重命名失败'); } } setEditingName(false); }} /> ) : (

{ setNewName(project.name); setEditingName(true); }} title="点击重命名">{project.name} ✎

)} {project.track && {project.track}}
总计 {project.total} ✓ {project.reviewed || 0} ▶ {project.active || 0} ✕ {project.failed || 0}
{tab === 'standards' && } {tab === 'entries' && } {tab === 'deliverables' && } {tab === 'summary' && }
); } function StandardsManager({ projectId }: { projectId: string }) { const [standards, setStandards] = useState([]); const [showForm, setShowForm] = useState(false); const [name, setName] = useState(''); const [content, setContent] = useState(''); const [catTag, setCatTag] = useState(''); const [maxScore, setMaxScore] = useState('150'); const load = () => api.listStandards(projectId).then(setStandards).catch(() => {}); useEffect(() => { load(); }, [projectId]); const create = async () => { if (!name.trim() || !content.trim()) return; try { await api.createStandard(projectId, { name: name.trim(), content, category_tag: catTag, max_score: parseInt(maxScore) || 150 }); setName(''); setContent(''); setCatTag(''); setMaxScore('150'); setShowForm(false); await load(); } catch (err: any) { alert(err.message || '保存失败,请检查服务端'); } }; const remove = async (sid: string) => { if (!confirm('确定删除?')) return; try { await api.deleteStandard(projectId, sid); await load(); } catch (err: any) { alert(err.message || '删除失败'); } }; return (

评审标准 ({standards.length})

{showForm && (
setName(e.target.value)} placeholder="标准名称" /> setCatTag(e.target.value)} placeholder="分类标签(留空为默认标准)" /> setMaxScore(e.target.value)} placeholder="总分上限" type="number" min={1} style={{ width: 120, padding: '10px 14px', border: '2px solid var(--border)', borderRadius: 8, fontSize: 14, outline: 'none' }} />