收口前端平台组件库能力
新增 PlatformUiKit 通用弹窗、按钮、状态、空态、媒体、表单和标签等公共组件 迁移结果页、创作工作台、认证入口、RPG 暗色面板和运行态弹窗的重复 UI chrome 补充组件测试、页面回归测试、技术文档和 Hermes 共享决策记录
This commit is contained in:
@@ -7,7 +7,13 @@ import {
|
||||
RefreshCw,
|
||||
Upload,
|
||||
} from 'lucide-react';
|
||||
import { type ChangeEvent, type ReactNode, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type ReactNode,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import type {
|
||||
BarkBattleConfigEditorPayload,
|
||||
@@ -18,6 +24,10 @@ import {
|
||||
regenerateBarkBattleImageAsset,
|
||||
uploadBarkBattleAsset,
|
||||
} from '../../services/bark-battle-creation';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformPillBadge } from '../common/PlatformPillBadge';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSubpanel } from '../common/PlatformSubpanel';
|
||||
import { BarkBattlePreviewCard } from './BarkBattlePreviewCard';
|
||||
|
||||
type BarkBattleResultViewProps = {
|
||||
@@ -36,7 +46,9 @@ const SLOT_LABELS = {
|
||||
'ui-background': 'UI背景',
|
||||
} satisfies Record<BarkBattleAssetSlot, string>;
|
||||
|
||||
function mapDraftToConfig(draft: BarkBattleDraftConfig): BarkBattleConfigEditorPayload {
|
||||
function mapDraftToConfig(
|
||||
draft: BarkBattleDraftConfig,
|
||||
): BarkBattleConfigEditorPayload {
|
||||
return {
|
||||
title: draft.title,
|
||||
description: draft.description,
|
||||
@@ -75,7 +87,10 @@ function applyAssetToDraft(
|
||||
return { ...draft, updatedAt };
|
||||
}
|
||||
|
||||
function getSlotAssetSrc(draft: BarkBattleDraftConfig, slot: BarkBattleAssetSlot) {
|
||||
function getSlotAssetSrc(
|
||||
draft: BarkBattleDraftConfig,
|
||||
slot: BarkBattleAssetSlot,
|
||||
) {
|
||||
if (slot === 'player-character') {
|
||||
return draft.playerCharacterImageSrc ?? '';
|
||||
}
|
||||
@@ -100,16 +115,14 @@ function ResultActionButton({
|
||||
tone?: 'primary' | 'secondary';
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={`platform-button ${
|
||||
tone === 'primary' ? 'platform-button--primary' : 'platform-button--secondary'
|
||||
} min-h-10 justify-center text-sm disabled:cursor-not-allowed disabled:opacity-55 sm:min-h-11`}
|
||||
tone={tone}
|
||||
className="min-h-10 gap-2 text-sm sm:min-h-11"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -177,7 +190,13 @@ function BarkBattleAssetSlotControl({
|
||||
const isSlotBusy = isUploading || isRegenerating;
|
||||
|
||||
return (
|
||||
<article className="rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/72 p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.74)] sm:p-3">
|
||||
<PlatformSubpanel
|
||||
as="article"
|
||||
surface="flat"
|
||||
radius="sm"
|
||||
padding="none"
|
||||
className="p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.74)] sm:p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2 sm:gap-3">
|
||||
<div className="min-w-0">
|
||||
<h3 className="m-0 text-xs font-black text-[var(--platform-text-strong)] sm:text-sm">
|
||||
@@ -202,26 +221,30 @@ function BarkBattleAssetSlotControl({
|
||||
aria-label={`上传${SLOT_LABELS[slot]}文件`}
|
||||
onChange={handleUpload}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
disabled={disabled || isSlotBusy}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="platform-button platform-button--secondary min-h-8 justify-center rounded-full px-2.5 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-55 sm:min-h-9 sm:px-3 sm:py-1.5 sm:text-xs"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="min-h-8 gap-1.5 px-2.5 py-1 text-[11px] sm:min-h-9 sm:px-3 sm:py-1.5 sm:text-xs"
|
||||
>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
上传
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</PlatformActionButton>
|
||||
<PlatformActionButton
|
||||
disabled={disabled || isSlotBusy}
|
||||
onClick={handleRegenerate}
|
||||
className="platform-button platform-button--secondary min-h-8 justify-center rounded-full px-2.5 py-1 text-[11px] disabled:cursor-not-allowed disabled:opacity-55 sm:min-h-9 sm:px-3 sm:py-1.5 sm:text-xs"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="min-h-8 gap-1.5 px-2.5 py-1 text-[11px] sm:min-h-9 sm:px-3 sm:py-1.5 sm:text-xs"
|
||||
>
|
||||
<RefreshCw className="h-3.5 w-3.5" />
|
||||
重新生成
|
||||
</button>
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
</article>
|
||||
</PlatformSubpanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,31 +266,43 @@ export function BarkBattleResultView({
|
||||
<div className="platform-page-stage platform-remap-surface flex h-full min-h-0 flex-col overflow-hidden px-2 pb-2 pt-2 sm:px-4 sm:pt-4 xl:px-5 xl:pb-4 xl:pt-4">
|
||||
<div className="mx-auto flex h-full min-h-0 w-full max-w-4xl flex-col">
|
||||
<div className="mb-2 flex shrink-0 items-center justify-between gap-2 sm:mb-3 sm:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
<PlatformActionButton
|
||||
onClick={onBack}
|
||||
disabled={isActionBusy}
|
||||
className={`platform-button platform-button--ghost min-h-0 px-3 py-1.5 text-[11px] ${isActionBusy ? 'opacity-45' : ''}`}
|
||||
tone="ghost"
|
||||
size="xs"
|
||||
className="min-h-0 gap-1.5 px-3 py-1.5 text-[11px]"
|
||||
>
|
||||
<ArrowLeft className="h-3.5 w-3.5" />
|
||||
返回编辑
|
||||
</button>
|
||||
<span className="rounded-full border border-emerald-200 bg-emerald-50 px-2.5 py-0.5 text-[11px] font-black text-emerald-700 sm:px-3 sm:py-1">
|
||||
</PlatformActionButton>
|
||||
<PlatformPillBadge
|
||||
tone="success"
|
||||
size="xs"
|
||||
className="sm:px-3 sm:py-1"
|
||||
>
|
||||
草稿
|
||||
</span>
|
||||
</PlatformPillBadge>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pr-0.5">
|
||||
<section className="grid gap-2.5 lg:grid-cols-[minmax(0,0.94fr)_minmax(18rem,0.86fr)] lg:gap-3">
|
||||
<div className="grid gap-2.5 lg:gap-3">
|
||||
<div className="rounded-[1.15rem] border border-[var(--platform-subpanel-border)] bg-white/68 p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.74)] sm:p-4">
|
||||
<PlatformSubpanel
|
||||
as="div"
|
||||
surface="flat"
|
||||
radius="md"
|
||||
padding="sm"
|
||||
className="shadow-[inset_0_1px_0_rgba(255,255,255,0.74)]"
|
||||
data-testid="bark-battle-draft-summary-panel"
|
||||
>
|
||||
<div className="text-xs font-black text-[var(--platform-text-soft)] sm:text-sm">
|
||||
草稿编译
|
||||
</div>
|
||||
<h1 className="m-0 mt-1 text-2xl font-black leading-tight tracking-normal text-[var(--platform-text-strong)] sm:mt-2 sm:text-4xl lg:text-5xl">
|
||||
{draft.title || '未命名声浪竞技场'}
|
||||
</h1>
|
||||
</div>
|
||||
</PlatformSubpanel>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{(
|
||||
[
|
||||
@@ -294,9 +329,14 @@ export function BarkBattleResultView({
|
||||
</section>
|
||||
|
||||
{visibleError ? (
|
||||
<div className="platform-banner platform-banner--danger mt-3 rounded-2xl text-sm leading-6">
|
||||
<PlatformStatusMessage
|
||||
tone="error"
|
||||
surface="platform"
|
||||
size="md"
|
||||
className="mt-3 rounded-2xl"
|
||||
>
|
||||
{visibleError}
|
||||
</div>
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user