M2c:前端镜树页(路径阅读流/候答切换/采用归真/局部树图/SSE 芽体)与灵胚页(创建/导入卡/照心试演/启镜),契约错误码段接入前端
This commit is contained in:
1 parent
30447fc40f
commit
2a640a901b
21 files changed
+2126
-4
No files matched your search
@@ -0,0 +1,32 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { EmbryoManager } from "@/components/embryos/EmbryoManager";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { EmbryoDraftView } from "@/types/mirror-tree";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function EmbryosPage() {
|
||||
let embryos: EmbryoDraftView[] = [];
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
embryos = await serverApi.get<EmbryoDraftView[]>("/embryo-drafts");
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title="灵胚"
|
||||
description="启镜前的可审阅草稿:原创、导入或照心共塑;试演不入正式经历。"
|
||||
>
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<EmbryoManager initialEmbryos={embryos} />
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { TreeWorkspace } from "@/components/trees/TreeWorkspace";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type {
|
||||
GraphResponse,
|
||||
MirrorTreeView,
|
||||
PathResponse,
|
||||
} from "@/types/mirror-tree";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function TreeDetailPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ treeId: string }>;
|
||||
}) {
|
||||
const { treeId } = await params;
|
||||
|
||||
let tree: MirrorTreeView | null = null;
|
||||
let error: string | null = null;
|
||||
try {
|
||||
tree = await serverApi.get<MirrorTreeView>(`/trees/${treeId}`);
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
if (error || !tree) {
|
||||
return (
|
||||
<PageContainer title="镜树">
|
||||
<ErrorState message={error ?? "树不存在。"} />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const [pathData, graphData] = await Promise.all([
|
||||
serverApi.get<PathResponse>(`/trees/${treeId}/path`).catch(() => null),
|
||||
serverApi
|
||||
.get<GraphResponse>(`/trees/${treeId}/local-graph`, { depth: 4 })
|
||||
.catch(() => null),
|
||||
]);
|
||||
if (!pathData || !graphData) {
|
||||
return (
|
||||
<PageContainer title="镜树">
|
||||
<ErrorState message="树数据读取失败,请刷新重试。" />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
title={tree.title}
|
||||
description="当前路径阅读 · 采用/归真分离 · 局部树图定位。"
|
||||
>
|
||||
<TreeWorkspace
|
||||
tree={tree}
|
||||
initialPath={pathData}
|
||||
initialGraph={graphData}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { PageContainer } from "@/components/layout/PageContainer";
|
||||
import { ErrorState } from "@/components/shared/StateViews";
|
||||
import { serverApi } from "@/lib/api/server-client";
|
||||
import { describeError } from "@/lib/utils/error";
|
||||
import type { Paginated, Character } from "@/types/api";
|
||||
import type { MirrorTreeView } from "@/types/mirror-tree";
|
||||
|
||||
import { TreeCreateForm } from "@/components/trees/TreeCreateForm";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function TreesPage() {
|
||||
let trees: MirrorTreeView[] = [];
|
||||
let characterId: string | undefined;
|
||||
let error: string | null = null;
|
||||
|
||||
try {
|
||||
const active = await serverApi
|
||||
.get<Character>("/characters/active")
|
||||
.catch(() => null);
|
||||
characterId = active?.id;
|
||||
// 树列表为非分页数组端点;兼容分页包装
|
||||
const data = await serverApi.get<
|
||||
MirrorTreeView[] | Paginated<MirrorTreeView>
|
||||
>("/trees");
|
||||
trees = Array.isArray(data) ? data : data.items;
|
||||
} catch (e) {
|
||||
error = describeError(e);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer title="镜树" description="围绕主题自然生长的树状对话。">
|
||||
{error ? (
|
||||
<ErrorState message={error} />
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{characterId && <TreeCreateForm characterId={characterId} />}
|
||||
{trees.length === 0 ? (
|
||||
<p className="rounded-xl border border-dashed border-rule p-6 text-center text-sm text-muted">
|
||||
还没有镜树。创建一棵,从第一个问题开始。
|
||||
</p>
|
||||
) : (
|
||||
<ul className="grid gap-3 sm:grid-cols-2">
|
||||
{trees.map((tree) => (
|
||||
<li key={tree.id}>
|
||||
<Link
|
||||
href={`/trees/${tree.id}`}
|
||||
className="block rounded-xl border border-rule bg-surface p-4 transition-colors hover:border-patina"
|
||||
>
|
||||
<h2 className="font-medium">{tree.title}</h2>
|
||||
<p className="mt-1 text-xs text-muted">
|
||||
{tree.transplanted_from?.source_tree_id
|
||||
? "移栽自另一棵树 · "
|
||||
: ""}
|
||||
创建于 {new Date(tree.created_at).toLocaleString("zh-CN")}
|
||||
</p>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user