refactor: 收口玩过作品打开意图

This commit is contained in:
2026-06-04 02:42:43 +08:00
parent 4e8cac3856
commit 80dab35646
7 changed files with 517 additions and 134 deletions

View File

@@ -512,6 +512,7 @@ import {
resolveMatch3DRuntimeGeneratedBackgroundAsset,
resolveMatch3DRuntimeGeneratedItemAssets,
} from './platformMatch3DRuntimeProfile';
import { resolvePlatformPlayedWorkOpenIntent } from './platformPlayedWorkOpenModel';
import {
type PlatformPublicCodeSearchStep,
resolvePlatformPublicCodeSearchPlan,
@@ -13815,145 +13816,59 @@ export function PlatformEntryFlowShellImpl({
const openPlayedWork = useCallback(
(work: ProfilePlayedWorkSummary) => {
const worldType = (work.worldType ?? '').toLowerCase();
const intent = resolvePlatformPlayedWorkOpenIntent(work);
setIsProfilePlayStatsOpen(false);
if (worldType === 'puzzle' || work.worldKey.startsWith('puzzle:')) {
const profileId =
work.profileId ?? work.worldKey.replace(/^puzzle:/u, '');
if (profileId) {
void openPuzzlePublicWorkDetail(profileId, { tab: 'profile' });
}
return;
}
if (
worldType === 'match3d' ||
worldType === 'match_3d' ||
work.worldKey.startsWith('match3d:')
) {
const profileId =
work.profileId ?? work.worldKey.replace(/^match3d:/u, '');
if (profileId) {
void openMatch3DPublicWorkDetail(profileId);
}
return;
}
if (
worldType === 'square-hole' ||
worldType === 'square_hole' ||
work.worldKey.startsWith('square-hole:')
) {
const profileId =
work.profileId ?? work.worldKey.replace(/^square-hole:/u, '');
if (profileId) {
void openSquareHolePublicWorkDetail(profileId);
}
return;
}
if (
worldType === 'jump-hop' ||
worldType === 'jump_hop' ||
work.worldKey.startsWith('jump-hop:')
) {
const profileId =
work.profileId ?? work.worldKey.replace(/^jump-hop:/u, '');
if (profileId) {
void openJumpHopPublicWorkDetail(profileId);
}
return;
}
if (
worldType === 'wooden-fish' ||
worldType === 'wooden_fish' ||
work.worldKey.startsWith('wooden-fish:')
) {
const profileId =
work.profileId ?? work.worldKey.replace(/^wooden-fish:/u, '');
if (profileId) {
void openWoodenFishPublicWorkDetail(profileId);
}
return;
}
if (
worldType === 'big_fish' ||
worldType === 'big-fish' ||
work.worldKey.startsWith('big-fish:')
) {
const sessionId =
work.profileId ?? work.worldKey.replace(/^big-fish:/u, '');
if (!sessionId) {
switch (intent.type) {
case 'noop':
return;
}
void refreshBigFishGallery()
.then((entries) => {
const matchedEntry = entries.find(
(entry) => entry.sourceSessionId === sessionId,
);
if (matchedEntry) {
openPublicWorkDetail(
mapBigFishWorkToPublicWorkDetail(matchedEntry),
);
return;
}
openPublicWorkDetail(
mapBigFishWorkToPublicWorkDetail({
workId: `big-fish:${sessionId}`,
sourceSessionId: sessionId,
ownerUserId: work.ownerUserId ?? '',
authorDisplayName: work.worldSubtitle || '玩家',
title: work.worldTitle,
subtitle: work.worldSubtitle,
summary: work.worldSubtitle,
coverImageSrc: null,
status: 'published',
updatedAt: work.lastPlayedAt,
publishReady: true,
levelCount: 0,
levelMainImageReadyCount: 0,
levelMotionReadyCount: 0,
backgroundReady: false,
}),
);
})
.catch((error) => {
setBigFishError(
resolveBigFishErrorMessage(error, '进入大鱼吃小鱼作品失败。'),
);
case 'open-puzzle':
void openPuzzlePublicWorkDetail(intent.profileId, {
tab: intent.tab,
});
return;
return;
case 'open-match3d':
void openMatch3DPublicWorkDetail(intent.profileId);
return;
case 'open-square-hole':
void openSquareHolePublicWorkDetail(intent.profileId);
return;
case 'open-jump-hop':
void openJumpHopPublicWorkDetail(intent.profileId);
return;
case 'open-wooden-fish':
void openWoodenFishPublicWorkDetail(intent.profileId);
return;
case 'open-big-fish':
void refreshBigFishGallery()
.then((entries) => {
const matchedEntry = entries.find(
(entry) => entry.sourceSessionId === intent.sessionId,
);
if (matchedEntry) {
openPublicWorkDetail(
mapBigFishWorkToPublicWorkDetail(matchedEntry),
);
return;
}
openPublicWorkDetail(
mapBigFishWorkToPublicWorkDetail(intent.fallbackWork),
);
})
.catch((error) => {
setBigFishError(
resolveBigFishErrorMessage(error, '进入大鱼吃小鱼作品失败。'),
);
});
return;
case 'open-rpg':
void openRpgPublicWorkDetail(intent.detail);
return;
default: {
const exhaustive: never = intent;
return exhaustive;
}
}
const profileId = work.profileId ?? work.worldKey;
const ownerUserId = work.ownerUserId;
if (!ownerUserId || !profileId) {
return;
}
void openRpgPublicWorkDetail({
ownerUserId,
profileId,
publicWorkCode: null,
authorPublicUserCode: null,
visibility: 'published',
publishedAt: work.firstPlayedAt,
updatedAt: work.lastPlayedAt,
authorDisplayName: work.worldSubtitle,
worldName: work.worldTitle,
subtitle: work.worldSubtitle,
summaryText: '',
coverImageSrc: null,
themeMode: 'martial',
playableNpcCount: 0,
landmarkCount: 0,
playCount: 0,
remixCount: 0,
likeCount: 0,
});
},
[
openMatch3DPublicWorkDetail,

View File

@@ -0,0 +1,205 @@
import { describe, expect, test } from 'vitest';
import type { ProfilePlayedWorkSummary } from '../../../packages/shared/src/contracts/runtime';
import { resolvePlatformPlayedWorkOpenIntent } from './platformPlayedWorkOpenModel';
function buildPlayedWork(
overrides: Partial<ProfilePlayedWorkSummary> = {},
): ProfilePlayedWorkSummary {
return {
worldKey: 'custom:world-1',
ownerUserId: 'user-1',
profileId: 'world-1',
worldType: 'CUSTOM',
worldTitle: '潮雾列岛',
worldSubtitle: '旧灯塔与失控航路',
firstPlayedAt: '2026-04-18T12:00:00.000Z',
lastPlayedAt: '2026-04-19T12:00:00.000Z',
lastObservedPlayTimeMs: 12_000,
...overrides,
};
}
describe('platformPlayedWorkOpenModel', () => {
test('opens puzzle played works with profile tab context', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldType: 'PUZZLE',
profileId: 'puzzle-profile-1',
}),
),
).toEqual({
type: 'open-puzzle',
profileId: 'puzzle-profile-1',
tab: 'profile',
});
});
test('falls back to worldKey prefixes when profile id is absent', () => {
const cases = [
['puzzle:profile-1', 'open-puzzle', 'profile-1'],
['match3d:profile-2', 'open-match3d', 'profile-2'],
['square-hole:profile-3', 'open-square-hole', 'profile-3'],
['jump-hop:profile-4', 'open-jump-hop', 'profile-4'],
['wooden-fish:profile-5', 'open-wooden-fish', 'profile-5'],
] as const;
for (const [worldKey, type, profileId] of cases) {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldKey,
profileId: null,
worldType: null,
}),
),
).toMatchObject({ type, profileId });
}
});
test('keeps explicit profile id ahead of worldKey fallback', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldKey: 'jump-hop:key-profile',
profileId: 'explicit-profile',
worldType: null,
}),
),
).toMatchObject({
type: 'open-jump-hop',
profileId: 'explicit-profile',
});
});
test('supports played work type aliases for mini-games', () => {
const cases = [
['match_3d', 'open-match3d'],
['square_hole', 'open-square-hole'],
['jump_hop', 'open-jump-hop'],
['wooden_fish', 'open-wooden-fish'],
] as const;
for (const [worldType, type] of cases) {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldType,
profileId: `${worldType}-profile`,
}),
),
).toMatchObject({
type,
profileId: `${worldType}-profile`,
});
}
});
test('returns noop when a mini-game target is empty', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldKey: 'puzzle:key-profile',
profileId: '',
worldType: 'puzzle',
}),
),
).toEqual({
type: 'noop',
reason: 'missing-target',
});
});
test('builds big fish intent and fallback work for gallery misses', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldKey: 'big-fish:big-fish-session-1',
ownerUserId: null,
profileId: null,
worldType: 'big_fish',
worldTitle: '机械深海',
worldSubtitle: '',
}),
),
).toEqual({
type: 'open-big-fish',
sessionId: 'big-fish-session-1',
fallbackWork: {
workId: 'big-fish:big-fish-session-1',
sourceSessionId: 'big-fish-session-1',
ownerUserId: '',
authorDisplayName: '玩家',
title: '机械深海',
subtitle: '',
summary: '',
coverImageSrc: null,
status: 'published',
updatedAt: '2026-04-19T12:00:00.000Z',
publishReady: true,
levelCount: 0,
levelMainImageReadyCount: 0,
levelMotionReadyCount: 0,
backgroundReady: false,
},
});
});
test('opens unknown played work types as RPG detail when identity is complete', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldType: 'CUSTOM',
profileId: null,
}),
),
).toEqual({
type: 'open-rpg',
detail: {
ownerUserId: 'user-1',
profileId: 'custom:world-1',
publicWorkCode: null,
authorPublicUserCode: null,
visibility: 'published',
publishedAt: '2026-04-18T12:00:00.000Z',
updatedAt: '2026-04-19T12:00:00.000Z',
authorDisplayName: '旧灯塔与失控航路',
worldName: '潮雾列岛',
subtitle: '旧灯塔与失控航路',
summaryText: '',
coverImageSrc: null,
themeMode: 'martial',
playableNpcCount: 0,
landmarkCount: 0,
playCount: 0,
remixCount: 0,
likeCount: 0,
},
});
});
test('returns noop for RPG fallback when owner or profile is missing', () => {
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
ownerUserId: null,
}),
),
).toEqual({
type: 'noop',
reason: 'missing-target',
});
expect(
resolvePlatformPlayedWorkOpenIntent(
buildPlayedWork({
worldKey: '',
profileId: null,
}),
),
).toEqual({
type: 'noop',
reason: 'missing-target',
});
});
});

