refactor: 收口公开作者展示模型

This commit is contained in:
2026-06-03 19:05:00 +08:00
parent a0efb14e84
commit 0b71b79e7a
6 changed files with 133 additions and 52 deletions

View File

@@ -194,6 +194,7 @@ import {
describePlatformPublicWorkKind,
describePlatformThemeLabel,
formatPlatformCompactCount,
formatPlatformPublicAuthorAvatarLabel,
formatPlatformWorkDisplayName,
formatPlatformWorkDisplayTag,
formatPlatformWorldTime,
@@ -203,6 +204,8 @@ import {
isPuzzleGalleryEntry,
isVisualNovelGalleryEntry,
type PlatformPublicGalleryCard,
type PlatformPublicWorkAuthorLookup,
resolvePlatformPublicWorkAuthorLookup,
resolvePlatformPublicWorkCode,
resolvePlatformWorkAuthorDisplayName,
resolvePlatformWorldCoverImage,
@@ -597,7 +600,7 @@ function WorldCard({
entry,
authorSummary,
);
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const authorAvatarLabel = formatPlatformPublicAuthorAvatarLabel(authorName);
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
const cardLabel = `${entry.worldName}${typeLabel}${formatPlatformCompactCount(playCount)}游玩,${formatPlatformCompactCount(remixCount)}改造,${formatPlatformCompactCount(likeCount)}点赞`;
const coverStats = [
@@ -966,7 +969,7 @@ function RecommendRuntimeMeta({
entry,
authorSummary,
);
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const authorAvatarLabel = formatPlatformPublicAuthorAvatarLabel(authorName);
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
const displayName = formatPlatformWorkDisplayName(entry.worldName);
const stopActionPointer = (event: PointerEvent<HTMLButtonElement>) => {
@@ -1637,36 +1640,20 @@ function PlatformWorkSearchResults({
);
}
function buildPublicWorkAuthorLookupKey(entry: PlatformPublicGalleryCard) {
if ('authorPublicUserCode' in entry) {
const authorPublicUserCode = entry.authorPublicUserCode?.trim();
if (authorPublicUserCode) {
return `code:${authorPublicUserCode}`;
}
}
const ownerUserId = entry.ownerUserId.trim();
return ownerUserId ? `id:${ownerUserId}` : null;
}
async function getPublicWorkAuthorSummary(
authorLookupKey: string,
authorLookup: PlatformPublicWorkAuthorLookup,
): Promise<PublicUserSummary | null> {
if (authorLookupKey.startsWith('code:')) {
return getPublicAuthUserByCode(authorLookupKey.slice('code:'.length));
if (authorLookup.source === 'publicUserCode') {
return getPublicAuthUserByCode(authorLookup.value);
}
if (authorLookupKey.startsWith('id:')) {
return getPublicAuthUserById(authorLookupKey.slice('id:'.length));
if (authorLookup.source === 'ownerUserId') {
return getPublicAuthUserById(authorLookup.value);
}
return null;
}
function getPublicAuthorAvatarLabel(authorDisplayName: string) {
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
}
function formatSnapshotTime(value: string | null | undefined) {
if (!value) {
return '刚刚保存';
@@ -3619,25 +3606,25 @@ export function RpgEntryHomeView({
);
const getPublicEntryAuthorAvatarUrl = useCallback(
(entry: PlatformPublicGalleryCard) => {
const authorLookupKey = buildPublicWorkAuthorLookupKey(entry);
if (!authorLookupKey) {
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
if (!authorLookup) {
return null;
}
return (
publicAuthorSummariesByKey[authorLookupKey]?.avatarUrl?.trim() || null
publicAuthorSummariesByKey[authorLookup.key]?.avatarUrl?.trim() || null
);
},
[publicAuthorSummariesByKey],
);
const getPublicEntryAuthorSummary = useCallback(
(entry: PlatformPublicGalleryCard) => {
const authorLookupKey = buildPublicWorkAuthorLookupKey(entry);
if (!authorLookupKey) {
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
if (!authorLookup) {
return null;
}
return publicAuthorSummariesByKey[authorLookupKey] ?? null;
return publicAuthorSummariesByKey[authorLookup.key] ?? null;
},
[publicAuthorSummariesByKey],
);
@@ -3775,37 +3762,38 @@ export function RpgEntryHomeView({
}, [categoryGroups, selectedCategoryTag]);
useEffect(() => {
const missingAuthorKeys = [
...new Set(
publicEntries
.map(buildPublicWorkAuthorLookupKey)
.filter((key): key is string => Boolean(key)),
),
].filter(
(key) =>
!(key in publicAuthorSummariesByKey) &&
!pendingPublicAuthorKeysRef.current.has(key),
const authorLookupsByKey = new Map<string, PlatformPublicWorkAuthorLookup>();
publicEntries.forEach((entry) => {
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
if (authorLookup) {
authorLookupsByKey.set(authorLookup.key, authorLookup);
}
});
const missingAuthorLookups = Array.from(authorLookupsByKey.values()).filter(
(authorLookup) =>
!(authorLookup.key in publicAuthorSummariesByKey) &&
!pendingPublicAuthorKeysRef.current.has(authorLookup.key),
);
if (missingAuthorKeys.length === 0) {
if (missingAuthorLookups.length === 0) {
return undefined;
}
let cancelled = false;
missingAuthorKeys.forEach((key) => {
pendingPublicAuthorKeysRef.current.add(key);
missingAuthorLookups.forEach((authorLookup) => {
pendingPublicAuthorKeysRef.current.add(authorLookup.key);
});
// 中文注释:头像来自公开用户摘要,失败时缓存空值,避免首页滚动时反复打公开用户接口。
void Promise.all(
missingAuthorKeys.map(async (authorLookupKey) => {
missingAuthorLookups.map(async (authorLookup) => {
try {
const author = await getPublicWorkAuthorSummary(authorLookupKey);
return [authorLookupKey, author] as const;
const author = await getPublicWorkAuthorSummary(authorLookup);
return [authorLookup.key, author] as const;
} catch {
return [authorLookupKey, null] as const;
return [authorLookup.key, null] as const;
} finally {
pendingPublicAuthorKeysRef.current.delete(authorLookupKey);
pendingPublicAuthorKeysRef.current.delete(authorLookup.key);
}
}),
).then((results) => {

View File

@@ -7,6 +7,7 @@ import {
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
formatPlatformCompactCount,
formatPlatformPublicAuthorAvatarLabel,
formatPlatformWorkDisplayName,
formatPlatformWorkDisplayTags,
formatPlatformWorldTime,
@@ -18,9 +19,11 @@ import {
mapBarkBattleWorkToPlatformGalleryCard,
mapVisualNovelWorkToPlatformGalleryCard,
mapWoodenFishWorkToPlatformGalleryCard,
type PlatformBarkBattleGalleryCard,
type PlatformBigFishGalleryCard,
type PlatformEdutainmentGalleryCard,
type PlatformPuzzleGalleryCard,
resolvePlatformPublicWorkAuthorLookup,
resolvePlatformPublicWorkCode,
resolvePlatformWorkAuthorDisplayName,
resolvePlatformWorldFallbackCoverImage,
@@ -312,6 +315,57 @@ test('public work author display hides phone masks and public user codes on card
expect(resolvePlatformWorkAuthorDisplayName(card, null)).toBe('玩家');
});
test('public work author lookup keeps public user code priority and avatar labels', () => {
const barkBattleCard: PlatformBarkBattleGalleryCard = {
sourceType: 'bark-battle',
workId: 'bark-battle-work-author',
profileId: 'bark-battle-profile-author',
sourceSessionId: null,
publicWorkCode: 'BB-AUTHOR',
ownerUserId: 'user-author-id',
authorPublicUserCode: ' SY-00012345 ',
authorDisplayName: '声浪玩家',
worldName: '声浪擂台',
subtitle: '汪汪声浪',
summaryText: '公开作品',
coverImageSrc: null,
coverRenderMode: 'image',
coverCharacterImageSrcs: [],
themeTags: ['声浪'],
themeMode: 'martial',
playableNpcCount: 0,
landmarkCount: 0,
visibility: 'published',
publishedAt: '2026-05-22T00:00:00.000Z',
updatedAt: '2026-05-22T00:00:00.000Z',
};
expect(resolvePlatformPublicWorkAuthorLookup(barkBattleCard)).toEqual({
key: 'code:SY-00012345',
source: 'publicUserCode',
value: 'SY-00012345',
});
expect(
resolvePlatformPublicWorkAuthorLookup({
...barkBattleCard,
authorPublicUserCode: ' ',
}),
).toEqual({
key: 'id:user-author-id',
source: 'ownerUserId',
value: 'user-author-id',
});
expect(
resolvePlatformPublicWorkAuthorLookup({
...barkBattleCard,
authorPublicUserCode: null,
ownerUserId: ' ',
}),
).toBeNull();
expect(formatPlatformPublicAuthorAvatarLabel(' 声浪玩家')).toBe('声');
expect(formatPlatformPublicAuthorAvatarLabel('')).toBe('玩');
});
test('keeps baby object match public card code and template label intact', () => {
const card: PlatformEdutainmentGalleryCard = {
sourceType: 'edutainment',

View File

@@ -300,6 +300,12 @@ export type PlatformPublicGalleryCard =
| PlatformBarkBattleGalleryCard
| PlatformEdutainmentGalleryCard;
export type PlatformPublicWorkAuthorLookup = {
key: string;
source: 'publicUserCode' | 'ownerUserId';
value: string;
};
export function isLibraryWorldEntry(
entry: PlatformWorldCardLike,
): entry is CustomWorldLibraryEntry<CustomWorldProfile> {
@@ -923,6 +929,36 @@ export function resolvePlatformWorkAuthorDisplayName(
return displayName || entryAuthorName || '玩家';
}
export function resolvePlatformPublicWorkAuthorLookup(
entry: PlatformPublicGalleryCard,
): PlatformPublicWorkAuthorLookup | null {
if ('authorPublicUserCode' in entry) {
const authorPublicUserCode = entry.authorPublicUserCode?.trim();
if (authorPublicUserCode) {
return {
key: `code:${authorPublicUserCode}`,
source: 'publicUserCode',
value: authorPublicUserCode,
};
}
}
const ownerUserId = entry.ownerUserId.trim();
return ownerUserId
? {
key: `id:${ownerUserId}`,
source: 'ownerUserId',
value: ownerUserId,
}
: null;
}
export function formatPlatformPublicAuthorAvatarLabel(
authorDisplayName: string,
) {
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
}
function normalizePlatformPublicAuthorName(value: string | null | undefined) {
const normalized = value?.trim() ?? '';
if (!normalized || normalized === 'null' || normalized === 'undefined') {