diff --git a/src/components/overview/OverviewPage.tsx b/src/components/overview/OverviewPage.tsx
index ecb2689..b684412 100644
--- a/src/components/overview/OverviewPage.tsx
+++ b/src/components/overview/OverviewPage.tsx
@@ -1,9 +1,203 @@
-export function OverviewPage() {
+import { useState } from "react";
+import { useQuery, useQueries } from "@tanstack/react-query";
+import { listBuffedProjects, getBlueprint, getProjects, type BuffStatus, type BlueprintData, type Project } from "../../lib/commands";
+import { BlueprintModal } from "../dashboard/BlueprintModal";
+
+// ── 时间格式化 ────────────────────────────────────────────────────────────────
+
+function relativeTime(iso: string): string {
+ const diff = Date.now() - new Date(iso).getTime();
+ const m = Math.floor(diff / 60000);
+ if (m < 1) return "刚刚";
+ if (m < 60) return `${m} 分钟前`;
+ const h = Math.floor(m / 60);
+ if (h < 24) return `${h} 小时前`;
+ return `${Math.floor(h / 24)} 天前`;
+}
+
+// ── 单个项目卡片 ──────────────────────────────────────────────────────────────
+
+function BuffProjectCard({
+ buff,
+ blueprint,
+ projectName,
+ onOpenBlueprint,
+}: {
+ buff: BuffStatus;
+ blueprint?: BlueprintData | null;
+ projectName: string;
+ onOpenBlueprint: () => void;
+}) {
+ const tasks = blueprint?.manifest.modules.flatMap((m) => m.tasks) ?? [];
+ const total = tasks.length;
+ const done = tasks.filter((t) => t.status === "done").length;
+ const inProgress = tasks.filter((t) => t.status === "in_progress").length;
+ const todo = tasks.filter((t) => t.status === "todo").length;
+ const blocked = tasks.filter((t) => t.status === "blocked").length;
+ const progress = total > 0 ? Math.round((done / total) * 100) : 0;
+
return (
-
-
📊
-
仪表盘
-
待规划:提醒、动态、快捷入口等
+
+
+
+ {projectName}
+ ⚡ Buff
+
+
+
+
+ {total > 0 && (
+
+
+ {done}/{total} 完成
+ {progress}%
+
+
+
+ )}
+
+ {total > 0 && (
+
+ {inProgress > 0 && 🔵 {inProgress} 进行中}
+ {todo > 0 && 📋 {todo} 待做}
+ {blocked > 0 && 🔴 {blocked} 受阻}
+ {inProgress === 0 && todo === 0 && blocked === 0 && (
+ ✅ 全部完成
+ )}
+
+ )}
+
+
+ {buff.project_path}
+ {buff.last_sync_at ? (
+ 同步 {relativeTime(buff.last_sync_at)}
+ ) : (
+ 尚未同步
+ )}
+
+
+ );
+}
+
+// ── 主页面 ───────────────────────────────────────────────────────────────────
+
+export function OverviewPage() {
+ const [openModal, setOpenModal] = useState<{ projectName: string; projectPath: string; projectId: string } | null>(null);
+
+ const { data: buffs = [], isLoading: buffsLoading } = useQuery
({
+ queryKey: ["buffed-projects"],
+ queryFn: listBuffedProjects,
+ staleTime: 10000,
+ });
+
+ const { data: projects = [] } = useQuery({
+ queryKey: ["projects"],
+ queryFn: () => getProjects(),
+ staleTime: 60000,
+ });
+
+ const blueprintResults = useQueries({
+ queries: buffs.map((b) => ({
+ queryKey: ["blueprint", b.project_path],
+ queryFn: () => getBlueprint(b.project_path),
+ staleTime: 30000,
+ })),
+ });
+
+ const projectMap = new Map((projects as Project[]).map((p: Project) => [p.id, p]));
+
+ const getName = (buff: BuffStatus) =>
+ (projectMap.get(buff.project_id) as Project | undefined)?.name
+ ?? buff.project_path.split(/[/\\]/).filter(Boolean).pop()
+ ?? buff.project_path;
+
+ // 跨项目进行中任务
+ const inProgressTasks = buffs.flatMap((b, i) => {
+ const bp = blueprintResults[i]?.data as BlueprintData | null | undefined;
+ if (!bp) return [];
+ return bp.manifest.modules
+ .flatMap((m) => m.tasks)
+ .filter((t) => t.status === "in_progress")
+ .map((t) => ({ title: t.title, projectName: getName(b) }));
+ });
+
+ if (buffsLoading) {
+ return (
+ 加载中…
+ );
+ }
+
+ if (buffs.length === 0) {
+ return (
+
+
⚡
+
暂无激活的蓝图 Buff
+
在项目蓝图 → 治理面板中为项目施加 Buff
+
+ );
+ }
+
+ return (
+
+
+
+
仪表盘
+
{buffs.length} 个项目已施加蓝图 Buff
+
+
+ {/* 跨项目进行中任务 */}
+ {inProgressTasks.length > 0 && (
+
+
🔵 跨项目进行中
+
+ {inProgressTasks.map((item, i) => (
+
+ {item.title}
+ {item.projectName}
+
+ ))}
+
+
+ )}
+
+ {/* Buff 项目卡片 */}
+
+
⚡ Buff 项目
+
+ {buffs.map((buff, i) => (
+
+ setOpenModal({
+ projectName: getName(buff),
+ projectPath: buff.project_path,
+ projectId: buff.project_id,
+ })
+ }
+ />
+ ))}
+
+
+
+
+ {openModal && (
+
setOpenModal(null)}
+ />
+ )}
);
}