收口创作入口契约后台表单

将统一创作契约泥点消耗改为数字字段并由前端格式化展示
将后台契约编辑从 JSON 文本改为结构化卡片与弹窗表单
隐藏玩法阶段等内部标识并按玩法默认映射自动带出
更新创作入口文档、团队记忆和回归测试
This commit is contained in:
2026-06-07 23:53:26 +08:00
parent 2a6da01307
commit 17662916cd
13 changed files with 822 additions and 108 deletions

View File

@@ -241,7 +241,7 @@ test('creation start card renders reference-aligned banner and template metadata
expect(html).toContain('creation-template-card__body');
expect(html).toContain('creation-template-card__cost-badge');
expect(html).toContain('拼图关卡创作');
expect(html).toContain('10-20泥点数');
expect(html).toContain('10泥点数');
expect(html).toContain('即将开放');
expect(html).toContain('data-locked="true"');
expect(html).toContain('暂未开放');
@@ -292,7 +292,62 @@ test('locked creation template card replaces mud point cost with unavailable sta
expect(html).toContain('data-locked="true"');
expect(html).toContain('即将开放');
expect(html).toContain('暂未开放');
expect(html).not.toContain('10-20泥点数');
expect(html).not.toContain('10泥点数');
});
test('creation template card renders mud point cost from unified creation spec', () => {
const config = {
...testEntryConfig,
creationTypes: [
{
id: 'puzzle',
title: '拼图',
subtitle: '拼图关卡创作',
badge: '可创建',
imageSrc: '/creation-type-references/puzzle.webp',
visible: true,
open: true,
sortOrder: 30,
categoryId: 'recommended',
categoryLabel: '热门推荐',
categorySortOrder: 20,
updatedAtMicros: 1,
unifiedCreationSpec: {
playId: 'puzzle',
title: '拼图',
mudPointCost: 12,
workspaceStage: 'puzzle-agent-workspace',
generationStage: 'puzzle-generating',
resultStage: 'puzzle-result',
fields: [
{
id: 'pictureDescription',
kind: 'text',
label: '画面描述',
required: true,
},
],
},
},
],
} satisfies CreationEntryConfig;
const html = renderToStaticMarkup(
<CustomWorldCreationHub
items={[]}
loading={false}
error={null}
onRetry={() => {}}
onCreateType={noopCreateType}
onOpenDraft={() => {}}
onEnterPublished={() => {}}
entryConfig={config}
creationTypes={derivePlatformCreationTypes(config.creationTypes)}
mode="start-only"
/>,
);
expect(html).toContain('12泥点数');
expect(html).not.toContain('10泥点数');
});
test('creation start card falls back to legacy single banner when eventBanners is empty', () => {

View File

@@ -33,7 +33,7 @@ function shouldShowCreationBadge(badge: string) {
}
/** 从后端入口配置中解析创作入口公告位,保留旧单条字段兜底。 */
export function resolveCreationEntryEventBanners(
function resolveCreationEntryEventBanners(
entryConfig: CreationEntryConfig,
): CreationEventBannerCard[] {
const configuredBanners = Array.isArray(entryConfig.eventBanners)
@@ -379,7 +379,7 @@ export function CustomWorldCreationStartCard({
<Coins className="h-3 w-3 shrink-0" />
)}
<span className="truncate">
{item.locked ? '暂未开放' : '10-20泥点数'}
{item.locked ? '暂未开放' : item.mudPointCostLabel}
</span>
</span>
</div>

View File

@@ -3,8 +3,8 @@ import { afterEach, expect, test, vi } from 'vitest';
import type { CreationEntryTypeConfig } from '../../services/creationEntryConfigService';
import {
derivePlatformCreationTypes,
groupVisiblePlatformCreationTypes,
getVisiblePlatformCreationTypes,
groupVisiblePlatformCreationTypes,
isPlatformCreationTypeOpen,
isPlatformCreationTypeVisible,
} from './platformEntryCreationTypes';
@@ -42,6 +42,22 @@ test('database entry config controls visibility open state and display order', (
categoryLabel: '最近创作',
categorySortOrder: 10,
updatedAtMicros: 1,
unifiedCreationSpec: {
playId: 'match3d',
title: '抓大鹅',
mudPointCost: 8,
workspaceStage: 'match3d-agent-workspace',
generationStage: 'match3d-generating',
resultStage: 'match3d-result',
fields: [
{
id: 'themeText',
kind: 'text',
label: '题材',
required: true,
},
],
},
},
{
id: 'square-hole',
@@ -64,6 +80,7 @@ test('database entry config controls visibility open state and display order', (
id: 'match3d',
locked: false,
hidden: false,
mudPointCostLabel: '8泥点数',
}),
expect.objectContaining({
id: 'square-hole',
@@ -75,6 +92,7 @@ test('database entry config controls visibility open state and display order', (
title: '数据库拼图',
locked: true,
hidden: false,
mudPointCostLabel: '10泥点数',
}),
]);
});

View File

@@ -2,7 +2,10 @@ import {
assertPlatformCreationTypeId,
type PlatformCreationTypeId,
} from '../../../packages/shared/src/contracts/playTypes';
import type { CreationEntryTypeConfig } from '../../services/creationEntryConfigService';
import {
type CreationEntryTypeConfig,
DEFAULT_UNIFIED_CREATION_MUD_POINT_COST,
} from '../../services/creationEntryConfigService';
import { isEdutainmentEntryEnabled } from './platformEdutainmentVisibility';
export type { PlatformCreationTypeId };
@@ -13,6 +16,7 @@ export type PlatformCreationTypeCard = {
subtitle: string;
badge: string;
imageSrc: string;
mudPointCostLabel: string;
locked: boolean;
categoryId: string;
categoryLabel: string;
@@ -32,6 +36,16 @@ const RECENT_CREATION_CATEGORY_ID = 'recent';
const FALLBACK_CREATION_CATEGORY_ID = 'recommended';
const FALLBACK_CREATION_CATEGORY_LABEL = '热门推荐';
function normalizeMudPointCost(value: number | null | undefined) {
return typeof value === 'number' && Number.isFinite(value) && value > 0
? Math.trunc(value)
: DEFAULT_UNIFIED_CREATION_MUD_POINT_COST;
}
function formatMudPointCostText(value: number | null | undefined) {
return `${normalizeMudPointCost(value)}泥点数`;
}
export function getVisiblePlatformCreationTypes(
creationTypes: readonly PlatformCreationTypeCard[],
) {
@@ -130,6 +144,9 @@ export function derivePlatformCreationTypes(
subtitle: item.subtitle,
badge: item.badge,
imageSrc: item.imageSrc,
mudPointCostLabel: formatMudPointCostText(
item.unifiedCreationSpec?.mudPointCost,
),
locked: !item.open,
categoryId: normalizeCategoryId(item.categoryId),
categoryLabel: normalizeCategoryLabel(item.categoryLabel),