收口前端平台组件库能力

新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件
迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome
补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
2026-06-10 10:24:18 +08:00
parent a4ee6ff698
commit 1ad25e30f8
226 changed files with 23364 additions and 7825 deletions

View File

@@ -16,8 +16,13 @@ import type {
BigFishSessionSnapshotResponse,
ExecuteBigFishActionRequest,
} from '../../../packages/shared/src/contracts/bigFish';
import { UnifiedModal } from '../common/UnifiedModal';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconBadge } from '../common/PlatformIconBadge';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { UnifiedConfirmDialog } from '../common/UnifiedConfirmDialog';
type BigFishAssetStudioTarget =
| {
@@ -94,12 +99,7 @@ function buildStudioAssetPreview(
);
}
return buildLevelAssetPreview(
findAssetSlot(
slots,
'level_motion',
target.level.level,
target.motionKey,
),
findAssetSlot(slots, 'level_motion', target.level.level, target.motionKey),
);
}
@@ -168,44 +168,42 @@ function BigFishAssetStudioModal({
</div>
</div>
<div className="space-y-4 px-4 py-4">
<div className="rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/72 p-4">
<div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
PROMPT
</div>
<PlatformSubpanel as="div" surface="flat">
<PlatformFieldLabel variant="section">PROMPT</PlatformFieldLabel>
<div className="mt-2 text-sm leading-6 text-[var(--platform-text-strong)]">
{prompt}
</div>
</div>
<div className="flex aspect-[9/5] items-center justify-center overflow-hidden rounded-[1.4rem] border border-dashed border-cyan-300/50 bg-cyan-50/40 text-sm text-[var(--platform-text-base)]">
{previewUrl ? (
<ResolvedAssetImage
src={previewUrl}
alt={title}
className="h-full w-full object-cover"
/>
) : (
'AI 资产候选预览'
)}
</div>
</PlatformSubpanel>
<PlatformMediaFrame
src={previewUrl}
alt={title}
fallbackLabel="AI 资产候选预览"
aspect="wide"
surface="none"
className="rounded-[1.4rem] border border-dashed border-cyan-300/50 bg-cyan-50/40"
fallbackClassName="tracking-normal text-[var(--platform-text-base)]"
/>
</div>
<div className="flex justify-end gap-2 border-t border-[var(--platform-subpanel-border)] px-4 py-4">
<button
type="button"
<PlatformActionButton
onClick={onClose}
disabled={isBusy}
className="rounded-full border border-[var(--platform-subpanel-border)] px-4 py-2 text-sm font-semibold text-[var(--platform-text-base)] disabled:opacity-45"
tone="ghost"
shape="pill"
size="xs"
>
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
onClick={execute}
disabled={isBusy}
className="inline-flex items-center gap-2 rounded-full bg-cyan-600 px-4 py-2 text-sm font-bold text-white disabled:opacity-45"
shape="pill"
size="xs"
className="gap-2"
>
{isBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
</button>
</PlatformActionButton>
</div>
</div>
</div>
@@ -223,11 +221,7 @@ function BigFishLevelCard({
isBusy: boolean;
onOpenStudio: (target: BigFishAssetStudioTarget) => void;
}) {
const mainImageSlot = findAssetSlot(
slots,
'level_main_image',
level.level,
);
const mainImageSlot = findAssetSlot(slots, 'level_main_image', level.level);
const idleSlot = findAssetSlot(
slots,
'level_motion',
@@ -243,19 +237,23 @@ function BigFishLevelCard({
const previewUrl = buildLevelAssetPreview(mainImageSlot);
return (
<article className="overflow-hidden rounded-[1.45rem] border border-[var(--platform-subpanel-border)] bg-white/78">
<PlatformSubpanel
as="article"
surface="flat"
radius="xl"
padding="none"
className="overflow-hidden bg-white/78"
>
<div className="flex gap-3 p-3">
<div className="flex h-24 w-24 shrink-0 items-center justify-center overflow-hidden rounded-[1.15rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.28),transparent_68%),linear-gradient(145deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))] text-white">
{previewUrl ? (
<ResolvedAssetImage
src={previewUrl}
alt={level.name}
className="h-full w-full object-cover"
/>
) : (
<Waves className="h-8 w-8 text-cyan-100/72" />
)}
</div>
<PlatformMediaFrame
src={previewUrl}
alt={level.name}
fallbackLabel="关卡主图"
fallbackContent={<Waves className="h-8 w-8 text-cyan-100/72" />}
aspect="square"
surface="none"
className="h-24 w-24 shrink-0 rounded-[1.15rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.28),transparent_68%),linear-gradient(145deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))] text-white"
/>
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div>
@@ -267,9 +265,13 @@ function BigFishLevelCard({
</div>
</div>
{level.isFinalLevel ? (
<span className="rounded-full bg-amber-100 px-2 py-1 text-xs font-bold text-amber-700">
<PlatformPillBadge
tone="warning"
size="xs"
className="px-2 py-1 text-xs font-bold"
>
</span>
</PlatformPillBadge>
) : null}
</div>
<div className="mt-2 line-clamp-2 text-sm leading-5 text-[var(--platform-text-base)]">
@@ -280,24 +282,25 @@ function BigFishLevelCard({
<span> {level.threatWindow.join('/') || '-'}</span>
<span> {assetReadyLabel(mainImageSlot)}</span>
<span>
{[assetReadyLabel(idleSlot), assetReadyLabel(moveSlot)].join('/')}
{' '}
{[assetReadyLabel(idleSlot), assetReadyLabel(moveSlot)].join('/')}
</span>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-2 border-t border-[var(--platform-subpanel-border)] p-3">
<button
type="button"
<PlatformActionButton
disabled={isBusy}
onClick={() => {
onOpenStudio({ kind: 'level_main_image', level });
}}
className="rounded-full bg-cyan-600 px-3 py-2 text-xs font-bold text-white disabled:opacity-45"
shape="pill"
size="xs"
className="px-3"
>
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
disabled={isBusy}
onClick={() => {
onOpenStudio({
@@ -306,12 +309,14 @@ function BigFishLevelCard({
motionKey: 'idle_float',
});
}}
className="rounded-full border border-[var(--platform-subpanel-border)] px-3 py-2 text-xs font-bold text-[var(--platform-text-base)] disabled:opacity-45"
tone="ghost"
shape="pill"
size="xs"
className="px-3"
>
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
disabled={isBusy}
onClick={() => {
onOpenStudio({
@@ -320,12 +325,15 @@ function BigFishLevelCard({
motionKey: 'move_swim',
});
}}
className="rounded-full border border-[var(--platform-subpanel-border)] px-3 py-2 text-xs font-bold text-[var(--platform-text-base)] disabled:opacity-45"
tone="ghost"
shape="pill"
size="xs"
className="px-3"
>
</button>
</PlatformActionButton>
</div>
</article>
</PlatformSubpanel>
);
}
@@ -366,9 +374,14 @@ export function BigFishResultView({
if (!draft) {
return (
<div className="flex h-full items-center justify-center">
<div className="platform-subpanel rounded-2xl px-5 py-4 text-sm text-[var(--platform-text-base)]">
<PlatformSubpanel
as="div"
radius="sm"
padding="none"
className="px-5 py-4 text-sm text-[var(--platform-text-base)]"
>
稿
</div>
</PlatformSubpanel>
</div>
);
}
@@ -456,7 +469,12 @@ export function BigFishResultView({
</div>
<aside className="min-h-0 space-y-3 overflow-y-auto">
<div className="rounded-[1.45rem] border border-[var(--platform-subpanel-border)] bg-[var(--platform-subpanel-fill)] p-4">
<PlatformSubpanel
as="section"
surface="flat"
radius="xl"
className="bg-[var(--platform-subpanel-fill)]"
>
<div className="flex items-center justify-between gap-2">
<div>
<div className="text-sm font-black text-[var(--platform-text-strong)]">
@@ -468,29 +486,36 @@ export function BigFishResultView({
</div>
<ImagePlus className="h-5 w-5 text-cyan-600" />
</div>
<div className="mt-3 aspect-[9/16] overflow-hidden rounded-[1.2rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.2),transparent_62%),linear-gradient(180deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))]">
{backgroundPreviewUrl ? (
<ResolvedAssetImage
src={backgroundPreviewUrl}
alt={`${draft.background.theme} 场地背景`}
className="h-full w-full object-cover"
/>
) : null}
</div>
<button
type="button"
<PlatformMediaFrame
src={backgroundPreviewUrl}
alt={`${draft.background.theme} 场地背景`}
fallbackLabel="场地背景"
fallbackContent={<span className="sr-only"></span>}
aspect="portrait"
surface="none"
className="mt-3 rounded-[1.2rem] bg-[radial-gradient(circle_at_center,rgba(34,211,238,0.2),transparent_62%),linear-gradient(180deg,rgba(8,47,73,0.88),rgba(15,23,42,0.94))]"
/>
<PlatformActionButton
disabled={isBusy}
onClick={() => {
setStudioTarget({ kind: 'stage_background' });
}}
className="mt-3 inline-flex w-full items-center justify-center gap-2 rounded-full bg-cyan-600 px-4 py-2 text-sm font-bold text-white disabled:opacity-45"
shape="pill"
size="xs"
fullWidth
className="mt-3 gap-2"
>
<Sparkles className="h-4 w-4" />
</button>
</div>
</PlatformActionButton>
</PlatformSubpanel>
<div className="rounded-[1.45rem] border border-[var(--platform-subpanel-border)] bg-[var(--platform-subpanel-fill)] p-4">
<PlatformSubpanel
as="section"
surface="flat"
radius="xl"
className="bg-[var(--platform-subpanel-fill)]"
>
<div className="text-sm font-black text-[var(--platform-text-strong)]">
</div>
@@ -504,12 +529,15 @@ export function BigFishResultView({
{session.assetCoverage.requiredLevelCount * 2}
</div>
<div>
{session.assetCoverage.backgroundReady ? '已完成' : '待生成'}
{' '}
{session.assetCoverage.backgroundReady ? '已完成' : '待生成'}
</div>
</div>
{isPublished ? (
<div className="mt-3 text-sm font-semibold text-emerald-600">
<div className="mt-3">
<PlatformPillBadge tone="success" size="sm">
</PlatformPillBadge>
</div>
) : blockers.length > 0 ? (
<div className="mt-3 space-y-1 text-xs leading-5 text-amber-700">
@@ -518,11 +546,13 @@ export function BigFishResultView({
))}
</div>
) : (
<div className="mt-3 text-sm font-semibold text-emerald-600">
<div className="mt-3">
<PlatformPillBadge tone="success" size="sm">
</PlatformPillBadge>
</div>
)}
</div>
</PlatformSubpanel>
</aside>
</div>
@@ -562,37 +592,32 @@ function BigFishResultErrorModal({
onClose: () => void;
}) {
return (
<UnifiedModal
<UnifiedConfirmDialog
open
title="发布失败"
onClose={onClose}
closeOnBackdrop={false}
showCloseButton={false}
confirmLabel="知道了"
confirmClassName="w-full justify-center border-slate-950 bg-slate-950 text-white"
size="sm"
zIndexClassName="z-[160]"
overlayClassName="bg-slate-950/58"
panelClassName="border-red-100/80 bg-white text-slate-950 shadow-2xl"
bodyClassName="p-5"
footer={(
<button
type="button"
onClick={onClose}
className="inline-flex w-full items-center justify-center rounded-full bg-slate-950 px-4 py-2.5 text-sm font-bold text-white"
>
</button>
)}
footerClassName="border-t-0 px-5 pb-5 pt-0"
>
<div className="flex items-start gap-3">
<div className="mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-red-50 text-red-600">
<Waves className="h-4 w-4" />
</div>
<PlatformIconBadge
icon={<Waves className="h-4 w-4" />}
label="发布失败提示"
tone="danger"
className="mt-0.5"
/>
<div className="min-w-0 flex-1 text-sm leading-6 text-slate-600">
{message}
</div>
</div>
</UnifiedModal>
</UnifiedConfirmDialog>
);
}