'use client'; import { useEffect, useMemo, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { ChevronLeft, Package, Calendar, Clock, ExternalLink, FileText, Palette, Layout } from 'lucide-react'; import { useProductStore } from '@/stores/useProductStore'; import { getVersionDetail } from '@/lib/derive'; import { STAGES } from '@/lib/stage'; import { VERSION_STATUS_LABEL, VERSION_STATUS_BG } from '@/lib/version-status'; import { CapsuleStages } from '@/components/version/CapsuleStages'; import { MemberChips } from '@/components/version/MemberChips'; import { HealthTrend, generateMockTrend } from '@/components/version/HealthTrend'; import { calcHealthScore, getHealthLevel, calcRiskTags, HEALTH_LEVEL_COLOR, HEALTH_LEVEL_DOT, HEALTH_LEVEL_LABEL, getTagStyle } from '@/lib/health'; const PRIORITY_STYLE: Record = { P0: 'bg-red-500/10 text-red-600', P1: 'bg-orange-500/10 text-orange-600', P2: 'bg-blue-500/10 text-blue-600', P3: 'bg-zinc-100 text-zinc-600', P4: 'bg-zinc-100 text-zinc-500', }; const TABS = [ { key: 'overview', label: '概览' }, { key: 'requirements', label: '需求' }, { key: 'tasks', label: '开发任务' }, { key: 'testcases', label: '测试用例' }, { key: 'bugs', label: 'Bug' }, ]; export default function VersionDetailPage() { const params = useParams(); const router = useRouter(); const versionId = params.id as string; const { overview, fetchOverview } = useProductStore(); const [activeTab, setActiveTab] = useState('overview'); useEffect(() => { fetchOverview(); }, [fetchOverview]); const version = useMemo(() => getVersionDetail(overview, versionId), [overview, versionId]); const elapsedDays = useMemo(() => { if (!version?.startDate) return 0; const start = new Date(version.startDate); start.setHours(0, 0, 0, 0); const now = new Date(); now.setHours(0, 0, 0, 0); return Math.max(0, Math.floor((now.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))); }, [version?.startDate]); if (!version) { return (

版本不存在

); } const healthScore = calcHealthScore(version.status, version.startDate, version.expectedReleaseDate, version.progress); const healthLevel = getHealthLevel(healthScore); const riskTags = calcRiskTags(version.status, version.startDate, version.expectedReleaseDate, version.progress, version.currentStage, version.members); const renderActions = () => { const buttons: { label: string; action: () => void; danger?: boolean }[] = []; if (version.status === 'developing' || version.status === 'planned') { buttons.push({ label: '暂停', action: () => {} }); buttons.push({ label: '关闭', action: () => {}, danger: true }); } else if (version.status === 'paused') { buttons.push({ label: '恢复', action: () => {} }); buttons.push({ label: '关闭', action: () => {}, danger: true }); } return buttons.map((btn) => ( )); }; return (
{/* Header */}
/ {version.name}
{renderActions()}
{/* Tab bar */}
{TABS.map((tab) => ( ))}
{/* Content */}
{activeTab === 'overview' ? (
{/* Tag row */}
{version.priority && ( {version.priority} )} {VERSION_STATUS_LABEL[version.status]} {healthScore} {HEALTH_LEVEL_LABEL[healthLevel]} {riskTags.map((tag) => ( {tag.label} ))} {version.productName} / {version.projectName}
{/* Capsule stages */} {/* 风险详情 + 趋势:健康度低于60时显示 */} {healthScore < 60 && riskTags.length > 0 && (
{/* 左:风险详情(可滚动) */}
风险详情 健康度 {healthScore} · {HEALTH_LEVEL_LABEL[healthLevel]}
{riskTags.map((tag) => (
{tag.label}
{tag.reason &&
原因:{tag.reason}
} {tag.suggestion &&
建议:{tag.suggestion}
}
))}
{/* 右:健康趋势 */}
健康趋势
)} {/* 健康度正常时也显示趋势(紧凑) */} {healthScore >= 60 && (
健康趋势
)} {/* Two-column layout: left info + right links */}
{/* Left: 2/3 width */}
{/* Date + elapsed */}
{version.startDate ?? '未设置'} {version.expectedReleaseDate ?? '未设置'}
已耗时 {elapsedDays}
{/* Members */}
参与人员
{version.members && version.members.length > 0 ? ( ) : ( 暂无成员 )}
{/* Right: 1/3 width - links card */}
相关链接
} label="调研报告" url={version.links?.research} /> } label="原型地址" url={version.links?.prototype} /> } label="UI设计稿" url={version.links?.ui} />
) : (
功能开发中,敬请期待
)}
); } function LinkItem({ icon, label, url }: { icon: React.ReactNode; label: string; url?: string }) { return (
{icon} {url ? ( {label} ) : ( {label} · 未设置 )}
); }