This commit is contained in:
2026-04-30 17:49:07 +08:00
parent 805d6f8cae
commit 9d684cb7b3
615 changed files with 15368 additions and 6172 deletions

View File

@@ -133,6 +133,8 @@ const puzzlePublicEntry = {
summaryText: '一张用于公开分享的拼图作品。',
coverImageSrc: null,
themeTags: ['奇幻'],
playCount: 20,
remixCount: 5,
likeCount: 12,
visibility: 'published',
publishedAt: '1777110165.990127Z',
@@ -553,22 +555,40 @@ test('public gallery cards hide work code until detail is opened', async () => {
expect(onOpenGalleryDetail).toHaveBeenCalledWith(puzzlePublicEntry);
});
test('mobile public work cards render cover, content and like count', () => {
test('mobile public work cards render cover, author, kind and cover stats', () => {
const { container } = renderLoggedOutHomeView(vi.fn(), {
latestEntries: [puzzlePublicEntry],
});
const card = screen.getByRole('button', {
name: /12/u,
name: /20512/u,
});
expect(
card.querySelector('.platform-public-work-card__cover.aspect-video'),
).toBeTruthy();
expect(
card.querySelector('.platform-public-work-card__cover-stats'),
).toBeTruthy();
expect(
card.querySelectorAll('.platform-public-work-card__cover-stat'),
).toHaveLength(3);
expect(
card.querySelector('.platform-public-work-card__kind')?.textContent,
).toBe('拼图');
expect(
card.querySelector('.platform-public-work-card__author-avatar')
?.textContent,
).toBe('拼');
expect(screen.getByText('奇幻拼图')).toBeTruthy();
expect(screen.getByText('拼图玩家')).toBeTruthy();
expect(screen.getByText('一张用于公开分享的拼图作品。')).toBeTruthy();
expect(screen.getByText('奇幻')).toBeTruthy();
expect(screen.getByText('20')).toBeTruthy();
expect(screen.getByText('5')).toBeTruthy();
expect(screen.getByText('12')).toBeTruthy();
expect(screen.getByText('点赞')).toBeTruthy();
expect(card.querySelector('.platform-pill--warm')?.textContent).not.toBe(
'推荐',
);
expect(
container.querySelector('.platform-mobile-home-channel--active')
?.textContent,

View File

@@ -9,6 +9,7 @@ import {
Clock3,
Coins,
Copy,
Gamepad2,
Heart,
House,
LogIn,
@@ -362,22 +363,40 @@ function SaveArchivePreview({
function WorldCard({
entry,
badge,
metaLabel,
onClick,
className,
}: {
entry: PlatformWorldCardLike;
badge: string;
metaLabel: string;
entry: PlatformPublicGalleryCard;
onClick: () => void;
className?: string;
}) {
const coverImage = resolvePlatformWorldCoverImage(entry);
const displayName = formatPlatformWorkDisplayName(entry.worldName);
const tags = buildPlatformWorldDisplayTags(entry, 3);
const playCount = getPlatformWorldPlayCount(entry);
const remixCount = getPlatformWorldRemixCount(entry);
const likeCount = getPlatformWorldLikeCount(entry);
const cardLabel = `${entry.worldName}${formatCompactCount(likeCount)}点赞`;
const typeLabel = describePublicGalleryCardKind(entry);
const authorName = entry.authorDisplayName.trim() || '玩家';
const authorAvatarLabel = getPublicAuthorAvatarLabel(authorName);
const cardLabel = `${entry.worldName}${typeLabel}${formatCompactCount(playCount)}游玩,${formatCompactCount(remixCount)}改造,${formatCompactCount(likeCount)}点赞`;
const coverStats = [
{
label: '游玩',
value: playCount,
icon: Gamepad2,
},
{
label: '改造',
value: remixCount,
icon: Pencil,
},
{
label: '点赞',
value: likeCount,
icon: Heart,
},
];
return (
<button
@@ -397,13 +416,16 @@ function WorldCard({
<div className="absolute inset-0 bg-[radial-gradient(circle_at_18%_16%,rgba(255,255,255,0.28),transparent_30%),linear-gradient(135deg,rgba(255,118,117,0.42),rgba(89,164,255,0.34))]" />
)}
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(0,0,0,0.02),rgba(0,0,0,0.18))]" />
<div className="absolute left-3 top-3 flex min-w-0 max-w-[calc(100%-1.5rem)] flex-wrap gap-1.5">
<span className="platform-pill platform-pill--warm max-w-[9rem] truncate px-2.5">
{badge}
</span>
<span className="platform-pill platform-pill--neutral max-w-[9rem] truncate px-2.5">
{metaLabel}
</span>
<div className="platform-public-work-card__cover-stats">
{coverStats.map(({ label, value, icon: Icon }) => (
<span key={label} className="platform-public-work-card__cover-stat">
<Icon
className={`h-3.5 w-3.5 ${label === '点赞' ? 'fill-current' : ''}`}
aria-hidden="true"
/>
<span>{formatCompactCount(value)}</span>
</span>
))}
</div>
</div>
@@ -413,21 +435,21 @@ function WorldCard({
<div className="line-clamp-1 break-words text-base font-black leading-tight text-[var(--platform-text-strong)]">
{displayName}
</div>
{entry.subtitle ? (
<div className="mt-0.5 line-clamp-1 break-words text-[11px] font-medium text-[var(--platform-text-soft)]">
{entry.subtitle}
</div>
) : null}
</div>
<div className="platform-public-work-card__likes shrink-0 text-right">
<div className="flex items-center justify-end gap-1 text-xs font-black text-[var(--platform-warm-text)]">
<Heart className="h-3.5 w-3.5 fill-current" />
<span>{formatCompactCount(likeCount)}</span>
</div>
<div className="mt-0.5 text-[10px] font-semibold text-[var(--platform-text-soft)]">
<div className="mt-1 flex min-w-0 items-center gap-1.5">
<span
aria-hidden="true"
className="platform-public-work-card__author-avatar"
>
{authorAvatarLabel}
</span>
<span className="line-clamp-1 break-words text-[11px] font-semibold text-[var(--platform-text-soft)]">
{authorName}
</span>
</div>
</div>
<span className="platform-public-work-card__kind shrink-0">
{typeLabel}
</span>
</div>
<div className="line-clamp-2 break-words text-xs leading-5 text-[color:color-mix(in_srgb,var(--platform-text-base)_88%,transparent)]">
@@ -960,6 +982,10 @@ function describePublicGalleryCardKind(entry: PlatformPublicGalleryCard) {
return formatPlatformWorkDisplayTag(kind);
}
function getPublicAuthorAvatarLabel(authorDisplayName: string) {
return Array.from(authorDisplayName.trim() || '玩')[0] ?? '玩';
}
function getPlatformWorldLikeCount(entry: PlatformWorldCardLike) {
return Math.max(0, Math.round(entry.likeCount ?? 0));
}
@@ -1404,9 +1430,16 @@ function ProfileNicknameModal({
}) {
return (
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
<div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
<div
role="dialog"
aria-modal="true"
aria-labelledby="profile-nickname-title"
className="platform-modal-shell platform-remap-surface w-full max-w-sm overflow-hidden rounded-[1.4rem]"
>
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
<div className="text-base font-black"></div>
<div id="profile-nickname-title" className="text-base font-black">
</div>
<button
type="button"
aria-label="关闭昵称修改"
@@ -1529,9 +1562,16 @@ function ProfileAvatarCropModal({
return (
<div className="platform-modal-backdrop fixed inset-0 z-[80] flex items-center justify-center px-4 py-6">
<div className="platform-recharge-modal w-full max-w-sm overflow-hidden rounded-[1.4rem]">
<div
role="dialog"
aria-modal="true"
aria-labelledby="profile-avatar-crop-title"
className="platform-modal-shell platform-remap-surface w-full max-w-sm overflow-hidden rounded-[1.4rem]"
>
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">
<div className="text-base font-black"></div>
<div id="profile-avatar-crop-title" className="text-base font-black">
</div>
<button
type="button"
aria-label="关闭头像裁剪"
@@ -2726,12 +2766,6 @@ export function RpgEntryHomeView({
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:mobile-feed:${mobileHomeChannel}`}
entry={entry}
badge={
mobileHomeChannel === 'recommend'
? '推荐'
: describePublicGalleryCardKind(entry)
}
metaLabel={entry.authorDisplayName}
onClick={() => onOpenGalleryDetail(entry)}
className="w-full"
/>
@@ -3244,8 +3278,6 @@ export function RpgEntryHomeView({
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-featured`}
entry={entry}
badge="推荐"
metaLabel={describePublicGalleryCardKind(entry)}
onClick={() => onOpenGalleryDetail(entry)}
className="w-full min-w-0"
/>
@@ -3375,8 +3407,6 @@ export function RpgEntryHomeView({
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-latest`}
entry={entry}
badge={describePublicGalleryCardKind(entry)}
metaLabel={entry.authorDisplayName}
onClick={() => onOpenGalleryDetail(entry)}
className="w-full min-w-0"
/>

View File

@@ -96,9 +96,9 @@ export function mapPuzzleWorkToPlatformGalleryCard(
publicWorkCode: buildPuzzlePublicWorkCode(work.profileId),
ownerUserId: work.ownerUserId,
authorDisplayName: work.authorDisplayName,
worldName: work.levelName,
worldName: work.workTitle || work.levelName,
subtitle: '拼图关卡',
summaryText: work.summary,
summaryText: work.workDescription || work.summary,
coverImageSrc: work.coverImageSrc,
themeTags: work.themeTags,
playCount: work.playCount ?? 0,
@@ -120,7 +120,7 @@ export function mapBigFishWorkToPlatformGalleryCard(
profileId: work.sourceSessionId,
publicWorkCode: buildBigFishPublicWorkCode(work.sourceSessionId),
ownerUserId: work.ownerUserId,
authorDisplayName: '大鱼陶泥主',
authorDisplayName: work.authorDisplayName,
worldName: work.title,
subtitle: work.subtitle || '大鱼吃小鱼',
summaryText: work.summary,