View File

@@ -0,0 +1,212 @@
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type {
CustomWorldGalleryCard,
ProfilePlayedWorkSummary,
} from '../../../packages/shared/src/contracts/runtime';
export type PlatformPlayedWorkOpenIntent =
| {
type: 'noop';
reason: 'missing-target';
}
| {
type: 'open-puzzle';
profileId: string;
tab: 'profile';
}
| {
type: 'open-match3d';
profileId: string;
}
| {
type: 'open-square-hole';
profileId: string;
}
| {
type: 'open-jump-hop';
profileId: string;
}
| {
type: 'open-wooden-fish';
profileId: string;
}
| {
type: 'open-big-fish';
sessionId: string;
fallbackWork: BigFishWorkSummary;
}
| {
type: 'open-rpg';
detail: CustomWorldGalleryCard;
};
function normalizePlayedWorkWorldType(worldType: string | null) {
return (worldType ?? '').toLowerCase();
}
function resolvePlayedWorkTargetId(
work: ProfilePlayedWorkSummary,
worldKeyPrefix: string,
) {
const prefixedWorldKey = `${worldKeyPrefix}:`;
return (
work.profileId ??
(work.worldKey.startsWith(prefixedWorldKey)
? work.worldKey.slice(prefixedWorldKey.length)
: work.worldKey)
);
}
function resolvePlayedWorkProfileIntent<TIntent extends PlatformPlayedWorkOpenIntent>(
profileId: string,
intent: (profileId: string) => TIntent,
) {
return profileId ? intent(profileId) : buildMissingPlayedWorkTargetIntent();
}
function buildMissingPlayedWorkTargetIntent(): PlatformPlayedWorkOpenIntent {
return {
type: 'noop',
reason: 'missing-target',
};
}
function buildPlayedBigFishFallbackWork(
work: ProfilePlayedWorkSummary,
sessionId: string,
): BigFishWorkSummary {
return {
workId: `big-fish:${sessionId}`,
sourceSessionId: sessionId,
ownerUserId: work.ownerUserId ?? '',
authorDisplayName: work.worldSubtitle || '玩家',
title: work.worldTitle,
subtitle: work.worldSubtitle,
summary: work.worldSubtitle,
coverImageSrc: null,
status: 'published',
updatedAt: work.lastPlayedAt,
publishReady: true,
levelCount: 0,
levelMainImageReadyCount: 0,
levelMotionReadyCount: 0,
backgroundReady: false,
};
}
function buildPlayedRpgDetail(
work: ProfilePlayedWorkSummary,
profileId: string,
ownerUserId: string,
): CustomWorldGalleryCard {
return {
ownerUserId,
profileId,
publicWorkCode: null,
authorPublicUserCode: null,
visibility: 'published',
publishedAt: work.firstPlayedAt,
updatedAt: work.lastPlayedAt,
authorDisplayName: work.worldSubtitle,
worldName: work.worldTitle,
subtitle: work.worldSubtitle,
summaryText: '',
coverImageSrc: null,
themeMode: 'martial',
playableNpcCount: 0,
landmarkCount: 0,
playCount: 0,
remixCount: 0,
likeCount: 0,
};
}
/** 收口个人“玩过作品”点击后的玩法打开意图,壳层只执行副作用。 */
export function resolvePlatformPlayedWorkOpenIntent(
work: ProfilePlayedWorkSummary,
): PlatformPlayedWorkOpenIntent {
const worldType = normalizePlayedWorkWorldType(work.worldType);
if (worldType === 'puzzle' || work.worldKey.startsWith('puzzle:')) {
const profileId = resolvePlayedWorkTargetId(work, 'puzzle');
return resolvePlayedWorkProfileIntent(profileId, (resolvedProfileId) => ({
type: 'open-puzzle',
profileId: resolvedProfileId,
tab: 'profile',
}));
}
if (
worldType === 'match3d' ||
worldType === 'match_3d' ||
work.worldKey.startsWith('match3d:')
) {
const profileId = resolvePlayedWorkTargetId(work, 'match3d');
return resolvePlayedWorkProfileIntent(profileId, (resolvedProfileId) => ({
type: 'open-match3d',
profileId: resolvedProfileId,
}));
}
if (
worldType === 'square-hole' ||
worldType === 'square_hole' ||
work.worldKey.startsWith('square-hole:')
) {
const profileId = resolvePlayedWorkTargetId(work, 'square-hole');
return resolvePlayedWorkProfileIntent(profileId, (resolvedProfileId) => ({
type: 'open-square-hole',
profileId: resolvedProfileId,
}));
}
if (
worldType === 'jump-hop' ||
worldType === 'jump_hop' ||
work.worldKey.startsWith('jump-hop:')
) {
const profileId = resolvePlayedWorkTargetId(work, 'jump-hop');
return resolvePlayedWorkProfileIntent(profileId, (resolvedProfileId) => ({
type: 'open-jump-hop',
profileId: resolvedProfileId,
}));
}
if (
worldType === 'wooden-fish' ||
worldType === 'wooden_fish' ||
work.worldKey.startsWith('wooden-fish:')
) {
const profileId = resolvePlayedWorkTargetId(work, 'wooden-fish');
return resolvePlayedWorkProfileIntent(profileId, (resolvedProfileId) => ({
type: 'open-wooden-fish',
profileId: resolvedProfileId,
}));
}
if (
worldType === 'big_fish' ||
worldType === 'big-fish' ||
work.worldKey.startsWith('big-fish:')
) {
const sessionId = resolvePlayedWorkTargetId(work, 'big-fish');
return sessionId
? {
type: 'open-big-fish',
sessionId,
fallbackWork: buildPlayedBigFishFallbackWork(work, sessionId),
}
: buildMissingPlayedWorkTargetIntent();
}
const profileId = work.profileId ?? work.worldKey;
const ownerUserId = work.ownerUserId;
if (!ownerUserId || !profileId) {
return buildMissingPlayedWorkTargetIntent();
}
return {
type: 'open-rpg',
detail: buildPlayedRpgDetail(work, profileId, ownerUserId),
};
}