收口前端平台组件库能力

新增 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

@@ -5,7 +5,12 @@ import type {
PuzzleClearDraftResponse,
PuzzleClearWorkProfileResponse,
} from '../../../packages/shared/src/contracts/puzzleClear';
import { ResolvedAssetImage } from '../ResolvedAssetImage';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformMediaTileGrid } from '../common/PlatformMediaTileGrid';
import { PlatformStatGrid } from '../common/PlatformStatGrid';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
type PuzzleClearResultViewProps = {
profile: PuzzleClearDraftResponse | PuzzleClearWorkProfileResponse;
@@ -42,14 +47,17 @@ export function PuzzleClearResultView({
const isWorkProfile = isPuzzleClearWorkProfile(profile);
const draft = getDraft(profile);
const summary = isWorkProfile ? profile.summary : null;
const title = summary?.workTitle?.trim() || draft.workTitle.trim() || '拼消消';
const title =
summary?.workTitle?.trim() || draft.workTitle.trim() || '拼消消';
const description =
summary?.workDescription?.trim() || draft.workDescription.trim();
const boardBackgroundAsset = isWorkProfile
? profile.boardBackgroundAsset ?? draft.boardBackgroundAsset
? (profile.boardBackgroundAsset ?? draft.boardBackgroundAsset)
: draft.boardBackgroundAsset;
const atlasAsset = isWorkProfile ? profile.atlasAsset : draft.atlasAsset;
const patternGroups = isWorkProfile ? profile.patternGroups : draft.patternGroups;
const patternGroups = isWorkProfile
? profile.patternGroups
: draft.patternGroups;
const cardAssets = isWorkProfile ? profile.cardAssets : draft.cardAssets;
const previewCards = cardAssets.slice(0, 24);
const canPublish = Boolean(isWorkProfile && summary?.publishReady);
@@ -66,27 +74,29 @@ export function PuzzleClearResultView({
return (
<div className="platform-remap-surface mx-auto flex h-full min-h-0 w-full max-w-6xl flex-col px-3 pb-3 pt-3 sm:px-4 sm:pt-4">
<div className="mb-3 flex items-center justify-between gap-3">
<button
type="button"
<PlatformActionButton
onClick={onBack}
className="platform-button platform-button--ghost min-h-0 px-3 py-2 text-sm"
tone="ghost"
size="xs"
className="min-h-0 gap-2 py-2 text-sm"
>
<ArrowLeft className="h-4 w-4" />
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
onClick={onRegenerateAtlas}
disabled={isBusy}
className="platform-button platform-button--ghost min-h-0 px-3 py-2 text-sm"
tone="ghost"
size="xs"
className="min-h-0 gap-2 py-2 text-sm"
>
<RefreshCcw className="h-4 w-4" />
</button>
</PlatformActionButton>
</div>
<div className="grid min-h-0 flex-1 gap-3 lg:grid-cols-[minmax(0,1.05fr)_minmax(19rem,0.95fr)]">
<section className="platform-subpanel flex min-h-0 flex-col rounded-[1.25rem] p-4">
<PlatformSubpanel className="flex min-h-0 flex-col" radius="md">
<div className="text-2xl font-black text-[var(--platform-text-strong)]">
{title}
</div>
@@ -97,105 +107,90 @@ export function PuzzleClearResultView({
) : null}
<div className="mt-4 grid min-h-0 flex-1 gap-3 sm:grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)]">
<div className="overflow-hidden rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/80">
{boardBackgroundAsset?.imageSrc ? (
<ResolvedAssetImage
src={boardBackgroundAsset.imageSrc}
alt="场地底图"
className="aspect-[9/16] h-full w-full object-cover"
/>
) : (
<div className="grid aspect-[9/16] place-items-center text-sm text-[var(--platform-text-soft)]">
</div>
)}
</div>
<PlatformSubpanel
as="div"
surface="flat"
radius="sm"
padding="none"
className="overflow-hidden bg-white/80"
>
<PlatformMediaFrame
src={boardBackgroundAsset?.imageSrc}
alt="场地底图"
fallbackLabel="底图"
aspect="portrait"
surface="none"
className="rounded-none"
fallbackClassName="tracking-normal text-[var(--platform-text-soft)]"
/>
</PlatformSubpanel>
<div className="flex min-h-0 flex-col gap-3">
<div className="overflow-hidden rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/80">
{atlasAsset?.imageSrc ? (
<ResolvedAssetImage
src={atlasAsset.imageSrc}
alt="素材图集"
className="aspect-square w-full object-cover"
/>
) : (
<div className="grid aspect-square place-items-center text-sm text-[var(--platform-text-soft)]">
</div>
)}
</div>
<PlatformSubpanel
as="div"
surface="flat"
radius="sm"
padding="none"
className="overflow-hidden bg-white/80"
>
<PlatformMediaFrame
src={atlasAsset?.imageSrc}
alt="素材图集"
fallbackLabel="图集"
aspect="square"
surface="none"
className="rounded-none"
fallbackClassName="tracking-normal text-[var(--platform-text-soft)]"
/>
</PlatformSubpanel>
<div className="grid grid-cols-6 gap-1.5">
{previewCards.map((card) => (
<div
key={card.cardId}
className="aspect-square overflow-hidden rounded-[0.45rem] border border-white/80 bg-white shadow-sm"
>
<ResolvedAssetImage
src={card.imageSrc}
alt=""
className="h-full w-full object-cover"
/>
</div>
))}
</div>
<PlatformMediaTileGrid
items={previewCards.map((card) => ({
id: card.cardId,
src: card.imageSrc,
fallbackLabel: '卡片',
}))}
/>
</div>
</div>
</section>
</PlatformSubpanel>
<aside className="flex min-h-0 flex-col gap-3 overflow-y-auto">
<section className="platform-subpanel rounded-[1.25rem] p-4">
<div className="grid grid-cols-3 gap-2 text-center">
<div className="rounded-[0.9rem] bg-white/76 px-2 py-3">
<div className="text-xl font-black text-[var(--platform-text-strong)]">
{patternGroups.length}
</div>
<div className="mt-1 text-[0.68rem] font-bold tracking-[0.14em] text-[var(--platform-text-soft)]">
</div>
</div>
<div className="rounded-[0.9rem] bg-white/76 px-2 py-3">
<div className="text-xl font-black text-[var(--platform-text-strong)]">
{cardAssets.length}
</div>
<div className="mt-1 text-[0.68rem] font-bold tracking-[0.14em] text-[var(--platform-text-soft)]">
</div>
</div>
<div className="rounded-[0.9rem] bg-white/76 px-2 py-3">
<div className="text-xl font-black text-[var(--platform-text-strong)]">
{draft.generationStatus}
</div>
<div className="mt-1 text-[0.68rem] font-bold tracking-[0.14em] text-[var(--platform-text-soft)]">
</div>
</div>
</div>
</section>
<PlatformSubpanel>
<PlatformStatGrid
items={[
{ label: '图案组', value: patternGroups.length },
{ label: '卡片', value: cardAssets.length },
{ label: '状态', value: draft.generationStatus },
]}
density="compact"
itemClassName="rounded-[0.9rem] py-3"
/>
</PlatformSubpanel>
{error ? (
<div className="platform-banner platform-banner--danger rounded-2xl text-sm leading-6">
<PlatformStatusMessage tone="error" surface="platform" size="md">
{error}
</div>
</PlatformStatusMessage>
) : null}
<section className="platform-subpanel mt-auto rounded-[1.25rem] p-4">
<PlatformSubpanel className="mt-auto">
<div className="grid gap-2">
<button
type="button"
<PlatformActionButton
onClick={onStartTestRun}
disabled={isBusy || !isWorkProfile}
className="platform-button platform-button--primary min-h-11 justify-center gap-2 px-4 py-3"
size="md"
className="min-h-11 gap-2"
>
<Play className="h-4 w-4" />
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
onClick={handlePublish}
disabled={isBusy || isPublishing || !canPublish}
className="platform-button platform-button--secondary min-h-11 justify-center gap-2 px-4 py-3"
tone="secondary"
size="md"
className="min-h-11 gap-2"
>
{isPublishing ? (
<Loader2 className="h-4 w-4 animate-spin" />
@@ -203,17 +198,18 @@ export function PuzzleClearResultView({
<Send className="h-4 w-4" />
)}
</button>
<button
type="button"
</PlatformActionButton>
<PlatformActionButton
onClick={onEdit}
disabled={isBusy}
className="platform-button platform-button--ghost min-h-11 justify-center px-4 py-3"
tone="ghost"
size="md"
className="min-h-11"
>
</button>
</PlatformActionButton>
</div>
</section>
</PlatformSubpanel>
</aside>
</div>
</div>