1
This commit is contained in:
@@ -1,20 +1,32 @@
|
||||
import { ArrowLeft, Copy, GitFork, Play, Share2 } from 'lucide-react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Clock3,
|
||||
Copy,
|
||||
Gamepad2,
|
||||
GitFork,
|
||||
Heart,
|
||||
Play,
|
||||
Share2,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
import { copyTextToClipboard } from '../../services/clipboard';
|
||||
import { ResolvedAssetImage } from '../ResolvedAssetImage';
|
||||
import {
|
||||
buildPlatformWorldTags,
|
||||
buildPlatformWorldDisplayTags,
|
||||
formatPlatformWorkDisplayName,
|
||||
formatPlatformWorkDisplayTags,
|
||||
formatPlatformWorldTime,
|
||||
type PlatformPublicGalleryCard,
|
||||
resolvePlatformPublicWorkCode,
|
||||
resolvePlatformWorldCoverImage,
|
||||
resolvePlatformWorldStats,
|
||||
type PlatformPublicGalleryCard,
|
||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
|
||||
export interface PlatformWorkDetailViewProps {
|
||||
entry: PlatformPublicGalleryCard;
|
||||
authorAvatarUrl?: string | null;
|
||||
isBusy: boolean;
|
||||
error: string | null;
|
||||
onBack: () => void;
|
||||
@@ -40,8 +52,13 @@ function getSourceLabel(entry: PlatformPublicGalleryCard) {
|
||||
return 'RPG';
|
||||
}
|
||||
|
||||
function getAuthorAvatarLabel(authorDisplayName: string) {
|
||||
return Array.from(authorDisplayName.trim() || '作')[0] ?? '作';
|
||||
}
|
||||
|
||||
export function PlatformWorkDetailView({
|
||||
entry,
|
||||
authorAvatarUrl,
|
||||
isBusy,
|
||||
error,
|
||||
onBack,
|
||||
@@ -50,30 +67,51 @@ export function PlatformWorkDetailView({
|
||||
}: PlatformWorkDetailViewProps) {
|
||||
const coverImage = resolvePlatformWorldCoverImage(entry);
|
||||
const publicWorkCode = resolvePlatformPublicWorkCode(entry);
|
||||
const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? '';
|
||||
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>(
|
||||
'idle',
|
||||
);
|
||||
const [shareState, setShareState] = useState<'idle' | 'copied' | 'failed'>(
|
||||
'idle',
|
||||
);
|
||||
const displayName = formatPlatformWorkDisplayName(entry.worldName);
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
[
|
||||
getSourceLabel(entry),
|
||||
...buildPlatformWorldTags(entry).map((tag) => tag.trim()),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.slice(0, 4),
|
||||
formatPlatformWorkDisplayTags(
|
||||
[getSourceLabel(entry), ...buildPlatformWorldDisplayTags(entry, 3)],
|
||||
4,
|
||||
),
|
||||
[entry],
|
||||
);
|
||||
const stats = resolvePlatformWorldStats(entry);
|
||||
const statItems = [
|
||||
{ label: '改造次数', value: formatCompactCount(stats.remixCount) },
|
||||
{ label: '游玩次数', value: formatCompactCount(stats.playCount) },
|
||||
{ label: '点赞次数', value: formatCompactCount(stats.likeCount) },
|
||||
{
|
||||
label: '上线日期',
|
||||
value: formatPlatformWorldTime(stats.publishedAt),
|
||||
label: '改造',
|
||||
value: formatCompactCount(stats.remixCount),
|
||||
unit: '次',
|
||||
icon: GitFork,
|
||||
tone: 'remix',
|
||||
},
|
||||
{
|
||||
label: '游玩',
|
||||
value: formatCompactCount(stats.playCount),
|
||||
unit: '次',
|
||||
icon: Gamepad2,
|
||||
tone: 'play',
|
||||
},
|
||||
{
|
||||
label: '点赞',
|
||||
value: formatCompactCount(stats.likeCount),
|
||||
unit: '赞',
|
||||
icon: Heart,
|
||||
tone: 'like',
|
||||
},
|
||||
{
|
||||
label: '最近更新',
|
||||
value: formatPlatformWorldTime(stats.updatedAt ?? stats.publishedAt),
|
||||
icon: Clock3,
|
||||
tone: 'time',
|
||||
isTime: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -162,10 +200,26 @@ export function PlatformWorkDetailView({
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="platform-work-detail__name">
|
||||
{entry.worldName}
|
||||
{displayName}
|
||||
</div>
|
||||
<div className="platform-work-detail__author">
|
||||
{entry.authorDisplayName}
|
||||
<span className="platform-work-detail__author-avatar">
|
||||
{normalizedAuthorAvatarUrl ? (
|
||||
<ResolvedAssetImage
|
||||
src={normalizedAuthorAvatarUrl}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
className="platform-work-detail__author-avatar-image"
|
||||
/>
|
||||
) : (
|
||||
<span className="platform-work-detail__author-avatar-label">
|
||||
{getAuthorAvatarLabel(entry.authorDisplayName)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="platform-work-detail__author-name">
|
||||
{entry.authorDisplayName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -175,18 +229,37 @@ export function PlatformWorkDetailView({
|
||||
disabled={isBusy}
|
||||
>
|
||||
<GitFork className="h-5 w-5" />
|
||||
Remix
|
||||
作品改造
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="platform-work-detail__stats">
|
||||
{statItems.map((item) => (
|
||||
<div key={item.label} className="platform-work-detail__stat">
|
||||
<div className="platform-work-detail__stat-label">
|
||||
{item.label}
|
||||
<div
|
||||
key={item.label}
|
||||
className={`platform-work-detail__stat platform-work-detail__stat--${item.tone}`}
|
||||
>
|
||||
<div className="platform-work-detail__stat-head">
|
||||
<span className="platform-work-detail__stat-icon">
|
||||
<item.icon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
<span className="platform-work-detail__stat-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="platform-work-detail__stat-value">
|
||||
{item.value}
|
||||
<div
|
||||
className={`platform-work-detail__stat-value${
|
||||
item.isTime ? ' platform-work-detail__stat-value--time' : ''
|
||||
}`}
|
||||
>
|
||||
<span className="platform-work-detail__stat-number">
|
||||
{item.value}
|
||||
</span>
|
||||
{item.unit ? (
|
||||
<span className="platform-work-detail__stat-unit">
|
||||
{item.unit}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user