收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user