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:
selfrelease
2026-07-19 19:06:43 +08:00
parent a4044baa31
commit 44cbdc9d62
9 changed files with 705 additions and 31 deletions
@@ -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>
);
+120
View File
@@ -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>
);
}
+47
View File
@@ -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>
);
}