feat(backend): Phase 0 项目骨架完成 — 后端/前端/数据库/Docker

- 后端:FastAPI + SQLAlchemy + Alembic,7 张核心表迁移成功
- 前端:Next.js 16 + TailwindCSS 4 + 三端布局(投资人/创始人/Admin)
- 数据库:PostgreSQL 16,7 张核心实体表(tenants/users/companies/monthly_reports/health_scores/risk_events/audit_logs)
- Docker:docker-compose.yml + 前后端 Dockerfile
- 测试:健康检查 4 个测试全部 GREEN
- 文档:README/run.md/AGENTS.md/docs 体系完整
This commit is contained in:
selfrelease
2026-07-18 21:50:15 +08:00
parent f09aa33589
commit 51feae55ba
72 changed files with 8048 additions and 0 deletions
@@ -0,0 +1,28 @@
/** 空状态占位组件。 */
import { Inbox } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* 空状态占位。
* @param title - 标题
* @param description - 描述
* @param className - 额外类名
*/
export function EmptyState({
title = "暂无数据",
description,
className,
}: {
title?: string;
description?: string;
className?: string;
}) {
return (
<div className={cn("flex flex-col items-center justify-center py-12 text-center", className)}>
<Inbox className="mb-3 text-muted-foreground" size={40} aria-hidden="true" />
<p className="text-sm font-medium text-foreground">{title}</p>
{description && <p className="mt-1 text-xs text-muted-foreground">{description}</p>}
</div>
);
}
@@ -0,0 +1,29 @@
/** 健康度评分徽章组件。 */
import { cn } from "@/lib/utils";
/**
* 健康度评分徽章。
* @param score - 分数(0-100
* @param label - 标签文字
*/
export function HealthScoreBadge({ score, label }: { score: number; label?: string }) {
const level = score >= 80 ? "good" : score >= 60 ? "fair" : "poor";
const colors = {
good: "bg-[var(--success)]/10 text-[var(--success)] border-[var(--success)]/30",
fair: "bg-[var(--warning)]/10 text-[var(--warning)] border-[var(--warning)]/30",
poor: "bg-[var(--destructive)]/10 text-[var(--destructive)] border-[var(--destructive)]/30",
};
return (
<span
className={cn(
"inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium",
colors[level],
)}
>
{label && <span className="mr-1">{label}</span>}
{score}
</span>
);
}
@@ -0,0 +1,17 @@
/** 加载中旋转图标组件。 */
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* 加载中旋转图标。
* @param className - 额外类名
* @param size - 图标尺寸(像素)
*/
export function LoadingSpinner({ className, size = 24 }: { className?: string; size?: number }) {
return (
<div className={cn("flex items-center justify-center", className)} role="status">
<Loader2 className="animate-spin text-muted-foreground" size={size} aria-label="加载中" />
</div>
);
}