feat(frontend): P3+P4 创始人端增强 + 企业战情室集成
P3 创始人端: - 经营驾驶舱: 6 KPI 卡片 + 风险提示 - 投资人沟通准备中心: 会议列表 + 准备清单 + AI 建议 - 里程碑自填: 进度滑块 + 新增表单 - OKR 对齐视图: 投资人期望对齐度 + KR 进度 - BML 认知追踪: 信念/心智模型/学习追踪 - 通知页面 + 个人中心页面 P4 企业战情室集成: - HighlightsPanel: 顶部 KPI 摘要栏 - WorkModeSwitcher: 4 种工作模式切换 - InsightRail: 右侧 AI 面板(默认风险预警 Agent) Admin: - 商业秘密保护配置: 密级/留痕/导出管控/审计日志
This commit is contained in:
@@ -6,6 +6,9 @@ import { TabNav, TabPanel, type TabId } from "@/components/workbench/TabNav";
|
|||||||
import { HealthRadar, DIMENSIONS_14, DIMENSIONS_14_DETAIL, HealthDimensionDetail } from "@/components/health/HealthRadar";
|
import { HealthRadar, DIMENSIONS_14, DIMENSIONS_14_DETAIL, HealthDimensionDetail } from "@/components/health/HealthRadar";
|
||||||
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||||
import { EmptyState } from "@/components/shared/EmptyState";
|
import { EmptyState } from "@/components/shared/EmptyState";
|
||||||
|
import { HighlightsPanel } from "@/components/workbench/HighlightsPanel";
|
||||||
|
import { WorkModeSwitcher, type WorkMode } from "@/components/shared/WorkModeSwitcher";
|
||||||
|
import { InsightRail } from "@/components/shared/InsightRail";
|
||||||
import { apiFetch } from "@/lib/api";
|
import { apiFetch } from "@/lib/api";
|
||||||
import { AlertTriangle, FileText, ClipboardList, ScrollText, Network, DollarSign, Activity, Users, Bot } from "lucide-react";
|
import { AlertTriangle, FileText, ClipboardList, ScrollText, Network, DollarSign, Activity, Users, Bot } from "lucide-react";
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ export default function WorkbenchPage() {
|
|||||||
const [activeTab, setActiveTab] = useState<TabId>("overview");
|
const [activeTab, setActiveTab] = useState<TabId>("overview");
|
||||||
const [detail, setDetail] = useState<any>(null);
|
const [detail, setDetail] = useState<any>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [workMode, setWorkMode] = useState<WorkMode>("overview");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!companyId) return;
|
if (!companyId) return;
|
||||||
@@ -41,28 +45,31 @@ export default function WorkbenchPage() {
|
|||||||
|
|
||||||
const { company, health_score, recent_reports, open_risks, recent_weak_signals, active_agreements, recent_board_meetings } = detail;
|
const { company, health_score, recent_reports, open_risks, recent_weak_signals, active_agreements, recent_board_meetings } = detail;
|
||||||
|
|
||||||
|
const highRisksCount = (open_risks || []).filter((r: any) => r.severity === "high" || r.severity === "critical").length;
|
||||||
|
const pendingTasksCount = (recent_reports || []).filter((r: any) => r.status === "pending" || r.status === "draft").length;
|
||||||
|
const runway = health_score?.runway_months ?? 12;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
{/* 企业头部信息 */}
|
{/* Highlights Panel — 企业 KPI 摘要 */}
|
||||||
<div className="flex items-center justify-between rounded-lg border border-[var(--border)] bg-white p-4">
|
<HighlightsPanel
|
||||||
<div>
|
companyName={company.name}
|
||||||
<h1 className="text-xl font-bold">{company.name}</h1>
|
healthScore={health_score?.total_score ?? 0}
|
||||||
<p className="text-sm text-muted-foreground">
|
healthTrend={health_score?.trend ?? "stable"}
|
||||||
{company.industry || "未知行业"} · {company.stage || "未知阶段"}
|
runway={runway}
|
||||||
</p>
|
highRisks={highRisksCount}
|
||||||
</div>
|
pendingTasks={pendingTasksCount}
|
||||||
{health_score && (
|
/>
|
||||||
<div className="text-right">
|
|
||||||
<div className="text-2xl font-bold text-[var(--investor-primary)]">
|
{/* Work Mode 切换器 */}
|
||||||
{health_score.total_score.toFixed(0)}
|
<div className="flex items-center justify-between">
|
||||||
</div>
|
<WorkModeSwitcher mode={workMode} onChange={setWorkMode} />
|
||||||
<div className="text-xs text-muted-foreground">健康度总分</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 主内容 + Insight Rail */}
|
||||||
|
<div className="flex gap-0">
|
||||||
{/* Tab 导航 + 内容 */}
|
{/* Tab 导航 + 内容 */}
|
||||||
<div className="flex flex-col gap-4 md:flex-row">
|
<div className="flex flex-1 flex-col gap-4 md:flex-row">
|
||||||
<TabNav active={activeTab} onChange={setActiveTab} />
|
<TabNav active={activeTab} onChange={setActiveTab} />
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{activeTab === "overview" && <OverviewTab detail={detail} />}
|
{activeTab === "overview" && <OverviewTab detail={detail} />}
|
||||||
@@ -77,6 +84,10 @@ export default function WorkbenchPage() {
|
|||||||
{activeTab === "synergy" && <SynergyTab companyId={company.id} />}
|
{activeTab === "synergy" && <SynergyTab companyId={company.id} />}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Insight Rail — 右侧 AI 面板 */}
|
||||||
|
<InsightRail defaultAgent="risk" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/** Admin 商业秘密保护配置页面 — 密级规则 + 查看留痕 + 导出管控。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ShieldCheck, Lock, Eye, Download, Settings } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ClassificationBadge, type ClassificationLevel } from "@/components/shared/ClassificationBadge";
|
||||||
|
|
||||||
|
/** 商业秘密保护配置页面。 */
|
||||||
|
export default function SecurityPage() {
|
||||||
|
const [defaultLevel, setDefaultLevel] = useState<ClassificationLevel>("internal");
|
||||||
|
const [watermarkEnabled, setWatermarkEnabled] = useState(true);
|
||||||
|
const [exportApproval, setExportApproval] = useState(true);
|
||||||
|
const [viewLogRetention, setViewLogRetention] = useState("180");
|
||||||
|
|
||||||
|
const levels: ClassificationLevel[] = ["public", "internal", "confidential", "secret"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<ShieldCheck className="text-[var(--admin-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">商业秘密保护</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 默认密级配置 */}
|
||||||
|
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-3 flex items-center gap-2">
|
||||||
|
<Lock size={18} className="text-[var(--admin-primary)]" aria-hidden="true" />
|
||||||
|
<h2 className="font-medium">默认密级配置</h2>
|
||||||
|
</div>
|
||||||
|
<p className="mb-3 text-sm text-muted-foreground">新建数据时的默认密级标签:</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{levels.map((level) => (
|
||||||
|
<button
|
||||||
|
key={level}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setDefaultLevel(level)}
|
||||||
|
className={`rounded-md border-2 p-2 transition-colors ${
|
||||||
|
defaultLevel === level ? "border-[var(--admin-primary)]" : "border-gray-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<ClassificationBadge level={level} showDescription />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 查看留痕 */}
|
||||||
|
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-3 flex items-center gap-2">
|
||||||
|
<Eye size={18} className="text-[var(--admin-primary)]" aria-hidden="true" />
|
||||||
|
<h2 className="font-medium">查看留痕</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-700">机密/绝密数据查看时自动记录</span>
|
||||||
|
<span className="text-xs text-emerald-600">已启用</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-700">留痕数据保留天数</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={viewLogRetention}
|
||||||
|
onChange={(e) => setViewLogRetention(e.target.value)}
|
||||||
|
className="w-20 rounded-md border px-2 py-1 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 导出管控 */}
|
||||||
|
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-3 flex items-center gap-2">
|
||||||
|
<Download size={18} className="text-[var(--admin-primary)]" aria-hidden="true" />
|
||||||
|
<h2 className="font-medium">导出管控</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<label className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-700">导出需审批</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={exportApproval}
|
||||||
|
onChange={(e) => setExportApproval(e.target.checked)}
|
||||||
|
className="h-4 w-4"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-gray-700">导出文件自动添加水印</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={watermarkEnabled}
|
||||||
|
onChange={(e) => setWatermarkEnabled(e.target.checked)}
|
||||||
|
className="h-4 w-4"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 审计日志摘要 */}
|
||||||
|
<div className="rounded-lg border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-3 flex items-center gap-2">
|
||||||
|
<Settings size={18} className="text-[var(--admin-primary)]" aria-hidden="true" />
|
||||||
|
<h2 className="font-medium">近期访问记录</h2>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
{[
|
||||||
|
{ user: "张三", action: "查看机密报告", time: "2026-07-14 10:23" },
|
||||||
|
{ user: "李四", action: "导出绝密数据", time: "2026-07-13 16:45" },
|
||||||
|
{ user: "王五", action: "查看机密财务", time: "2026-07-12 09:12" },
|
||||||
|
].map((log, i) => (
|
||||||
|
<div key={i} className="flex items-center justify-between rounded border px-3 py-2">
|
||||||
|
<span>{log.user} — {log.action}</span>
|
||||||
|
<span className="text-xs text-muted-foreground">{log.time}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
/** 创始人端 BML 认知追踪页面 — 信念-心智模型-学习追踪。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Brain, Lightbulb, BookOpen, TrendingUp } from "lucide-react";
|
||||||
|
|
||||||
|
/** BML 条目。 */
|
||||||
|
interface BMLItem {
|
||||||
|
type: "belief" | "mental_model" | "learning";
|
||||||
|
title: string;
|
||||||
|
status: "validating" | "validated" | "invalidated" | "learning";
|
||||||
|
evidence: string;
|
||||||
|
lastUpdated: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** BML 认知追踪页面。 */
|
||||||
|
export default function BMLTrackingPage() {
|
||||||
|
const items: BMLItem[] = [
|
||||||
|
{ type: "belief", title: "企业客户更看重数据安全而非价格", status: "validated", evidence: "5 个 PoC 反馈一致", lastUpdated: "2026-07-10" },
|
||||||
|
{ type: "belief", title: "AI 模型成本会持续下降", status: "validating", evidence: "推理成本月降 8%", lastUpdated: "2026-07-12" },
|
||||||
|
{ type: "mental_model", title: "PLG 不适用于企业级 AI 产品", status: "validated", evidence: "3 次免费试用转化率 <5%", lastUpdated: "2026-06-28" },
|
||||||
|
{ type: "learning", title: "董事会关注 AI 伦理风险", status: "learning", evidence: "上次董事会提问方向", lastUpdated: "2026-07-05" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const TYPE_CONFIG = {
|
||||||
|
belief: { label: "信念", icon: Lightbulb, color: "text-amber-600 bg-amber-50" },
|
||||||
|
mental_model: { label: "心智模型", icon: Brain, color: "text-indigo-600 bg-indigo-50" },
|
||||||
|
learning: { label: "学习", icon: BookOpen, color: "text-emerald-600 bg-emerald-50" },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const STATUS_LABELS = {
|
||||||
|
validating: "验证中",
|
||||||
|
validated: "已验证",
|
||||||
|
invalidated: "已证伪",
|
||||||
|
learning: "学习中",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Brain className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">BML 认知追踪</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 摘要 */}
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div className="rounded-xl border bg-white p-4 text-center shadow-sm">
|
||||||
|
<Lightbulb className="mx-auto mb-1 text-amber-500" size={20} aria-hidden="true" />
|
||||||
|
<div className="text-2xl font-bold">2</div>
|
||||||
|
<div className="text-xs text-muted-foreground">信念</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl border bg-white p-4 text-center shadow-sm">
|
||||||
|
<Brain className="mx-auto mb-1 text-indigo-500" size={20} aria-hidden="true" />
|
||||||
|
<div className="text-2xl font-bold">1</div>
|
||||||
|
<div className="text-xs text-muted-foreground">心智模型</div>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl border bg-white p-4 text-center shadow-sm">
|
||||||
|
<BookOpen className="mx-auto mb-1 text-emerald-500" size={20} aria-hidden="true" />
|
||||||
|
<div className="text-2xl font-bold">1</div>
|
||||||
|
<div className="text-xs text-muted-foreground">学习</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* BML 列表 */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{items.map((item, i) => {
|
||||||
|
const cfg = TYPE_CONFIG[item.type];
|
||||||
|
return (
|
||||||
|
<div key={i} className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className={`rounded px-2 py-0.5 text-xs font-medium ${cfg.color}`}>
|
||||||
|
{cfg.label}
|
||||||
|
</span>
|
||||||
|
<span className="font-medium">{item.title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground">{STATUS_LABELS[item.status]}</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
|
<TrendingUp size={12} aria-hidden="true" />
|
||||||
|
<span>证据:{item.evidence}</span>
|
||||||
|
<span>· 更新于 {item.lastUpdated}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/** 投资人沟通准备中心 — 帮助创始人准备与投资人的沟通。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { MessageSquare, FileText, CheckCircle2, Clock, AlertTriangle } from "lucide-react";
|
||||||
|
|
||||||
|
/** 投资人沟通准备中心页面。 */
|
||||||
|
export default function InvestorCommsPage() {
|
||||||
|
const upcomingMeetings = [
|
||||||
|
{ title: "7 月度沟通", date: "2026-07-20", status: "preparing", items: 5 },
|
||||||
|
{ title: "季度董事会", date: "2026-07-25", status: "preparing", items: 8 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const checklist = [
|
||||||
|
{ label: "月报已提交", done: true },
|
||||||
|
{ label: "OKR 进度更新", done: true },
|
||||||
|
{ label: "财务数据校验", done: true },
|
||||||
|
{ label: "风险项说明准备", done: false },
|
||||||
|
{ label: "下季度计划文档", done: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<MessageSquare className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">投资人沟通准备</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 即将到来的沟通 */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{upcomingMeetings.map((meeting) => (
|
||||||
|
<div key={meeting.title} className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium">{meeting.title}</h3>
|
||||||
|
<p className="mt-1 flex items-center gap-1 text-sm text-muted-foreground">
|
||||||
|
<Clock size={12} aria-hidden="true" />
|
||||||
|
{meeting.date}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="rounded-full bg-amber-50 px-3 py-1 text-xs text-amber-600">
|
||||||
|
准备中 · {meeting.items} 项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 准备清单 */}
|
||||||
|
<div className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<h2 className="mb-3 font-medium">准备清单</h2>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{checklist.map((item) => (
|
||||||
|
<div key={item.label} className="flex items-center gap-2 text-sm">
|
||||||
|
{item.done ? (
|
||||||
|
<CheckCircle2 className="text-emerald-500" size={16} aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<div className="h-4 w-4 rounded-full border-2 border-gray-300" />
|
||||||
|
)}
|
||||||
|
<span className={item.done ? "text-gray-400 line-through" : "text-gray-700"}>
|
||||||
|
{item.label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* AI 建议 */}
|
||||||
|
<div className="rounded-xl border border-indigo-200 bg-indigo-50 p-4">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<FileText className="text-indigo-500" size={18} aria-hidden="true" />
|
||||||
|
<h2 className="font-medium text-indigo-700">AI 沟通建议</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-indigo-800">
|
||||||
|
投资人可能关注客户流失率上升原因。建议准备:1) 流失客户分析报告 2) 回访计划 3) Q3 恢复目标。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/** 里程碑自填页面 — 创始人自主更新里程碑进度。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { GitBranch, Plus, CheckCircle2, Clock, Circle } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
/** 里程碑状态类型。 */
|
||||||
|
type MilestoneStatus = "completed" | "in_progress" | "planned";
|
||||||
|
|
||||||
|
/** 里程碑数据。 */
|
||||||
|
interface Milestone {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
targetDate: string;
|
||||||
|
status: MilestoneStatus;
|
||||||
|
progress: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 状态配置。 */
|
||||||
|
const STATUS_CONFIG: Record<MilestoneStatus, { label: string; icon: typeof Circle; color: string }> = {
|
||||||
|
completed: { label: "已完成", icon: CheckCircle2, color: "text-emerald-500" },
|
||||||
|
in_progress: { label: "进行中", icon: Clock, color: "text-amber-500" },
|
||||||
|
planned: { label: "计划中", icon: Circle, color: "text-gray-400" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 里程碑自填页面。 */
|
||||||
|
export default function MilestonesSelfPage() {
|
||||||
|
const [milestones, setMilestones] = useState<Milestone[]>([
|
||||||
|
{ id: "m1", title: "A+ 轮融资完成", targetDate: "2026-09-30", status: "in_progress", progress: 45 },
|
||||||
|
{ id: "m2", title: "客户数突破 200", targetDate: "2026-08-31", status: "in_progress", progress: 78 },
|
||||||
|
{ id: "m3", title: "AI 商业化 PoC #3 签约", targetDate: "2026-07-31", status: "planned", progress: 0 },
|
||||||
|
{ id: "m4", title: "核心团队扩招 5 人", targetDate: "2026-06-30", status: "completed", progress: 100 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
const [newTitle, setNewTitle] = useState("");
|
||||||
|
const [newDate, setNewDate] = useState("");
|
||||||
|
|
||||||
|
function addMilestone() {
|
||||||
|
if (!newTitle.trim()) return;
|
||||||
|
setMilestones((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ id: `m${prev.length + 1}`, title: newTitle, targetDate: newDate || "待定", status: "planned", progress: 0 },
|
||||||
|
]);
|
||||||
|
setNewTitle("");
|
||||||
|
setNewDate("");
|
||||||
|
setShowForm(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgress(id: string, progress: number) {
|
||||||
|
setMilestones((prev) =>
|
||||||
|
prev.map((m) =>
|
||||||
|
m.id === id
|
||||||
|
? { ...m, progress, status: progress >= 100 ? "completed" : progress > 0 ? "in_progress" : "planned" }
|
||||||
|
: m
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<GitBranch className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">里程碑</h1>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowForm(!showForm)}
|
||||||
|
className="flex items-center gap-1 rounded-md bg-indigo-50 px-3 py-1.5 text-sm text-indigo-600"
|
||||||
|
>
|
||||||
|
<Plus size={14} aria-hidden="true" />
|
||||||
|
新增里程碑
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 新增表单 */}
|
||||||
|
{showForm && (
|
||||||
|
<div className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="里程碑标题"
|
||||||
|
value={newTitle}
|
||||||
|
onChange={(e) => setNewTitle(e.target.value)}
|
||||||
|
className="w-full rounded-md border px-3 py-2 text-sm"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={newDate}
|
||||||
|
onChange={(e) => setNewDate(e.target.value)}
|
||||||
|
className="w-full rounded-md border px-3 py-2 text-sm"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addMilestone}
|
||||||
|
className="rounded-md bg-indigo-600 px-4 py-2 text-sm text-white"
|
||||||
|
>
|
||||||
|
确认新增
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 里程碑列表 */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
{milestones.map((m) => {
|
||||||
|
const cfg = STATUS_CONFIG[m.status];
|
||||||
|
return (
|
||||||
|
<div key={m.id} className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<cfg.icon className={cfg.color} size={18} aria-hidden="true" />
|
||||||
|
<span className="font-medium">{m.title}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground">目标日期:{m.targetDate}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="h-2 flex-1 overflow-hidden rounded-full bg-gray-100">
|
||||||
|
<div
|
||||||
|
className={`h-full rounded-full transition-all ${
|
||||||
|
m.status === "completed" ? "bg-emerald-500" : "bg-indigo-500"
|
||||||
|
}`}
|
||||||
|
style={{ width: `${m.progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium">{m.progress}%</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
value={m.progress}
|
||||||
|
onChange={(e) => updateProgress(m.id, Number(e.target.value))}
|
||||||
|
className="w-24"
|
||||||
|
aria-label={`${m.title} 进度`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/** 创始人端通知页面。 */
|
||||||
|
|
||||||
|
import { Bell } from "lucide-react";
|
||||||
|
|
||||||
|
/** 通知页面。 */
|
||||||
|
export default function NotificationsPage() {
|
||||||
|
const notifications = [
|
||||||
|
{ title: "月报审阅完成", content: "投资人已审阅 6 月月报,无修改意见。", time: "2 小时前", read: false },
|
||||||
|
{ title: "风险预警", content: "客户流失率连续 2 月上升,建议关注。", time: "5 小时前", read: false },
|
||||||
|
{ title: "协同机会", content: "智链科技与贵公司有供应链协同机会。", time: "1 天前", read: true },
|
||||||
|
{ title: "里程碑提醒", content: "A+ 轮融资目标日期 9 月 30 日,当前进度 45%。", time: "2 天前", read: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Bell className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">通知</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{notifications.map((n, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`rounded-xl border bg-white p-4 shadow-sm ${!n.read ? "border-indigo-200" : ""}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 className={`font-medium ${!n.read ? "text-indigo-700" : ""}`}>{n.title}</h3>
|
||||||
|
<p className="mt-1 text-sm text-muted-foreground">{n.content}</p>
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground">{n.time}</span>
|
||||||
|
</div>
|
||||||
|
{!n.read && (
|
||||||
|
<div className="mt-2 h-1.5 w-1.5 rounded-full bg-indigo-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/** 创始人端 OKR 对齐视图 — 查看公司 OKR 与投资人期望的对齐情况。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Target, CheckCircle2, Circle, AlertTriangle } from "lucide-react";
|
||||||
|
|
||||||
|
/** OKR 数据。 */
|
||||||
|
interface OKR {
|
||||||
|
objective: string;
|
||||||
|
keyResults: { title: string; progress: number; aligned: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** OKR 对齐视图页面。 */
|
||||||
|
export default function OKRAlignPage() {
|
||||||
|
const okrs: OKR[] = [
|
||||||
|
{
|
||||||
|
objective: "加速 AI 商业化落地",
|
||||||
|
keyResults: [
|
||||||
|
{ title: "完成 3 个 PoC 签约", progress: 67, aligned: true },
|
||||||
|
{ title: "AI 产品月营收达 100 万", progress: 45, aligned: true },
|
||||||
|
{ title: "客户满意度 NPS > 50", progress: 80, aligned: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
objective: "提升组织效能",
|
||||||
|
keyResults: [
|
||||||
|
{ title: "核心团队扩招 5 人", progress: 100, aligned: true },
|
||||||
|
{ title: "建立 OKR 季度复盘机制", progress: 100, aligned: true },
|
||||||
|
{ title: "人均产出提升 20%", progress: 60, aligned: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Target className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">OKR 对齐视图</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 对齐度摘要 */}
|
||||||
|
<div className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm text-muted-foreground">投资人期望对齐度</span>
|
||||||
|
<span className="text-2xl font-bold text-emerald-600">85%</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 h-2 overflow-hidden rounded-full bg-gray-100">
|
||||||
|
<div className="h-full rounded-full bg-emerald-500" style={{ width: "85%" }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* OKR 列表 */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{okrs.map((okr, i) => (
|
||||||
|
<div key={i} className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<h2 className="mb-3 font-medium">{okr.objective}</h2>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{okr.keyResults.map((kr, j) => (
|
||||||
|
<div key={j} className="flex items-center gap-2 text-sm">
|
||||||
|
{kr.progress >= 100 ? (
|
||||||
|
<CheckCircle2 className="text-emerald-500" size={16} aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<Circle className="text-gray-300" size={16} aria-hidden="true" />
|
||||||
|
)}
|
||||||
|
<span className="flex-1">{kr.title}</span>
|
||||||
|
{kr.aligned ? (
|
||||||
|
<span className="text-xs text-emerald-600">已对齐</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-0.5 text-xs text-amber-600">
|
||||||
|
<AlertTriangle size={10} aria-hidden="true" />
|
||||||
|
未对齐
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="font-medium">{kr.progress}%</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/** 经营驾驶舱页面 — 创始人端核心经营指标可视化。 */
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Gauge, TrendingUp, TrendingDown, Wallet, Users, Target, Cpu, AlertTriangle } from "lucide-react";
|
||||||
|
|
||||||
|
/** 经营驾驶舱页面。 */
|
||||||
|
export default function OperationsPage() {
|
||||||
|
const kpis = [
|
||||||
|
{ label: "月营收", value: "800万", trend: "up", trendValue: "+15%", icon: Wallet, color: "text-emerald-600" },
|
||||||
|
{ label: "毛利率", value: "42%", trend: "up", trendValue: "+3%", icon: TrendingUp, color: "text-emerald-600" },
|
||||||
|
{ label: "客户数", value: "156", trend: "up", trendValue: "+12", icon: Users, color: "text-emerald-600" },
|
||||||
|
{ label: "Runway", value: "18 月", trend: "stable", trendValue: "持平", icon: Gauge, color: "text-blue-600" },
|
||||||
|
{ label: "OKR 进度", value: "68%", trend: "up", trendValue: "+8%", icon: Target, color: "text-indigo-600" },
|
||||||
|
{ label: "AI PoC", value: "3 个", trend: "up", trendValue: "+1", icon: Cpu, color: "text-indigo-600" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Gauge className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">经营驾驶舱</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* KPI 卡片网格 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
|
||||||
|
{kpis.map((kpi) => (
|
||||||
|
<div key={kpi.label} className="rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<div className="mb-2 flex items-center justify-between">
|
||||||
|
<kpi.icon className={kpi.color} size={20} aria-hidden="true" />
|
||||||
|
<span className={`text-xs ${kpi.trend === "up" ? "text-emerald-500" : kpi.trend === "down" ? "text-rose-500" : "text-gray-400"}`}>
|
||||||
|
{kpi.trend === "up" && <TrendingUp size={10} className="inline" aria-hidden="true" />}
|
||||||
|
{kpi.trend === "down" && <TrendingDown size={10} className="inline" aria-hidden="true" />}
|
||||||
|
{kpi.trendValue}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">{kpi.value}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{kpi.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 风险提示 */}
|
||||||
|
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<AlertTriangle className="text-amber-500" size={18} aria-hidden="true" />
|
||||||
|
<h2 className="font-medium text-amber-700">需要关注</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-1.5 text-sm text-amber-800">
|
||||||
|
<li>• 客户流失率连续 2 月上升,建议制定回访计划</li>
|
||||||
|
<li>• 竞品发布类似产品,需加速差异化功能上线</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/** 创始人端个人中心页面。 */
|
||||||
|
|
||||||
|
import { UserCircle, Building2, Mail, LogOut } from "lucide-react";
|
||||||
|
|
||||||
|
/** 个人中心页面。 */
|
||||||
|
export default function ProfilePage() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<UserCircle className="text-[var(--founder-primary)]" size={24} />
|
||||||
|
<h1 className="text-2xl font-bold">我的</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 用户信息 */}
|
||||||
|
<div className="rounded-xl border bg-white p-6 shadow-sm">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-indigo-100">
|
||||||
|
<UserCircle className="text-indigo-500" size={32} aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold">李明</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">智链科技 · 创始人 & CEO</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 信息列表 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-3 rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<Building2 className="text-gray-400" size={18} aria-hidden="true" />
|
||||||
|
<span className="text-sm text-gray-700">企业:智链科技</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 rounded-xl border bg-white p-4 shadow-sm">
|
||||||
|
<Mail className="text-gray-400" size={18} aria-hidden="true" />
|
||||||
|
<span className="text-sm text-gray-700">liming@zhilian.tech</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex w-full items-center gap-3 rounded-xl border bg-white p-4 text-left shadow-sm transition-colors hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
<LogOut className="text-rose-400" size={18} aria-hidden="true" />
|
||||||
|
<span className="text-sm text-rose-500">退出登录</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user