feat(web): read-only identity pool, decisions happen in 待处理

Pool detail keeps 重新识别 only; rows with an open identity decision show
已进待处理 and link to /console/inbox?kind=identity_ambiguous.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 02:46:55 +08:00
1 parent ddb15145d8
commit f38758bd26
2 files changed
+72 -160

No files matched your search

+40 -7
View File
@@ -5,16 +5,16 @@ import type {
IdentityPoolItem,
IdentityStatus,
} from "@umt/contracts";
import { Fingerprint, Lock, SearchX } from "lucide-react";
import { ArrowRight, Fingerprint, Inbox, Lock, SearchX } from "lucide-react";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router";
import { Link, useSearchParams } from "react-router";
import { useExternalIdentities, useIdentityPool } from "~/api/identity.ts";
import { type DataColumn, DataTable } from "~/components/console/data-table.tsx";
import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { AliasesPanel } from "~/components/library/identity-aliases.tsx";
import { ExternalIdentitySheet, identityRef } from "~/components/library/identity-external-sheet.tsx";
import { PoolSheet, querySummary } from "~/components/library/identity-pool-sheet.tsx";
import { INBOX_IDENTITY, inInbox, PoolSheet, querySummary } from "~/components/library/identity-pool-sheet.tsx";
import {
EVIDENCE_LEVEL,
IDENTITY_STATUS,
@@ -24,9 +24,13 @@ import {
VERIFY_STATUS,
} from "~/components/library/labels.ts";
import { relTime, Tone } from "~/components/library/parts.tsx";
import { Button } from "~/components/ui/button.tsx";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs.tsx";
// 身份与元数据(REQ §7.2、§7.5):待识别池、外部身份映射与修订、别名。
// 身份与元数据(REQ §7.2、§7.5):待识别资源、外部身份映射与修订、别名。
// 待识别资源只读;选候选、忽略在待处理里做(docs/design/console-ia.md §3.1 判定只在待处理)。
export const meta = () => [{ title: "身份与元数据 · UHD 媒体自动化控制台" }];
function useDebounced(v: string, ms = 250) {
const [d, setD] = useState(v);
@@ -65,7 +69,25 @@ function PoolTab() {
</div>
),
},
{ key: "reason", header: "原因", cell: (r) => <Tone v={POOL_REASON[r.reason]} /> },
{
key: "reason",
header: "原因",
cell: (r) => (
<div className="flex flex-col items-start gap-1">
<Tone v={POOL_REASON[r.reason]} />
{inInbox(r) ? (
<Link
to={INBOX_IDENTITY}
onClick={(e) => e.stopPropagation()}
className="inline-flex items-center gap-0.5 text-meta whitespace-nowrap text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
>
已进待处理
<ArrowRight className="h-3 w-3" />
</Link>
) : null}
</div>
),
},
{
key: "cands",
header: "候选",
@@ -251,7 +273,18 @@ export default function IdentityPage() {
const tab = (TABS as readonly string[]).includes(params.get("tab") ?? "") ? params.get("tab")! : "pool";
return (
<>
<PageHeader title="身份与元数据" description="以本地 ID 为准。自动匹配不覆盖人工锁定的外部身份。" />
<PageHeader
title="身份与元数据"
description="以本地 ID 为准。自动匹配不覆盖人工锁定的外部身份。"
actions={
<Button asChild variant="outline" size="sm">
<Link to={INBOX_IDENTITY}>
<Inbox />
待确认身份
</Link>
</Button>
}
/>
<Tabs
value={tab}
onValueChange={(v) =>
@@ -265,7 +298,7 @@ export default function IdentityPage() {
}
>
<TabsList>
<TabsTrigger value="pool">待识别池</TabsTrigger>
<TabsTrigger value="pool">待识别</TabsTrigger>
<TabsTrigger value="external">外部身份</TabsTrigger>
<TabsTrigger value="aliases">别名</TabsTrigger>
</TabsList>