功能修改

This commit is contained in:
npc0-hue
2026-07-20 16:42:33 +08:00
parent 48b8ad8d6c
commit a0e69417db
224 changed files with 22015 additions and 884 deletions
+60 -10
View File
@@ -3,10 +3,12 @@ import { type ChangeEvent, useCallback, useEffect, useMemo, useState } from "rea
import { platformApiClient } from "../api/client";
import type { GamePluginStatus, MarketplacePluginFilterRequest, MarketplacePluginResponse, MarketplacePluginStateAction } from "../api/types";
import { ManagementDialog } from "../components/OperationControls";
import { ConfirmDialog, ManagementDialog } from "../components/OperationControls";
import { PluginLifecycleWorkbench } from "../components/PluginLifecycleWorkbench";
import { EmptyState, ErrorState, LoadingState, ResultBadge } from "../components/StateViews";
import { PageFrame } from "../components/PageFrame";
import type { PageComponentProps } from "../contracts/page";
import { isPlatformAdmin } from "../contracts/workspace";
import { cx } from "../utils/classes";
type ListState = "loading" | "ready" | "error";
@@ -34,7 +36,7 @@ const statusFilters: Array<{ id: StatusFilter; label: string }> = [
{ id: "updating", label: "更新中" }
];
export function PluginsPage({ initialState }: PluginsPageProps = {}) {
export function PluginsPage({ initialState, session, operations }: PluginsPageProps = {}) {
const [listState, setListState] = useState<ListState>(initialState?.listState ?? "loading");
const [listError, setListError] = useState(initialState?.listError ?? "");
const [plugins, setPlugins] = useState<MarketplacePluginResponse[]>(initialState?.plugins ?? []);
@@ -47,6 +49,7 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
const [serverType, setServerType] = useState("");
const [capability, setCapability] = useState("");
const [actionPending, setActionPending] = useState<MarketplacePluginStateAction | null>(null);
const [confirmAction, setConfirmAction] = useState<MarketplacePluginStateAction | null>(null);
const [actionResult, setActionResult] = useState<{ status: "succeeded" | "failed"; label: string } | null>(initialState?.actionResult ?? null);
const [usingFallback, setUsingFallback] = useState(initialState?.usingFallback ?? false);
@@ -110,6 +113,22 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
.finally(() => setDetailPending(false));
}, [plugins, selectedId, usingFallback]);
const retryDetail = useCallback(async () => {
if (!selectedId || usingFallback) {
return;
}
setDetailPending(true);
setDetailError("");
try {
const plugin = await platformApiClient.getMarketplacePlugin(selectedId);
setDetail(plugin);
} catch (error) {
setDetailError(error instanceof Error ? error.message : "插件详情加载失败");
} finally {
setDetailPending(false);
}
}, [selectedId, usingFallback]);
const serverTypes = useMemo(() => unique(plugins.map((plugin) => plugin.serverType)), [plugins]);
const capabilities = useMemo(() => unique(plugins.flatMap((plugin) => [...plugin.capabilities, ...plugin.bridgeActions])), [plugins]);
const installedCount = plugins.filter((plugin) => plugin.status === "installed").length;
@@ -129,6 +148,8 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
if (!detail || usingFallback) {
return;
}
const intent = `${stateActionLabel(action)}插件`;
const operationId = operations?.begin({ intent, targetKind: "plugin", targetId: detail.id, requester: session?.displayName });
setActionPending(action);
setActionResult(null);
try {
@@ -136,23 +157,37 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
setDetail(updated);
setPlugins((current) => current.map((plugin) => (plugin.id === updated.id ? updated : plugin)));
setActionResult({ status: "succeeded", label: `${updated.name}${stateActionLabel(action)}` });
if (operationId) operations?.succeed(operationId, `${updated.name}${stateActionLabel(action)}`);
setConfirmAction(null);
} catch (error) {
setActionResult({ status: "failed", label: error instanceof Error ? error.message : "状态更新失败" });
const reason = error instanceof Error ? error.message : "状态更新失败";
setActionResult({ status: "failed", label: reason });
if (operationId) operations?.fail(operationId, reason, operationId);
} finally {
setActionPending(null);
}
}
function requestStateChange(action: MarketplacePluginStateAction) {
if (!canManage || usingFallback || actionPending !== null) {
return;
}
setActionResult(null);
setConfirmAction(action);
}
const canManage = Boolean(session && isPlatformAdmin(session));
return (
<div className="console-page">
<PageFrame
kicker="扩展"
title="插件市场"
status={usingFallback ? "本地演示数据" : "平台 API"}
status={usingFallback ? "本地演示数据" : listState === "loading" ? "正在连接平台 API" : listState === "error" ? "平台 API 不可用" : "平台 API"}
metrics={[
{ label: "已安装", value: `${installedCount}`, tone: "success" },
{ label: "桥接动作", value: `${bridgeActionCount}`, tone: "success" },
{ label: "校验失败", value: `${invalidCount}`, tone: invalidCount > 0 ? "warning" : "success" }
{ label: "已安装", value: listState === "ready" ? `${installedCount}` : "--", tone: "success" },
{ label: "桥接动作", value: listState === "ready" ? `${bridgeActionCount}` : "--", tone: "success" },
{ label: "校验失败", value: listState === "ready" ? `${invalidCount}` : "--", tone: invalidCount > 0 ? "warning" : "success" }
]}
/>
@@ -196,6 +231,7 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
</div>
{usingFallback && <ResultBadge status="pending" label="本地演示数据仅用于前端开发,连接平台 API 后会自动替换" />}
{!canManage && !usingFallback && <ResultBadge status="pending" label="当前账号为只读模式,插件状态动作需要平台管理员权限" />}
{actionResult && <ResultBadge status={actionResult.status} label={actionResult.label} />}
{listState === "loading" && <LoadingState label="正在加载插件市场…" />}
@@ -248,13 +284,23 @@ export function PluginsPage({ initialState }: PluginsPageProps = {}) {
</section>
)}
<ManagementDialog open={selectedId !== ""} title={detail?.name ?? "插件详情"} wide onClose={() => { setSelectedId(""); setDetail(null); setDetailError(""); }}>
<ManagementDialog open={selectedId !== ""} title={detail?.name ?? "插件详情"} wide onClose={() => { if (actionPending === null) { setSelectedId(""); setDetail(null); setDetailError(""); setConfirmAction(null); } }}>
<div className="plugin-detail-panel" aria-label="plugin marketplace detail">
{detailPending && <LoadingState label="正在加载插件详情…" compact />}
{detailError && <ErrorState title="插件详情加载失败" reason={detailError} diagnosticId={`plugin-detail:${selectedId}`} compact />}
{detail && <PluginDetail plugin={detail} actionPending={actionPending} actionsDisabled={usingFallback} onAction={(action) => void changeState(action)} />}
{detailError && <ErrorState title="插件详情加载失败" reason={detailError} diagnosticId={`plugin-detail:${selectedId}`} onRetry={() => void retryDetail()} compact />}
{detail && <PluginDetail plugin={detail} actionPending={actionPending} actionsDisabled={usingFallback || !canManage} onAction={requestStateChange} />}
</div>
</ManagementDialog>
<ConfirmDialog
open={confirmAction !== null}
title="确认插件状态变更"
description={detail ? `将对 ${detail.name} 执行“${stateActionLabel(confirmAction ?? "disable")}”。平台会返回持久状态,失败时保留当前状态并允许重试。` : "请确认插件状态变更。"}
confirmLabel={stateActionLabel(confirmAction ?? "disable")}
danger={confirmAction === "disable"}
busy={actionPending !== null}
onCancel={() => { if (actionPending === null) setConfirmAction(null); }}
onConfirm={() => { if (confirmAction) void changeState(confirmAction); }}
/>
</div>
);
}
@@ -282,6 +328,7 @@ function PluginDetail({ plugin, actionPending, actionsDisabled, onAction }: Plug
<DetailStat label="权限" value={`${plugin.declaredPermissions.length}`} />
<DetailStat label="AI 用途" value={plugin.aiPurposes.length ? plugin.aiPurposes.join(", ") : "--"} />
</div>
<PluginLifecycleWorkbench pluginId={plugin.id} pluginName={plugin.name} operations={plugin.productionLifecycle?.operations} disabled={actionsDisabled} />
<dl className="detail-list">
<div>
<dt></dt>
@@ -345,5 +392,8 @@ function statusLabel(status: string): string {
}
function stateActionLabel(action: MarketplacePluginStateAction): string {
if (action === "install") {
return "安装";
}
return action === "disable" ? "停用" : "启用";
}