refactor: 收口公开作者展示模型
This commit is contained in:
@@ -1268,8 +1268,8 @@
|
|||||||
## 2026-06-03 Public Work Presentation 收口
|
## 2026-06-03 Public Work Presentation 收口
|
||||||
|
|
||||||
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
||||||
- 决策:在 `src/components/rpg-entry/rpgEntryWorldPresentation.ts` 追加单作品展示 Interface:`describePlatformPublicWorkKind` 与 `formatPlatformCompactCount`;页面删除本地实现。集合筛选、排序和指标选择继续留在 `rpgEntryPublicGalleryViewModel.ts`。
|
- 决策:在 `src/components/rpg-entry/rpgEntryWorldPresentation.ts` 追加单作品展示 Interface:`describePlatformPublicWorkKind`、`formatPlatformCompactCount`、`resolvePlatformPublicWorkAuthorLookup` 与 `formatPlatformPublicAuthorAvatarLabel`;页面删除本地玩法类型、紧凑计数、公开作者 lookup 和头像首字实现。集合筛选、排序和指标选择继续留在 `rpgEntryPublicGalleryViewModel.ts`。
|
||||||
- 影响范围:公开作品卡片 aria label、推荐点赞 / 改造文案、排行数值、分类主指标、搜索结果和桌面 hero 玩法 label。
|
- 影响范围:公开作品卡片 aria label、推荐点赞 / 改造文案、排行数值、分类主指标、搜索结果、桌面 hero 玩法 label、公开作者摘要缓存 key 与无头像首字兜底。
|
||||||
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|ranking|category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|ranking|category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】PublicWorkPresentation收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】PublicWorkPresentation收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
公开作品的玩法类型 label 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品的玩法类型 label、公开作者 lookup 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
推荐 feed 的公开作品去重、普通内容过滤、active 窗口与上一条 / 下一条回环选择也收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RecommendFeedViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RecommendFeedViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
推荐 feed 的公开作品去重、普通内容过滤、active 窗口与上一条 / 下一条回环选择也收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RecommendFeedViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RecommendFeedViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
`RpgEntryHomeView.tsx` 的作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用公开作品玩法类型 label 与紧凑计数格式。原先 `describePublicGalleryCardKind` 与 `formatCompactCount` 放在页面 **Implementation** 内,导致新增玩法或调整数字展示时需要穿过多段 JSX。
|
`RpgEntryHomeView.tsx` 的作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用公开作品玩法类型 label 与紧凑计数格式。原先 `describePublicGalleryCardKind` 与 `formatCompactCount` 放在页面 **Implementation** 内,导致新增玩法或调整数字展示时需要穿过多段 JSX。公开作者 lookup key 与头像首字也曾由页面手写,页面既要知道公开作品作者来源优先级,又要知道 `code:` / `id:` 前缀约定。
|
||||||
|
|
||||||
## 决策
|
## 决策
|
||||||
|
|
||||||
@@ -10,13 +10,16 @@
|
|||||||
|
|
||||||
- `describePlatformPublicWorkKind(entry)`:统一公开作品玩法类型 label,并继续复用 `formatPlatformWorkDisplayTag` 的 4 字截断口径。
|
- `describePlatformPublicWorkKind(entry)`:统一公开作品玩法类型 label,并继续复用 `formatPlatformWorkDisplayTag` 的 4 字截断口径。
|
||||||
- `formatPlatformCompactCount(value)`:统一游玩、改造、点赞、排行和分类指标的紧凑数字展示。
|
- `formatPlatformCompactCount(value)`:统一游玩、改造、点赞、排行和分类指标的紧凑数字展示。
|
||||||
|
- `resolvePlatformPublicWorkAuthorLookup(entry)`:统一公开作者查询 lookup,优先使用 `authorPublicUserCode`,否则回退 `ownerUserId`,并用结构化 `{ key, source, value }` 避免页面复写前缀规则。
|
||||||
|
- `formatPlatformPublicAuthorAvatarLabel(authorDisplayName)`:统一公开作者头像无图时的首字兜底。
|
||||||
|
|
||||||
`RpgEntryHomeView.tsx` 删除本地类型 label 与紧凑计数 **Implementation**,仅消费 `rpgEntryWorldPresentation.ts`。集合筛选、排序和指标选择仍留在 `rpgEntryPublicGalleryViewModel.ts`,避免单作品展示 **Module** 与集合 **Module** 混杂。
|
`RpgEntryHomeView.tsx` 删除本地类型 label、紧凑计数、公开作者 lookup 与头像首字 **Implementation**,仅消费 `rpgEntryWorldPresentation.ts`。认证请求、缓存和失败兜底仍留页面侧 Adapter;集合筛选、排序和指标选择仍留在 `rpgEntryPublicGalleryViewModel.ts`,避免单作品展示 **Module** 与集合 **Module** 混杂。
|
||||||
|
|
||||||
## 约定
|
## 约定
|
||||||
|
|
||||||
- 紧凑计数保留既有口径:`10000` 显示 `1.0万`,`100000000` 显示 `1.0亿`,一万以下不加千分位。
|
- 紧凑计数保留既有口径:`10000` 显示 `1.0万`,`100000000` 显示 `1.0亿`,一万以下不加千分位。
|
||||||
- 玩法类型 label 继续遵循 4 字展示限制,例如“大鱼吃小鱼”外显为“大鱼吃小”。
|
- 玩法类型 label 继续遵循 4 字展示限制,例如“大鱼吃小鱼”外显为“大鱼吃小”。
|
||||||
|
- 公开作者 lookup 的 `key` 只用于缓存索引;真正调用公开用户 Adapter 时以 `source` 和 `value` 分发,页面不得解析 `code:` / `id:` 前缀。
|
||||||
- 本次不迁移排行 metric label / value 配对;该规则属于集合排序 **Module** 的后续切片。
|
- 本次不迁移排行 metric label / value 配对;该规则属于集合排序 **Module** 的后续切片。
|
||||||
|
|
||||||
## 验证
|
## 验证
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ import {
|
|||||||
describePlatformPublicWorkKind,
|
describePlatformPublicWorkKind,
|
||||||
describePlatformThemeLabel,
|
describePlatformThemeLabel,
|
||||||
formatPlatformCompactCount,
|
formatPlatformCompactCount,
|
||||||
|
formatPlatformPublicAuthorAvatarLabel,
|
||||||
formatPlatformWorkDisplayName,
|
formatPlatformWorkDisplayName,
|
||||||
formatPlatformWorkDisplayTag,
|
formatPlatformWorkDisplayTag,
|
||||||
formatPlatformWorldTime,
|
formatPlatformWorldTime,
|
||||||
@@ -203,6 +204,8 @@ import {
|
|||||||
isPuzzleGalleryEntry,
|
isPuzzleGalleryEntry,
|
||||||
isVisualNovelGalleryEntry,
|
isVisualNovelGalleryEntry,
|
||||||
type PlatformPublicGalleryCard,
|
type PlatformPublicGalleryCard,
|
||||||
|
type PlatformPublicWorkAuthorLookup,
|
||||||
|
resolvePlatformPublicWorkAuthorLookup,
|
||||||
resolvePlatformPublicWorkCode,
|
resolvePlatformPublicWorkCode,
|
||||||
resolvePlatformWorkAuthorDisplayName,
|
resolvePlatformWorkAuthorDisplayName,
|
||||||
resolvePlatformWorldCoverImage,
|
resolvePlatformWorldCoverImage,
|
||||||
@@ -597,7 +600,7 @@ function WorldCard({
|
|||||||
entry,
|
entry,
|
||||||
authorSummary,
|
authorSummary,
|
||||||
);
|
);
|
||||||
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
|
const authorAvatarLabel = formatPlatformPublicAuthorAvatarLabel(authorName);
|
||||||
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
||||||
const cardLabel = `${entry.worldName},${typeLabel},${formatPlatformCompactCount(playCount)}游玩,${formatPlatformCompactCount(remixCount)}改造,${formatPlatformCompactCount(likeCount)}点赞`;
|
const cardLabel = `${entry.worldName},${typeLabel},${formatPlatformCompactCount(playCount)}游玩,${formatPlatformCompactCount(remixCount)}改造,${formatPlatformCompactCount(likeCount)}点赞`;
|
||||||
const coverStats = [
|
const coverStats = [
|
||||||
@@ -966,7 +969,7 @@ function RecommendRuntimeMeta({
|
|||||||
entry,
|
entry,
|
||||||
authorSummary,
|
authorSummary,
|
||||||
);
|
);
|
||||||
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
|
const authorAvatarLabel = formatPlatformPublicAuthorAvatarLabel(authorName);
|
||||||
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
||||||
const displayName = formatPlatformWorkDisplayName(entry.worldName);
|
const displayName = formatPlatformWorkDisplayName(entry.worldName);
|
||||||
const stopActionPointer = (event: PointerEvent<HTMLButtonElement>) => {
|
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(
|
async function getPublicWorkAuthorSummary(
|
||||||
authorLookupKey: string,
|
authorLookup: PlatformPublicWorkAuthorLookup,
|
||||||
): Promise<PublicUserSummary | null> {
|
): Promise<PublicUserSummary | null> {
|
||||||
if (authorLookupKey.startsWith('code:')) {
|
if (authorLookup.source === 'publicUserCode') {
|
||||||
return getPublicAuthUserByCode(authorLookupKey.slice('code:'.length));
|
return getPublicAuthUserByCode(authorLookup.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authorLookupKey.startsWith('id:')) {
|
if (authorLookup.source === 'ownerUserId') {
|
||||||
return getPublicAuthUserById(authorLookupKey.slice('id:'.length));
|
return getPublicAuthUserById(authorLookup.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPublicAuthorAvatarLabel(authorDisplayName: string) {
|
|
||||||
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatSnapshotTime(value: string | null | undefined) {
|
function formatSnapshotTime(value: string | null | undefined) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return '刚刚保存';
|
return '刚刚保存';
|
||||||
@@ -3619,25 +3606,25 @@ export function RpgEntryHomeView({
|
|||||||
);
|
);
|
||||||
const getPublicEntryAuthorAvatarUrl = useCallback(
|
const getPublicEntryAuthorAvatarUrl = useCallback(
|
||||||
(entry: PlatformPublicGalleryCard) => {
|
(entry: PlatformPublicGalleryCard) => {
|
||||||
const authorLookupKey = buildPublicWorkAuthorLookupKey(entry);
|
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
|
||||||
if (!authorLookupKey) {
|
if (!authorLookup) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
publicAuthorSummariesByKey[authorLookupKey]?.avatarUrl?.trim() || null
|
publicAuthorSummariesByKey[authorLookup.key]?.avatarUrl?.trim() || null
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[publicAuthorSummariesByKey],
|
[publicAuthorSummariesByKey],
|
||||||
);
|
);
|
||||||
const getPublicEntryAuthorSummary = useCallback(
|
const getPublicEntryAuthorSummary = useCallback(
|
||||||
(entry: PlatformPublicGalleryCard) => {
|
(entry: PlatformPublicGalleryCard) => {
|
||||||
const authorLookupKey = buildPublicWorkAuthorLookupKey(entry);
|
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
|
||||||
if (!authorLookupKey) {
|
if (!authorLookup) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return publicAuthorSummariesByKey[authorLookupKey] ?? null;
|
return publicAuthorSummariesByKey[authorLookup.key] ?? null;
|
||||||
},
|
},
|
||||||
[publicAuthorSummariesByKey],
|
[publicAuthorSummariesByKey],
|
||||||
);
|
);
|
||||||
@@ -3775,37 +3762,38 @@ export function RpgEntryHomeView({
|
|||||||
}, [categoryGroups, selectedCategoryTag]);
|
}, [categoryGroups, selectedCategoryTag]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const missingAuthorKeys = [
|
const authorLookupsByKey = new Map<string, PlatformPublicWorkAuthorLookup>();
|
||||||
...new Set(
|
publicEntries.forEach((entry) => {
|
||||||
publicEntries
|
const authorLookup = resolvePlatformPublicWorkAuthorLookup(entry);
|
||||||
.map(buildPublicWorkAuthorLookupKey)
|
if (authorLookup) {
|
||||||
.filter((key): key is string => Boolean(key)),
|
authorLookupsByKey.set(authorLookup.key, authorLookup);
|
||||||
),
|
}
|
||||||
].filter(
|
});
|
||||||
(key) =>
|
const missingAuthorLookups = Array.from(authorLookupsByKey.values()).filter(
|
||||||
!(key in publicAuthorSummariesByKey) &&
|
(authorLookup) =>
|
||||||
!pendingPublicAuthorKeysRef.current.has(key),
|
!(authorLookup.key in publicAuthorSummariesByKey) &&
|
||||||
|
!pendingPublicAuthorKeysRef.current.has(authorLookup.key),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (missingAuthorKeys.length === 0) {
|
if (missingAuthorLookups.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
missingAuthorKeys.forEach((key) => {
|
missingAuthorLookups.forEach((authorLookup) => {
|
||||||
pendingPublicAuthorKeysRef.current.add(key);
|
pendingPublicAuthorKeysRef.current.add(authorLookup.key);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 中文注释:头像来自公开用户摘要,失败时缓存空值,避免首页滚动时反复打公开用户接口。
|
// 中文注释:头像来自公开用户摘要,失败时缓存空值,避免首页滚动时反复打公开用户接口。
|
||||||
void Promise.all(
|
void Promise.all(
|
||||||
missingAuthorKeys.map(async (authorLookupKey) => {
|
missingAuthorLookups.map(async (authorLookup) => {
|
||||||
try {
|
try {
|
||||||
const author = await getPublicWorkAuthorSummary(authorLookupKey);
|
const author = await getPublicWorkAuthorSummary(authorLookup);
|
||||||
return [authorLookupKey, author] as const;
|
return [authorLookup.key, author] as const;
|
||||||
} catch {
|
} catch {
|
||||||
return [authorLookupKey, null] as const;
|
return [authorLookup.key, null] as const;
|
||||||
} finally {
|
} finally {
|
||||||
pendingPublicAuthorKeysRef.current.delete(authorLookupKey);
|
pendingPublicAuthorKeysRef.current.delete(authorLookup.key);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
).then((results) => {
|
).then((results) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||||
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||||
formatPlatformCompactCount,
|
formatPlatformCompactCount,
|
||||||
|
formatPlatformPublicAuthorAvatarLabel,
|
||||||
formatPlatformWorkDisplayName,
|
formatPlatformWorkDisplayName,
|
||||||
formatPlatformWorkDisplayTags,
|
formatPlatformWorkDisplayTags,
|
||||||
formatPlatformWorldTime,
|
formatPlatformWorldTime,
|
||||||
@@ -18,9 +19,11 @@ import {
|
|||||||
mapBarkBattleWorkToPlatformGalleryCard,
|
mapBarkBattleWorkToPlatformGalleryCard,
|
||||||
mapVisualNovelWorkToPlatformGalleryCard,
|
mapVisualNovelWorkToPlatformGalleryCard,
|
||||||
mapWoodenFishWorkToPlatformGalleryCard,
|
mapWoodenFishWorkToPlatformGalleryCard,
|
||||||
|
type PlatformBarkBattleGalleryCard,
|
||||||
type PlatformBigFishGalleryCard,
|
type PlatformBigFishGalleryCard,
|
||||||
type PlatformEdutainmentGalleryCard,
|
type PlatformEdutainmentGalleryCard,
|
||||||
type PlatformPuzzleGalleryCard,
|
type PlatformPuzzleGalleryCard,
|
||||||
|
resolvePlatformPublicWorkAuthorLookup,
|
||||||
resolvePlatformPublicWorkCode,
|
resolvePlatformPublicWorkCode,
|
||||||
resolvePlatformWorkAuthorDisplayName,
|
resolvePlatformWorkAuthorDisplayName,
|
||||||
resolvePlatformWorldFallbackCoverImage,
|
resolvePlatformWorldFallbackCoverImage,
|
||||||
@@ -312,6 +315,57 @@ test('public work author display hides phone masks and public user codes on card
|
|||||||
expect(resolvePlatformWorkAuthorDisplayName(card, null)).toBe('玩家');
|
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', () => {
|
test('keeps baby object match public card code and template label intact', () => {
|
||||||
const card: PlatformEdutainmentGalleryCard = {
|
const card: PlatformEdutainmentGalleryCard = {
|
||||||
sourceType: 'edutainment',
|
sourceType: 'edutainment',
|
||||||
|
|||||||
@@ -300,6 +300,12 @@ export type PlatformPublicGalleryCard =
|
|||||||
| PlatformBarkBattleGalleryCard
|
| PlatformBarkBattleGalleryCard
|
||||||
| PlatformEdutainmentGalleryCard;
|
| PlatformEdutainmentGalleryCard;
|
||||||
|
|
||||||
|
export type PlatformPublicWorkAuthorLookup = {
|
||||||
|
key: string;
|
||||||
|
source: 'publicUserCode' | 'ownerUserId';
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function isLibraryWorldEntry(
|
export function isLibraryWorldEntry(
|
||||||
entry: PlatformWorldCardLike,
|
entry: PlatformWorldCardLike,
|
||||||
): entry is CustomWorldLibraryEntry<CustomWorldProfile> {
|
): entry is CustomWorldLibraryEntry<CustomWorldProfile> {
|
||||||
@@ -923,6 +929,36 @@ export function resolvePlatformWorkAuthorDisplayName(
|
|||||||
return displayName || entryAuthorName || '玩家';
|
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) {
|
function normalizePlatformPublicAuthorName(value: string | null | undefined) {
|
||||||
const normalized = value?.trim() ?? '';
|
const normalized = value?.trim() ?? '';
|
||||||
if (!normalized || normalized === 'null' || normalized === 'undefined') {
|
if (!normalized || normalized === 'null' || normalized === 'undefined') {
|
||||||
|
|||||||
Reference in New Issue
Block a user