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 { LoadingSpinner } from "@/components/shared/LoadingSpinner";
|
||||
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 { 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 [detail, setDetail] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [workMode, setWorkMode] = useState<WorkMode>("overview");
|
||||
|
||||
useEffect(() => {
|
||||
if (!companyId) return;
|
||||
@@ -41,41 +45,48 @@ export default function WorkbenchPage() {
|
||||
|
||||
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 (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* 企业头部信息 */}
|
||||
<div className="flex items-center justify-between rounded-lg border border-[var(--border)] bg-white p-4">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">{company.name}</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{company.industry || "未知行业"} · {company.stage || "未知阶段"}
|
||||
</p>
|
||||
</div>
|
||||
{health_score && (
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-[var(--investor-primary)]">
|
||||
{health_score.total_score.toFixed(0)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">健康度总分</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Highlights Panel — 企业 KPI 摘要 */}
|
||||
<HighlightsPanel
|
||||
companyName={company.name}
|
||||
healthScore={health_score?.total_score ?? 0}
|
||||
healthTrend={health_score?.trend ?? "stable"}
|
||||
runway={runway}
|
||||
highRisks={highRisksCount}
|
||||
pendingTasks={pendingTasksCount}
|
||||
/>
|
||||
|
||||
{/* Work Mode 切换器 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<WorkModeSwitcher mode={workMode} onChange={setWorkMode} />
|
||||
</div>
|
||||
|
||||
{/* Tab 导航 + 内容 */}
|
||||
<div className="flex flex-col gap-4 md:flex-row">
|
||||
<TabNav active={activeTab} onChange={setActiveTab} />
|
||||
<TabPanel>
|
||||
{activeTab === "overview" && <OverviewTab detail={detail} />}
|
||||
{activeTab === "financial" && <FinancialTab detail={detail} />}
|
||||
{activeTab === "operational" && <OperationalTab detail={detail} />}
|
||||
{activeTab === "org" && <OrgTab detail={detail} />}
|
||||
{activeTab === "ai" && <AITab detail={detail} />}
|
||||
{activeTab === "risk" && <RiskTab risks={open_risks} signals={recent_weak_signals} />}
|
||||
{activeTab === "reports" && <ReportsTab reports={recent_reports} />}
|
||||
{activeTab === "board" && <BoardTab meetings={recent_board_meetings} />}
|
||||
{activeTab === "agreements" && <AgreementsTab agreements={active_agreements} />}
|
||||
{activeTab === "synergy" && <SynergyTab companyId={company.id} />}
|
||||
</TabPanel>
|
||||
{/* 主内容 + Insight Rail */}
|
||||
<div className="flex gap-0">
|
||||
{/* Tab 导航 + 内容 */}
|
||||
<div className="flex flex-1 flex-col gap-4 md:flex-row">
|
||||
<TabNav active={activeTab} onChange={setActiveTab} />
|
||||
<TabPanel>
|
||||
{activeTab === "overview" && <OverviewTab detail={detail} />}
|
||||
{activeTab === "financial" && <FinancialTab detail={detail} />}
|
||||
{activeTab === "operational" && <OperationalTab detail={detail} />}
|
||||
{activeTab === "org" && <OrgTab detail={detail} />}
|
||||
{activeTab === "ai" && <AITab detail={detail} />}
|
||||
{activeTab === "risk" && <RiskTab risks={open_risks} signals={recent_weak_signals} />}
|
||||
{activeTab === "reports" && <ReportsTab reports={recent_reports} />}
|
||||
{activeTab === "board" && <BoardTab meetings={recent_board_meetings} />}
|
||||
{activeTab === "agreements" && <AgreementsTab agreements={active_agreements} />}
|
||||
{activeTab === "synergy" && <SynergyTab companyId={company.id} />}
|
||||
</TabPanel>
|
||||
</div>
|
||||
|
||||
{/* Insight Rail — 右侧 AI 面板 */}
|
||||
<InsightRail defaultAgent="risk" />
|
||||
</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