Merge remote-tracking branch 'origin/master' into hermes/wechat
# Conflicts: # .hermes/shared-memory/pitfalls.md # .hermes/todos/【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md
This commit is contained in:
@@ -18,7 +18,9 @@ export function RpgEntryBrandLogo({
|
||||
aria-hidden={decorative || undefined}
|
||||
aria-label={decorative ? undefined : '陶泥儿 GENARRATIVE'}
|
||||
>
|
||||
<span className="platform-brand-logo__title">陶泥儿</span>
|
||||
<span className="platform-brand-logo__title">
|
||||
陶泥<span className="platform-brand-logo__title-suffix">儿</span>
|
||||
</span>
|
||||
<span className="platform-brand-logo__subtitle">GENARRATIVE</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -94,6 +94,7 @@ import {
|
||||
} from '../../services/puzzle-runtime';
|
||||
import {
|
||||
dragLocalPuzzlePiece,
|
||||
startLocalPuzzleRun,
|
||||
swapLocalPuzzlePieces,
|
||||
} from '../../services/puzzle-runtime/puzzleLocalRuntime';
|
||||
import {
|
||||
@@ -142,6 +143,8 @@ import {
|
||||
listSquareHoleGallery,
|
||||
listSquareHoleWorks,
|
||||
} from '../../services/square-hole-works';
|
||||
import { listVisualNovelGallery } from '../../services/visual-novel-runtime';
|
||||
import { listVisualNovelWorks } from '../../services/visual-novel-works';
|
||||
import { type CustomWorldProfile, WorldType } from '../../types';
|
||||
import {
|
||||
AuthUiContext,
|
||||
@@ -289,10 +292,10 @@ const testCreationEntryConfig = {
|
||||
id: 'visual-novel',
|
||||
title: '视觉小说',
|
||||
subtitle: '分支叙事体验',
|
||||
badge: '可创建',
|
||||
badge: '敬请期待',
|
||||
imageSrc: '/creation-type-references/visual-novel.webp',
|
||||
visible: true,
|
||||
open: true,
|
||||
visible: false,
|
||||
open: false,
|
||||
sortOrder: 60,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
@@ -318,6 +321,17 @@ const testCreationEntryConfig = {
|
||||
sortOrder: 80,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
{
|
||||
id: 'baby-object-match',
|
||||
title: '宝贝识物',
|
||||
subtitle: '亲子识物分类',
|
||||
badge: '可创建',
|
||||
imageSrc: '/child-motion-demo/picture-book-grass-stage.png',
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 90,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
],
|
||||
} satisfies CreationEntryConfig;
|
||||
|
||||
@@ -526,6 +540,28 @@ vi.mock('../../services/square-hole-works', () => ({
|
||||
listSquareHoleWorks: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/visual-novel-runtime', () => ({
|
||||
listVisualNovelGallery: vi.fn(),
|
||||
startVisualNovelRun: vi.fn(),
|
||||
streamVisualNovelRuntimeAction: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/visual-novel-works', () => ({
|
||||
deleteVisualNovelWork: vi.fn(),
|
||||
getVisualNovelWorkDetail: vi.fn(),
|
||||
listVisualNovelWorks: vi.fn(),
|
||||
publishVisualNovelWork: vi.fn(),
|
||||
updateVisualNovelWork: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/visual-novel-creation', () => ({
|
||||
compileVisualNovelWorkProfile: vi.fn(),
|
||||
createVisualNovelSession: vi.fn(),
|
||||
executeVisualNovelAction: vi.fn(),
|
||||
getVisualNovelSession: vi.fn(),
|
||||
streamVisualNovelMessage: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../../services/creative-agent', () => ({
|
||||
cancelCreativeAgentSession: vi.fn(),
|
||||
confirmCreativePuzzleTemplate: vi.fn(),
|
||||
@@ -542,6 +578,10 @@ vi.mock('../../services/puzzle-runtime/puzzleLocalRuntime', async () => {
|
||||
return {
|
||||
...actual,
|
||||
dragLocalPuzzlePiece: vi.fn(actual.dragLocalPuzzlePiece),
|
||||
startLocalPuzzleRun: vi.fn(
|
||||
(...args: Parameters<typeof actual.startLocalPuzzleRun>) =>
|
||||
actual.startLocalPuzzleRun(...args),
|
||||
),
|
||||
swapLocalPuzzlePieces: vi.fn(actual.swapLocalPuzzlePieces),
|
||||
};
|
||||
});
|
||||
@@ -810,10 +850,12 @@ vi.mock('../match3d-runtime/Match3DRuntimeShell', () => ({
|
||||
Match3DRuntimeShell: ({
|
||||
run,
|
||||
generatedItemAssets = [],
|
||||
generatedBackgroundAsset = null,
|
||||
onBack,
|
||||
}: {
|
||||
run: Match3DRunSnapshot | null;
|
||||
generatedItemAssets?: Match3DWorkSummary['generatedItemAssets'];
|
||||
generatedBackgroundAsset?: Match3DWorkSummary['generatedBackgroundAsset'];
|
||||
onBack: () => void;
|
||||
}) => (
|
||||
<div className="match3d-runtime-shell-mock">
|
||||
@@ -873,6 +915,22 @@ vi.mock('../match3d-runtime/Match3DRuntimeShell', () => ({
|
||||
).length
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-top-level-background-count">
|
||||
{
|
||||
generatedBackgroundAsset?.imageSrc?.trim() ||
|
||||
generatedBackgroundAsset?.imageObjectKey?.trim()
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
</div>
|
||||
<div data-testid="match3d-runtime-top-level-container-ui-count">
|
||||
{
|
||||
generatedBackgroundAsset?.containerImageSrc?.trim() ||
|
||||
generatedBackgroundAsset?.containerImageObjectKey?.trim()
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
</div>
|
||||
<button type="button" onClick={onBack}>
|
||||
返回
|
||||
</button>
|
||||
@@ -1946,6 +2004,8 @@ beforeEach(() => {
|
||||
vi.mocked(upsertProfileBrowseHistory).mockResolvedValue([]);
|
||||
vi.mocked(clearProfileBrowseHistory).mockResolvedValue([]);
|
||||
vi.mocked(deleteRpgEntryWorldProfile).mockResolvedValue([]);
|
||||
vi.mocked(listVisualNovelGallery).mockResolvedValue({ works: [] });
|
||||
vi.mocked(listVisualNovelWorks).mockResolvedValue({ works: [] });
|
||||
vi.mocked(recordBigFishPlay).mockResolvedValue({ items: [] });
|
||||
vi.mocked(recordRpgEntryWorldGalleryPlay).mockImplementation(
|
||||
async (ownerUserId, profileId) => ({
|
||||
@@ -2672,6 +2732,30 @@ beforeEach(() => {
|
||||
vi.mocked(usePuzzleRuntimeProp).mockImplementation(async (runId) => ({
|
||||
run: buildMockPuzzleRun(`${runId}-profile`, '后端同步关卡'),
|
||||
}));
|
||||
vi.mocked(startLocalPuzzleRun).mockImplementation((item, levelId) => {
|
||||
const runId = `local-puzzle-run-${item.profileId}`;
|
||||
const firstLevel = item.levels?.[0] ?? null;
|
||||
return {
|
||||
...buildMockPuzzleRun(item.profileId, firstLevel?.levelName ?? item.levelName),
|
||||
runId,
|
||||
entryProfileId: item.profileId,
|
||||
currentLevel: {
|
||||
...buildMockPuzzleRun(item.profileId, firstLevel?.levelName ?? item.levelName)
|
||||
.currentLevel!,
|
||||
runId,
|
||||
levelId: levelId ?? firstLevel?.levelId ?? null,
|
||||
coverImageSrc: firstLevel?.coverImageSrc ?? item.coverImageSrc,
|
||||
uiBackgroundImageSrc:
|
||||
firstLevel?.uiBackgroundImageSrc ??
|
||||
(firstLevel?.uiBackgroundImageObjectKey
|
||||
? `/${firstLevel.uiBackgroundImageObjectKey.replace(/^\/+/u, '')}`
|
||||
: null),
|
||||
uiBackgroundImageObjectKey:
|
||||
firstLevel?.uiBackgroundImageObjectKey ?? null,
|
||||
backgroundMusic: firstLevel?.backgroundMusic ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
vi.mocked(submitPuzzleLeaderboard).mockImplementation(
|
||||
async (runId, payload) => ({
|
||||
run: {
|
||||
@@ -2795,15 +2879,15 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '拼图' }).querySelector('img')?.src,
|
||||
).toContain('/creation-type-references/puzzle.webp');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '视觉小说' }).querySelector('img')?.src,
|
||||
).toContain('/creation-type-references/visual-novel.webp');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: 'AIRP' }).querySelector('img')?.src,
|
||||
).toContain('/creation-type-references/airp.webp');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '抓大鹅' }).querySelector('img')?.src,
|
||||
).toContain('/creation-type-references/match3d.webp');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '宝贝识物' }).querySelector('img')?.src,
|
||||
).toContain('/child-motion-demo/picture-book-grass-stage.png');
|
||||
expect(
|
||||
screen.getByRole('tab', { name: '拼图' }).querySelector('.text-white'),
|
||||
).toBeTruthy();
|
||||
@@ -2814,7 +2898,9 @@ test('create tab shows template tabs and embeds puzzle form by default', async (
|
||||
expect(screen.queryByPlaceholderText('问一问陶泥儿')).toBeNull();
|
||||
expect(screen.queryByRole('button', { name: /角色扮演/u })).toBeNull();
|
||||
expect(screen.queryByRole('tab', { name: /方洞挑战/u })).toBeNull();
|
||||
expect(screen.queryByRole('tab', { name: '视觉小说' })).toBeNull();
|
||||
expect(screen.getByRole('tab', { name: /抓大鹅/u })).toBeTruthy();
|
||||
expect(screen.getByRole('tab', { name: /宝贝识物/u })).toBeTruthy();
|
||||
expect(createRpgCreationSession).not.toHaveBeenCalled();
|
||||
expect(match3dCreationClient.createSession).not.toHaveBeenCalled();
|
||||
expect(createPuzzleAgentSession).not.toHaveBeenCalled();
|
||||
@@ -3875,27 +3961,46 @@ test('puzzle draft generation auto starts trial and runtime back opens draft res
|
||||
await openCreateTemplateHub(user);
|
||||
await user.click(screen.getByRole('button', { name: '生成草稿' }));
|
||||
|
||||
expect(await screen.findByText('雨夜猫街')).toBeTruthy();
|
||||
expect(updatePuzzleWork).toHaveBeenCalledWith(
|
||||
'puzzle-profile-auto-1',
|
||||
expect.objectContaining({
|
||||
levelName: '雨夜猫街',
|
||||
coverImageSrc: '/puzzle/auto-candidate.png',
|
||||
levels: [
|
||||
expect.objectContaining({
|
||||
uiBackgroundImageSrc:
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/ui/background.png',
|
||||
backgroundMusic: expect.objectContaining({
|
||||
audioSrc:
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/audio/background.mp3',
|
||||
await waitFor(() => {
|
||||
expect(updatePuzzleWork).toHaveBeenCalledWith(
|
||||
'puzzle-profile-auto-1',
|
||||
expect.objectContaining({
|
||||
levelName: '雨夜猫街',
|
||||
coverImageSrc: '/puzzle/auto-candidate.png',
|
||||
levels: [
|
||||
expect.objectContaining({
|
||||
uiBackgroundImageSrc:
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/ui/background.png',
|
||||
backgroundMusic: expect.objectContaining({
|
||||
audioSrc:
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/audio/background.mp3',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(startLocalPuzzleRun).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const runtimeWork = vi.mocked(startLocalPuzzleRun).mock.calls[0]?.[0];
|
||||
expect(runtimeWork?.levels?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
uiBackgroundImageSrc:
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/ui/background.png',
|
||||
uiBackgroundImageObjectKey:
|
||||
'generated-puzzle-assets/puzzle-session-auto-1/ui/background.png',
|
||||
}),
|
||||
);
|
||||
const runtimeSnapshot = vi.mocked(startLocalPuzzleRun).mock.results[0]?.value;
|
||||
expect(runtimeSnapshot?.currentLevel?.uiBackgroundImageSrc).toBe(
|
||||
'/generated-puzzle-assets/puzzle-session-auto-1/ui/background.png',
|
||||
);
|
||||
expect(screen.queryByText('拼图结果页')).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '返回上一页' }));
|
||||
await user.click(
|
||||
await screen.findByRole('button', { name: '返回上一页' }),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('拼图结果页')).toBeTruthy();
|
||||
expect(screen.getByDisplayValue('雨夜猫街')).toBeTruthy();
|
||||
@@ -4705,6 +4810,30 @@ test('creation hub clears all private work shelves immediately after logout stat
|
||||
});
|
||||
});
|
||||
|
||||
test('creation draft hub skips visual novel shelves when entry is not open', async () => {
|
||||
const user = userEvent.setup();
|
||||
vi.mocked(fetchCreationEntryConfig).mockResolvedValue({
|
||||
...testCreationEntryConfig,
|
||||
creationTypes: testCreationEntryConfig.creationTypes.map((entry) =>
|
||||
entry.id === 'visual-novel' ? { ...entry, open: false } : entry,
|
||||
),
|
||||
});
|
||||
vi.mocked(listVisualNovelGallery).mockRejectedValue(
|
||||
new Error('该玩法入口暂不可用'),
|
||||
);
|
||||
vi.mocked(listVisualNovelWorks).mockRejectedValue(
|
||||
new Error('该玩法入口暂不可用'),
|
||||
);
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await openDraftHub(user);
|
||||
|
||||
expect(listVisualNovelGallery).not.toHaveBeenCalled();
|
||||
expect(listVisualNovelWorks).not.toHaveBeenCalled();
|
||||
expect(screen.queryByText('该玩法入口暂不可用')).toBeNull();
|
||||
});
|
||||
|
||||
test('published puzzle works appear on home and mobile game category channel', async () => {
|
||||
const user = userEvent.setup();
|
||||
const publishedPuzzleWork = {
|
||||
@@ -4950,6 +5079,80 @@ test('home recommendation Match3D runtime keeps image, music and UI assets witho
|
||||
);
|
||||
});
|
||||
|
||||
test('home recommendation Match3D runtime passes top-level UI background assets', async () => {
|
||||
const match3dCard: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-card-top-level-ui',
|
||||
profileId: 'match3d-profile-card-top-level-ui',
|
||||
ownerUserId: 'user-2',
|
||||
sourceSessionId: 'match3d-session-card-top-level-ui',
|
||||
gameName: '果园抓大鹅',
|
||||
themeText: '果园',
|
||||
summary: '消除果园素材。',
|
||||
tags: ['果园', '抓大鹅'],
|
||||
coverImageSrc: null,
|
||||
referenceImageSrc: null,
|
||||
clearCount: 3,
|
||||
difficulty: 5,
|
||||
publicationStatus: 'published',
|
||||
playCount: 3,
|
||||
updatedAt: '2026-04-25T10:30:00.000Z',
|
||||
publishedAt: '2026-04-25T10:30:00.000Z',
|
||||
publishReady: true,
|
||||
backgroundImageObjectKey:
|
||||
'generated-match3d-assets/session/profile/background/background.png',
|
||||
generatedBackgroundAsset: {
|
||||
prompt: '果园竖屏纯背景',
|
||||
imageSrc: null,
|
||||
imageObjectKey:
|
||||
'generated-match3d-assets/session/profile/background/background.png',
|
||||
containerPrompt: '果园浅盘容器',
|
||||
containerImageSrc: null,
|
||||
containerImageObjectKey:
|
||||
'generated-match3d-assets/session/profile/ui-container/container.png',
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
generatedItemAssets: [
|
||||
{
|
||||
itemId: 'match3d-item-1',
|
||||
itemName: '草莓',
|
||||
imageSrc:
|
||||
'/generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
|
||||
imageObjectKey:
|
||||
'generated-match3d-assets/session/profile/items/match3d-item-1-item/image.png',
|
||||
modelSrc: null,
|
||||
modelObjectKey: null,
|
||||
modelFileName: null,
|
||||
taskUuid: null,
|
||||
subscriptionKey: null,
|
||||
status: 'image_ready',
|
||||
error: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
vi.mocked(listMatch3DGallery).mockResolvedValue({
|
||||
items: [match3dCard],
|
||||
});
|
||||
match3dServerRuntimeAdapterMock.startRun.mockResolvedValue({
|
||||
run: buildMockMatch3DRun(match3dCard.profileId),
|
||||
});
|
||||
|
||||
render(<TestWrapper withAuth />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-top-level-background-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
});
|
||||
expect(
|
||||
screen.getByTestId('match3d-runtime-top-level-container-ui-count'),
|
||||
).toHaveProperty('textContent', '1');
|
||||
expect(getMatch3DWorkDetail).not.toHaveBeenCalledWith(
|
||||
'match3d-profile-card-top-level-ui',
|
||||
);
|
||||
});
|
||||
|
||||
test('home recommendation Match3D runtime reloads detail when card only has UI assets', async () => {
|
||||
const match3dCard: Match3DWorkSummary = {
|
||||
workId: 'match3d-work-card-ui-only',
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
LogIn,
|
||||
MessageCircle,
|
||||
Pencil,
|
||||
Palette,
|
||||
Plus,
|
||||
Search,
|
||||
Settings,
|
||||
@@ -157,6 +158,7 @@ export interface RpgEntryHomeViewProps {
|
||||
onOpenCreateWorld: () => void;
|
||||
onOpenCreateTypePicker: () => void;
|
||||
onOpenGalleryDetail: (entry: PlatformPublicGalleryCard) => void;
|
||||
onOpenBabyLoveDrawing?: () => void;
|
||||
onOpenRecommendGalleryDetail?: (entry: PlatformPublicGalleryCard) => void;
|
||||
recommendRuntimeContent?: ReactNode;
|
||||
activeRecommendEntryKey?: string | null;
|
||||
@@ -267,6 +269,11 @@ const EDUTAINMENT_DISCOVER_CHANNEL = {
|
||||
id: 'edutainment',
|
||||
label: EDUTAINMENT_WORK_TAG,
|
||||
} as const;
|
||||
const BABY_LOVE_DRAWING_DEFAULT_CARD = {
|
||||
title: '宝贝爱画',
|
||||
subtitle: '空白画板',
|
||||
summary: '挥动小手画一张画。',
|
||||
};
|
||||
|
||||
const PLATFORM_RANKING_TABS: Array<{
|
||||
id: PlatformRankingTab;
|
||||
@@ -3367,6 +3374,7 @@ export function RpgEntryHomeView({
|
||||
onResumeSave,
|
||||
onOpenCreateTypePicker,
|
||||
onOpenGalleryDetail,
|
||||
onOpenBabyLoveDrawing,
|
||||
onOpenRecommendGalleryDetail,
|
||||
recommendRuntimeContent,
|
||||
activeRecommendEntryKey = null,
|
||||
@@ -4950,7 +4958,7 @@ export function RpgEntryHomeView({
|
||||
<section className="platform-mobile-home-feed">
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取公开作品..." />
|
||||
) : edutainmentFeedEntries.length > 0 ? (
|
||||
) : edutainmentFeedEntries.length > 0 || onOpenBabyLoveDrawing ? (
|
||||
<div className="grid min-w-0 gap-3">
|
||||
{edutainmentFeedEntries.map((entry) => {
|
||||
const cardKey = buildPublicGalleryCardKey(entry);
|
||||
@@ -4966,6 +4974,24 @@ export function RpgEntryHomeView({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{onOpenBabyLoveDrawing ? (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-edutainment-level-card"
|
||||
onClick={onOpenBabyLoveDrawing}
|
||||
>
|
||||
<span className="platform-edutainment-level-card__icon">
|
||||
<Palette className="h-7 w-7" />
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__body">
|
||||
<strong>{BABY_LOVE_DRAWING_DEFAULT_CARD.title}</strong>
|
||||
<span>{BABY_LOVE_DRAWING_DEFAULT_CARD.subtitle}</span>
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__summary">
|
||||
{BABY_LOVE_DRAWING_DEFAULT_CARD.summary}
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyShelf text="暂时还没有可展示的作品。" />
|
||||
@@ -5082,7 +5108,7 @@ export function RpgEntryHomeView({
|
||||
<SectionHeader title={EDUTAINMENT_WORK_TAG} detail="EDUTAINMENT" />
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取公开作品..." />
|
||||
) : edutainmentFeedEntries.length > 0 ? (
|
||||
) : edutainmentFeedEntries.length > 0 || onOpenBabyLoveDrawing ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{edutainmentFeedEntries.map((entry) => (
|
||||
<WorldCard
|
||||
@@ -5093,6 +5119,24 @@ export function RpgEntryHomeView({
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
{onOpenBabyLoveDrawing ? (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-edutainment-level-card"
|
||||
onClick={onOpenBabyLoveDrawing}
|
||||
>
|
||||
<span className="platform-edutainment-level-card__icon">
|
||||
<Palette className="h-7 w-7" />
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__body">
|
||||
<strong>{BABY_LOVE_DRAWING_DEFAULT_CARD.title}</strong>
|
||||
<span>{BABY_LOVE_DRAWING_DEFAULT_CARD.subtitle}</span>
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__summary">
|
||||
{BABY_LOVE_DRAWING_DEFAULT_CARD.summary}
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyShelf text="暂时还没有可展示的作品。" />
|
||||
|
||||
@@ -41,10 +41,9 @@ test('platform work display text limits names and tags by character count', () =
|
||||
expect(formatPlatformWorkDisplayName('热门高分拼图超长标题')).toBe(
|
||||
'热门高分拼图超长',
|
||||
);
|
||||
expect(formatPlatformWorkDisplayTags(['超长机关标签', '星桥', '超长机关标签'])).toEqual([
|
||||
'超长机关',
|
||||
'星桥',
|
||||
]);
|
||||
expect(
|
||||
formatPlatformWorkDisplayTags(['超长机关标签', '星桥', '超长机关标签']),
|
||||
).toEqual(['超长机关', '星桥']);
|
||||
});
|
||||
|
||||
test('buildPuzzleWorkCoverSlides prefers each level formal image', () => {
|
||||
@@ -195,6 +194,7 @@ test('maps baby object match draft to edutainment public card', () => {
|
||||
prompt: '香蕉',
|
||||
},
|
||||
],
|
||||
visualPackage: null,
|
||||
themeTags: ['寓教于乐', '宝贝识物'],
|
||||
publicationStatus: 'published',
|
||||
createdAt: '2026-05-11T10:00:00.000Z',
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/
|
||||
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
||||
import { BABY_OBJECT_MATCH_EDUTAINMENT_TAG } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
||||
import type {
|
||||
Match3DGeneratedBackgroundAsset,
|
||||
Match3DGeneratedItemAsset,
|
||||
Match3DWorkSummary,
|
||||
} from '../../../packages/shared/src/contracts/match3dWorks';
|
||||
@@ -117,6 +118,7 @@ export type PlatformMatch3DGalleryCard = {
|
||||
backgroundPrompt?: string | null;
|
||||
backgroundImageSrc?: string | null;
|
||||
backgroundImageObjectKey?: string | null;
|
||||
generatedBackgroundAsset?: Match3DGeneratedBackgroundAsset | null;
|
||||
generatedItemAssets?: Match3DGeneratedItemAsset[];
|
||||
};
|
||||
|
||||
@@ -298,6 +300,7 @@ export function mapMatch3DWorkToPlatformGalleryCard(
|
||||
backgroundPrompt: work.backgroundPrompt ?? null,
|
||||
backgroundImageSrc: work.backgroundImageSrc ?? null,
|
||||
backgroundImageObjectKey: work.backgroundImageObjectKey ?? null,
|
||||
generatedBackgroundAsset: work.generatedBackgroundAsset ?? null,
|
||||
generatedItemAssets: work.generatedItemAssets ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user