feat: 版本详情完整功能 + 数据串联 + 登录模块
- 版本详情:关联需求Tab(从需求池添加已采纳需求/移除释放) - 版本详情:调研/产品方案/UI设计Tab(计划CRUD、完成提交成果、耗时统计) - 版本详情:超期校验(结束日期超版本截止需填写原因) - 版本详情:概览统计卡片(加班时长/排名/原因占比) - 数据串联:产品→项目→版本→需求→加班全链路贯通 - 版本管理:规划中版本可删除,删除释放关联需求 - 数据清理:仅保留翻台宝/值班,需求池10条值班待评审需求 - 修复:列表页overflow裁剪菜单问题(4个页面统一修复) - 新增:登录模块、AuthGuard、VersionPlan store Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { ChevronLeft, Pencil, Trash2 } from 'lucide-react';
|
||||
import type { RequirementStatus } from '@/lib/requirement';
|
||||
import { useProductStore } from '@/stores/useProductStore';
|
||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||
import { useOvertimeStore } from '@/stores/useOvertimeStore';
|
||||
import { RequirementTable } from '@/components/product/RequirementTable';
|
||||
import { RequirementForm } from '@/components/product/RequirementForm';
|
||||
import { ProductForm } from '@/components/product/ProductForm';
|
||||
@@ -27,6 +28,7 @@ export default function ProductDetailPage() {
|
||||
fetchRequirements,
|
||||
deleteRequirement,
|
||||
} = useRequirementStore();
|
||||
const { records, fetchRecords } = useOvertimeStore();
|
||||
|
||||
const [showReqForm, setShowReqForm] = useState(false);
|
||||
const [editingReq, setEditingReq] = useState<any>(null);
|
||||
@@ -37,7 +39,8 @@ export default function ProductDetailPage() {
|
||||
useEffect(() => {
|
||||
fetchProduct(productId);
|
||||
fetchRequirements();
|
||||
}, [productId, fetchProduct, fetchRequirements]);
|
||||
fetchRecords();
|
||||
}, [productId, fetchProduct, fetchRequirements, fetchRecords]);
|
||||
|
||||
const productRequirements = useMemo(() => {
|
||||
let list = requirements.filter((r) => r.productId === productId);
|
||||
@@ -112,6 +115,27 @@ export default function ProductDetailPage() {
|
||||
{currentProduct.description && (
|
||||
<p className="line-clamp-2 py-3 text-[13px] text-[var(--ink-soft)]">{currentProduct.description}</p>
|
||||
)}
|
||||
{/* 汇总统计 */}
|
||||
<div className="flex gap-4 py-3 border-b border-[var(--line-soft)]">
|
||||
{(() => {
|
||||
const allReqs = requirements.filter((r) => r.productId === productId);
|
||||
const adopted = allReqs.filter((r) => r.status === 'adopted' || r.status === 'planned').length;
|
||||
const developing = allReqs.filter((r) => r.status === 'developing' || r.status === 'testing').length;
|
||||
const released = allReqs.filter((r) => r.status === 'released').length;
|
||||
const projectReqs = allReqs.filter((r) => r.projectId);
|
||||
const projectIds = [...new Set(projectReqs.map((r) => r.projectId!))];
|
||||
const overtimeHours = Math.round(records.filter((r) => projectIds.includes(r.projectId)).reduce((sum, r) => sum + r.duration, 0) * 10) / 10;
|
||||
return (
|
||||
<>
|
||||
<div className="text-center"><div className="text-[16px] font-semibold text-[var(--ink)]">{allReqs.length}</div><div className="text-[11px] text-[var(--ink-muted)]">需求总数</div></div>
|
||||
<div className="text-center"><div className="text-[16px] font-semibold text-blue-600">{adopted}</div><div className="text-[11px] text-[var(--ink-muted)]">已采纳</div></div>
|
||||
<div className="text-center"><div className="text-[16px] font-semibold text-amber-600">{developing}</div><div className="text-[11px] text-[var(--ink-muted)]">开发中</div></div>
|
||||
<div className="text-center"><div className="text-[16px] font-semibold text-green-600">{released}</div><div className="text-[11px] text-[var(--ink-muted)]">已发布</div></div>
|
||||
<div className="text-center"><div className="text-[16px] font-semibold text-red-500">{overtimeHours}h</div><div className="text-[11px] text-[var(--ink-muted)]">加班时长</div></div>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user