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