"use client"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; import { api, ApiError, type Character, type CursorPage } from "@/lib/api"; import { PanelState } from "@/lib/queries"; /** 角色状态 → 徽章(稳定值来自后端,文化名前端本地化) */ const STATUS_LABEL: Record = { draft: { cn: "灵胚", plain: "草稿", tone: "warn" }, active: { cn: "临照", plain: "运行中", tone: "info" }, resting: { cn: "静修", plain: "停止运行", tone: "muted" }, archived: { cn: "归藏", plain: "已隐藏", tone: "muted" }, deceased: { cn: "死亡", plain: "永久闭合", tone: "muted" }, }; function StatusBadge({ status }: { status: string }) { const label = STATUS_LABEL[status] ?? { cn: status, plain: "未知状态", tone: "muted" as const }; return ( {label.cn} · {label.plain} ); } /** 镜生页:左列真实列表 + 右列真实详情(选中联动) */ export function CharactersPagePanel() { const qc_client = { key: "characters" }; const [selectedId, setSelectedId] = useState(null); const [name, setName] = useState(""); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); const list = useQuery({ queryKey: [qc_client.key], queryFn: () => api>("/v1/characters?limit=20"), }); const detail = useQuery({ queryKey: ["character", selectedId], enabled: !!selectedId, queryFn: () => api(`/v1/characters/${selectedId}`), }); async function create(e: React.FormEvent) { e.preventDefault(); if (!name.trim()) return; setError(null); try { const c = await api("/v1/characters", { method: "POST", json: { name: name.trim(), summary: "" } }); setName(""); setCreating(false); list.refetch(); setSelectedId(c.id); } catch (err) { setError(err instanceof ApiError ? err.message : "创建失败"); } } async function activate(id: string) { try { await api(`/v1/characters/${id}/activations`, { method: "POST", json: {} }); list.refetch(); detail.refetch(); } catch (err) { setError(err instanceof ApiError ? err.message : "启镜失败"); } } const items = list.data?.data ?? []; return (
镜生{items.length} 位角色
{list.isPending &&

正在读取…

} {list.isError && (
{(list.error as ApiError).status === 401 || (list.error as ApiError).status === 403 ? ( <>

尚未登入,无法读取你的镜域。

去登入 ) : ( <>

读取失败

)}
)} {items.length === 0 && !list.isPending && !list.isError && (

镜域里还没有角色。

从一位灵胚(角色草稿)开始——可删除,不等于角色死亡。

)} {items.map((c) => (
setSelectedId(c.id)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter") setSelectedId(c.id); }} aria-current={selectedId === c.id ? "true" : undefined} > {c.name.slice(0, 1)}
{c.name}
{c.status === "draft" ? "灵胚 · 未启镜" : c.status} · {new Date(c.created_at).toLocaleDateString("zh-CN")}
))} {creating && (
setName(e.target.value)} aria-label="新角色名" autoFocus />
)} {error &&

{error}

}
{!selectedId && (
镜生选择一位角色查看详情

详情包含道体、内驱力(可编辑)、大周天与生命周期操作——全部来自真实接口。

)} {selectedId && ( detail.refetch()}> {(() => { const c = detail.data as Character; if (!c) return null; return (
); })()} )}
); }