diff --git a/frontend/src/app/(investor)/companies/[id]/page.tsx b/frontend/src/app/(investor)/companies/[id]/page.tsx new file mode 100644 index 0000000..f4cb738 --- /dev/null +++ b/frontend/src/app/(investor)/companies/[id]/page.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { useEffect, useState, use } from "react"; +import { Building2, Globe, Calendar, DollarSign, ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { getCompany, type Company } from "@/lib/companies"; +import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; +import { EmptyState } from "@/components/shared/EmptyState"; +import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge"; + +/** + * 投资人端 — 企业详情页。 + */ +export default function CompanyDetailPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = use(params); + const [company, setCompany] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(""); + + useEffect(() => { + async function load() { + setIsLoading(true); + try { + const resp = await getCompany(id); + if (resp.data) { + setCompany(resp.data); + } + } catch (err) { + setError(err instanceof Error ? err.message : "加载失败"); + } finally { + setIsLoading(false); + } + } + load(); + }, [id]); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error || !company) { + return ( +
+ +
+ ); + } + + return ( +
+ +
+ ); +} diff --git a/frontend/src/app/(investor)/companies/page.tsx b/frontend/src/app/(investor)/companies/page.tsx new file mode 100644 index 0000000..ad1163e --- /dev/null +++ b/frontend/src/app/(investor)/companies/page.tsx @@ -0,0 +1,169 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { Building2, Search, Plus, ArrowRight } from "lucide-react"; +import Link from "next/link"; +import { listCompanies, type Company } from "@/lib/companies"; +import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; +import { EmptyState } from "@/components/shared/EmptyState"; +import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge"; + +/** + * 投资人端 — 企业列表页。 + */ +export default function CompaniesPage() { + const [companies, setCompanies] = useState([]); + const [total, setTotal] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [keyword, setKeyword] = useState(""); + const [page, setPage] = useState(1); + const pageSize = 20; + + const loadCompanies = useCallback(async () => { + setIsLoading(true); + try { + const resp = await listCompanies({ page, page_size: pageSize, keyword: keyword || undefined }); + if (resp.data) { + setCompanies(resp.data.items); + setTotal(resp.data.total); + } + } catch { + setCompanies([]); + } finally { + setIsLoading(false); + } + }, [page, keyword]); + + useEffect(() => { + loadCompanies(); + }, [loadCompanies]); + + function handleSearch(e: React.FormEvent) { + e.preventDefault(); + setPage(1); + loadCompanies(); + } + + return ( +
+
+
+

被投企业

+

共 {total} 家企业

+
+ +
+ + {/* 搜索栏 */} +
+
+
+ +
+ + {/* 企业列表 */} + {isLoading ? ( +
+ +
+ ) : companies.length === 0 ? ( + + ) : ( +
+ {companies.map((company) => ( + +
+
+
+
+
+

+ {company.name} +

+

{company.industry || "未分类"}

+
+
+ +
+ +

+ {company.description || "暂无描述"} +

+ +
+
+ {company.stage && ( + + {company.stage.toUpperCase()} + + )} + {company.total_funding && ( + + {company.total_funding} + + )} +
+
+ + ))} +
+ )} + + {/* 分页 */} + {total > pageSize && ( +
+ + + 第 {page} 页 / 共 {Math.ceil(total / pageSize)} 页 + + +
+ )} +
+ ); +} diff --git a/frontend/src/lib/companies.ts b/frontend/src/lib/companies.ts new file mode 100644 index 0000000..4a4e162 --- /dev/null +++ b/frontend/src/lib/companies.ts @@ -0,0 +1,90 @@ +/** 企业相关类型和 API 函数。 */ + +import { apiFetch, type ApiResponse } from "./api"; + +/** 企业信息。 */ +export interface Company { + id: string; + tenant_id: string; + name: string; + industry: string | null; + stage: string | null; + logo_url: string | null; + description: string | null; + founded_at: string | null; + total_funding: string | null; + website: string | null; + created_at: string; + updated_at: string; +} + +/** 企业列表响应。 */ +export interface CompanyListResponse { + items: Company[]; + total: number; + page: number; + page_size: number; +} + +/** 创建/更新企业参数。 */ +export interface CompanyInput { + name: string; + industry?: string | null; + stage?: string | null; + description?: string | null; + website?: string | null; + total_funding?: string | null; +} + +/** + * 获取企业列表。 + */ +export async function listCompanies(params?: { + page?: number; + page_size?: number; + keyword?: string; + industry?: string; + stage?: string; +}): Promise> { + const query = new URLSearchParams(); + if (params?.page) query.set("page", String(params.page)); + if (params?.page_size) query.set("page_size", String(params.page_size)); + if (params?.keyword) query.set("keyword", params.keyword); + if (params?.industry) query.set("industry", params.industry); + if (params?.stage) query.set("stage", params.stage); + return apiFetch(`/companies?${query.toString()}`); +} + +/** + * 获取企业详情。 + */ +export async function getCompany(id: string): Promise> { + return apiFetch(`/companies/${id}`); +} + +/** + * 创建企业。 + */ +export async function createCompany(data: CompanyInput): Promise> { + return apiFetch("/companies", { + method: "POST", + body: JSON.stringify(data), + }); +} + +/** + * 更新企业。 + */ +export async function updateCompany(id: string, data: Partial): Promise> { + return apiFetch(`/companies/${id}`, { + method: "PUT", + body: JSON.stringify(data), + }); +} + +/** + * 删除企业。 + */ +export async function deleteCompany(id: string): Promise> { + return apiFetch(`/companies/${id}`, { method: "DELETE" }); +}