收口创作入口契约后台表单
将统一创作契约泥点消耗改为数字字段并由前端格式化展示 将后台契约编辑从 JSON 文本改为结构化卡片与弹窗表单 隐藏玩法阶段等内部标识并按玩法默认映射自动带出 更新创作入口文档、团队记忆和回归测试
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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泥点数',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { PlatformCreationTypeId } from '../../packages/shared/src/contracts/playTypes';
|
||||
import { requestJson } from './apiClient';
|
||||
|
||||
export const DEFAULT_UNIFIED_CREATION_MUD_POINT_COST = 10;
|
||||
|
||||
/** 后端下发的单个创作类型入口配置,前端只据此展示和分流。 */
|
||||
export type CreationEntryTypeConfig = {
|
||||
id: PlatformCreationTypeId;
|
||||
@@ -30,6 +32,7 @@ export type UnifiedCreationField = {
|
||||
export type UnifiedCreationSpec = {
|
||||
playId: PlatformCreationTypeId;
|
||||
title: string;
|
||||
mudPointCost?: number | null;
|
||||
workspaceStage: string;
|
||||
generationStage: string;
|
||||
resultStage: string;
|
||||
|
||||
Reference in New Issue
Block a user