refactor: 收口公开作品展示格式

This commit is contained in:
2026-06-03 18:18:01 +08:00
parent f67f57b415
commit 5fecceef4f
6 changed files with 150 additions and 64 deletions

View File

@@ -180,19 +180,17 @@ import {
} from './rpgEntryPublicGalleryViewModel';
import {
buildPlatformWorldDisplayTags,
describePlatformPublicWorkKind,
describePlatformThemeLabel,
formatPlatformCompactCount,
formatPlatformWorkDisplayName,
formatPlatformWorkDisplayTag,
formatPlatformWorldTime,
isBarkBattleGalleryEntry,
isBigFishGalleryEntry,
isEdutainmentGalleryEntry,
isJumpHopGalleryEntry,
isMatch3DGalleryEntry,
isPuzzleGalleryEntry,
isSquareHoleGalleryEntry,
isVisualNovelGalleryEntry,
isWoodenFishGalleryEntry,
type PlatformPublicGalleryCard,
resolvePlatformPublicWorkCode,
resolvePlatformWorkAuthorDisplayName,
@@ -638,14 +636,14 @@ function WorldCard({
const playCount = getPlatformWorldPlayCount(entry);
const remixCount = getPlatformWorldRemixCount(entry);
const likeCount = getPlatformWorldLikeCount(entry);
const typeLabel = describePublicGalleryCardKind(entry);
const typeLabel = describePlatformPublicWorkKind(entry);
const authorName = resolvePlatformWorkAuthorDisplayName(
entry,
authorSummary,
);
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
const cardLabel = `${entry.worldName}${typeLabel}${formatCompactCount(playCount)}游玩,${formatCompactCount(remixCount)}改造,${formatCompactCount(likeCount)}点赞`;
const cardLabel = `${entry.worldName}${typeLabel}${formatPlatformCompactCount(playCount)}游玩,${formatPlatformCompactCount(remixCount)}改造,${formatPlatformCompactCount(likeCount)}点赞`;
const coverStats = [
{
label: '游玩',
@@ -716,7 +714,7 @@ function WorldCard({
className={`h-3.5 w-3.5 ${label === '点赞' ? 'fill-current' : ''}`}
aria-hidden="true"
/>
<span>{formatCompactCount(value)}</span>
<span>{formatPlatformCompactCount(value)}</span>
</span>
))}
</div>
@@ -769,7 +767,7 @@ function WorldCard({
))
) : (
<span className="platform-pill platform-pill--neutral px-2.5">
{describePublicGalleryCardKind(entry)}
{describePlatformPublicWorkKind(entry)}
</span>
)}
</div>
@@ -892,7 +890,7 @@ function RecommendRuntimePreviewCard({
}) {
const coverImage = resolvePlatformWorldCoverImage(entry);
const displayName = formatPlatformWorkDisplayName(entry.worldName);
const typeLabel = describePublicGalleryCardKind(entry);
const typeLabel = describePlatformPublicWorkKind(entry);
return (
<div
@@ -1071,16 +1069,16 @@ function RecommendRuntimeMeta({
onLike?.();
}}
disabled={!isActive || !onLike}
aria-label={`点赞 ${formatCompactCount(likeCount)}`}
aria-label={`点赞 ${formatPlatformCompactCount(likeCount)}`}
title="点赞"
>
<ThumbsUp className="h-5 w-5" aria-hidden="true" />
</button>
<span
className="platform-recommend-work-meta__like-count"
aria-label={`${formatCompactCount(likeCount)} 个赞`}
aria-label={`${formatPlatformCompactCount(likeCount)} 个赞`}
>
{formatCompactCount(likeCount)}
{formatPlatformCompactCount(likeCount)}
</span>
<button
type="button"
@@ -1111,7 +1109,7 @@ function RecommendRuntimeMeta({
onRemix?.();
}}
disabled={!isActive || !onRemix}
aria-label={`改造 ${formatCompactCount(remixCount)}`}
aria-label={`改造 ${formatPlatformCompactCount(remixCount)}`}
title="改造"
>
<GitFork className="h-5 w-5" aria-hidden="true" />
@@ -1323,7 +1321,7 @@ function DesktopTrendingItem({
<div className="flex items-center gap-2 text-[10px] tracking-[0.18em] text-[var(--platform-text-soft)]">
<span>{`${rank}`.padStart(2, '0')}</span>
<span className="truncate">
{describePublicGalleryCardKind(entry)}
{describePlatformPublicWorkKind(entry)}
</span>
</div>
<div className="mt-2 line-clamp-1 text-lg font-semibold text-[var(--platform-text-strong)]">
@@ -1350,7 +1348,7 @@ function DesktopTrendingItem({
? '拼图'
: isEdutainmentGalleryEntry(entry)
? entry.templateName
: describePublicGalleryCardKind(entry)}
: describePlatformPublicWorkKind(entry)}
</span>
)}
</div>
@@ -1407,11 +1405,11 @@ function PlatformRankingItem({
</div>
<div className="mt-1 flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-1 text-xs font-semibold text-[var(--platform-text-soft)]">
<span className="text-[var(--platform-warm-text)]">
{formatCompactCount(metricValue)}
{formatPlatformCompactCount(metricValue)}
</span>
<span>{metricLabel}</span>
<span>·</span>
<span>{describePublicGalleryCardKind(entry)}</span>
<span>{describePlatformPublicWorkKind(entry)}</span>
</div>
<div className="platform-ranking-item__tags">
{tags.map((tag, index) => (
@@ -1440,7 +1438,7 @@ function PlatformCategoryGameItem({
const tags = buildPlatformWorldDisplayTags(entry, 2);
const metric = getPlatformCategoryPrimaryMetric(entry);
const metaParts = [
describePublicGalleryCardKind(entry),
describePlatformPublicWorkKind(entry),
...tags.filter((tag) => tag !== categoryTag),
].slice(0, 3);
const actionLabel =
@@ -1481,7 +1479,7 @@ function PlatformCategoryGameItem({
<div className="platform-category-game-item__meta">
<span className="platform-category-game-item__metric">
<Star className="h-3.5 w-3.5 fill-current" />
<span>{formatCompactCount(metric.value)}</span>
<span>{formatPlatformCompactCount(metric.value)}</span>
</span>
<span>{metric.label}</span>
{metaParts.length > 0 ? <span>{metaParts.join(' · ')}</span> : null}
@@ -1672,7 +1670,7 @@ function PlatformWorkSearchResults({
</div>
</div>
<span className="platform-pill platform-pill--neutral shrink-0 px-3">
{describePublicGalleryCardKind(entry)}
{describePlatformPublicWorkKind(entry)}
</span>
</button>
);
@@ -1711,51 +1709,10 @@ async function getPublicWorkAuthorSummary(
return null;
}
function describePublicGalleryCardKind(entry: PlatformPublicGalleryCard) {
if (isBigFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('大鱼吃小鱼');
}
if (isPuzzleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('拼图');
}
if (isMatch3DGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('抓大鹅');
}
if (isSquareHoleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('方洞挑战');
}
if (isJumpHopGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('跳一跳');
}
if (isWoodenFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('敲木鱼');
}
if (isVisualNovelGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('视觉小说');
}
if (isBarkBattleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('汪汪声浪');
}
if (isEdutainmentGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag(entry.templateName);
}
return formatPlatformWorkDisplayTag(describePlatformThemeLabel(entry.themeMode));
}
function getPublicAuthorAvatarLabel(authorDisplayName: string) {
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
}
function formatCompactCount(value: number) {
const normalizedValue = Math.max(0, Math.round(value));
if (normalizedValue >= 100000000) {
return `${(normalizedValue / 100000000).toFixed(1)}亿`;
}
if (normalizedValue >= 10000) {
return `${(normalizedValue / 10000).toFixed(1)}`;
}
return `${normalizedValue}`;
}
function formatSnapshotTime(value: string | null | undefined) {
if (!value) {
return '刚刚保存';
@@ -6041,7 +5998,7 @@ export function RpgEntryHomeView({
</span>
<span className="platform-pill platform-pill--neutral px-3">
{leadPublicEntry
? describePublicGalleryCardKind(leadPublicEntry)
? describePlatformPublicWorkKind(leadPublicEntry)
: '作品'}
</span>
</div>

View File

@@ -3,8 +3,10 @@ import { expect, test } from 'vitest';
import {
buildPlatformWorldDisplayTags,
buildPuzzleWorkCoverSlides,
describePlatformPublicWorkKind,
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
formatPlatformCompactCount,
formatPlatformWorkDisplayName,
formatPlatformWorkDisplayTags,
formatPlatformWorldTime,
@@ -16,10 +18,11 @@ import {
mapBarkBattleWorkToPlatformGalleryCard,
mapVisualNovelWorkToPlatformGalleryCard,
mapWoodenFishWorkToPlatformGalleryCard,
type PlatformBigFishGalleryCard,
type PlatformEdutainmentGalleryCard,
type PlatformPuzzleGalleryCard,
resolvePlatformWorkAuthorDisplayName,
resolvePlatformPublicWorkCode,
resolvePlatformWorkAuthorDisplayName,
resolvePlatformWorldFallbackCoverImage,
} from './rpgEntryWorldPresentation';
@@ -53,6 +56,48 @@ test('platform work display text limits names and tags by character count', () =
).toEqual(['超长机关', '星桥']);
});
test('platform public work presentation formats compact counts and kind labels', () => {
const puzzleCard: PlatformPuzzleGalleryCard = {
sourceType: 'puzzle',
workId: 'puzzle-work-kind',
profileId: 'puzzle-profile-kind',
publicWorkCode: 'PZ-KIND',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
worldName: '机关拼图',
subtitle: '拼图关卡',
summaryText: '公开作品',
coverImageSrc: null,
themeTags: ['拼图'],
visibility: 'published',
publishedAt: '2026-05-18T00:00:00.000Z',
updatedAt: '2026-05-18T00:00:00.000Z',
};
const bigFishCard: PlatformBigFishGalleryCard = {
sourceType: 'big-fish',
workId: 'big-fish-work-kind',
profileId: 'big-fish-profile-kind',
publicWorkCode: 'BF-KIND',
ownerUserId: 'user-1',
authorDisplayName: '玩家',
worldName: '大鱼海湾',
subtitle: '大鱼关卡',
summaryText: '公开作品',
coverImageSrc: null,
themeTags: ['大鱼'],
visibility: 'published',
publishedAt: '2026-05-18T00:00:00.000Z',
updatedAt: '2026-05-18T00:00:00.000Z',
};
expect(formatPlatformCompactCount(-1)).toBe('0');
expect(formatPlatformCompactCount(9999)).toBe('9999');
expect(formatPlatformCompactCount(10000)).toBe('1.0万');
expect(formatPlatformCompactCount(100000000)).toBe('1.0亿');
expect(describePlatformPublicWorkKind(puzzleCard)).toBe('拼图');
expect(describePlatformPublicWorkKind(bigFishCard)).toBe('大鱼吃小');
});
test('platform public cards use play type reference images as cover fallback', () => {
const puzzleCard: PlatformPuzzleGalleryCard = {
sourceType: 'puzzle',

View File

@@ -1,5 +1,5 @@
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import { BABY_OBJECT_MATCH_EDUTAINMENT_TAG } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
@@ -863,6 +863,52 @@ export function formatPlatformWorkDisplayTags(
].slice(0, limit);
}
export function formatPlatformCompactCount(value: number) {
const normalizedValue = Math.max(0, Math.round(value));
if (normalizedValue >= 100000000) {
return `${(normalizedValue / 100000000).toFixed(1)}亿`;
}
if (normalizedValue >= 10000) {
return `${(normalizedValue / 10000).toFixed(1)}`;
}
return `${normalizedValue}`;
}
export function describePlatformPublicWorkKind(
entry: PlatformPublicGalleryCard,
) {
if (isBigFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('大鱼吃小鱼');
}
if (isPuzzleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('拼图');
}
if (isMatch3DGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('抓大鹅');
}
if (isSquareHoleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('方洞挑战');
}
if (isJumpHopGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('跳一跳');
}
if (isWoodenFishGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('敲木鱼');
}
if (isVisualNovelGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('视觉小说');
}
if (isBarkBattleGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag('汪汪声浪');
}
if (isEdutainmentGalleryEntry(entry)) {
return formatPlatformWorkDisplayTag(entry.templateName);
}
return formatPlatformWorkDisplayTag(describePlatformThemeLabel(entry.themeMode));
}
export function resolvePlatformWorkAuthorDisplayName(
entry: PlatformPublicGalleryCard,
authorSummary?: PublicUserSummary | null,