公开作品详情页不再显示占位作者名「创作者」,按作者 ID 补查真实昵称
Project CI / Backend tests (pull_request) Failing after 30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m27s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m23s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m15s
Project CI / Repository checks (pull_request) Failing after 33s
Project CI / Frontend tests (pull_request) Successful in 3m59s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m10s
Project CI / Native shell tests (pull_request) Successful in 8m37s
Project CI / Backend tests (pull_request) Failing after 30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m27s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m23s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 4m15s
Project CI / Repository checks (pull_request) Failing after 33s
Project CI / Frontend tests (pull_request) Successful in 3m59s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m10s
Project CI / Native shell tests (pull_request) Successful in 8m37s
- useGameAuthorName.ts(新增):useGameAuthorDisplayName —— 回包名是占位词(「创作者」/「未知作者」)或空时,按 author.id 调 GET /api/auth/public-users/by-id/{id} 取 displayName;查询中返回 null(避免先闪占位词),查不到返回空串、不编造名字
- GameDetailPage.tsx:详情页用解析后的作者名装配展示对象(displayGame),替代直接用公开投影里的 author.name
- packages/shared/src/components/GameDetailDisplay/index.tsx:作者名为空时不渲染「由 X 制作」那一行;头像首字改用 Array.from(name)[0](避免 emoji 代理对被截断);作者行补 title 悬浮显示完整名字
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import './index.css';
|
||||
|
||||
import { Maximize2, Smartphone, Sparkles } from 'lucide-react';
|
||||
import { type ReactElement, type ReactNode,useState } from 'react';
|
||||
import { type ReactElement, type ReactNode, useState } from 'react';
|
||||
|
||||
/** 详情展示里素材出现的位置:hero 大图与缩略图条。 */
|
||||
export type GameDetailAssetRole = 'cover' | 'thumb';
|
||||
@@ -210,6 +210,8 @@ export function GameDetailDisplay({
|
||||
}: GameDetailDisplayProps) {
|
||||
const controls = (controlsLabel ?? '').trim();
|
||||
const version = (versionLabel ?? '').trim();
|
||||
/** 作者名由宿主解析(公开详情会按作者 ID 补查真实昵称);空名不渲染,避免出现「由 — 制作」。 */
|
||||
const authorName = (game.author.name ?? '').trim();
|
||||
return (
|
||||
<div
|
||||
className={['game-detail-display', className].filter(Boolean).join(' ')}
|
||||
@@ -232,12 +234,14 @@ export function GameDetailDisplay({
|
||||
{rating ? (
|
||||
<div className="game-detail-hero__rating">{rating}</div>
|
||||
) : null}
|
||||
<div className="game-detail-hero__author">
|
||||
<span className="game-detail-avatar">
|
||||
{game.author.name.slice(0, 1)}
|
||||
</span>
|
||||
<span>由 {game.author.name} 制作</span>
|
||||
</div>
|
||||
{authorName ? (
|
||||
<div className="game-detail-hero__author">
|
||||
<span className="game-detail-avatar" aria-hidden="true">
|
||||
{Array.from(authorName)[0]}
|
||||
</span>
|
||||
<span title={authorName}>由 {authorName} 制作</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="game-detail-hero__actions">
|
||||
{actions}
|
||||
{typeof game.playCount === 'number' ? (
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
VERSION_STATUS_LABEL,
|
||||
VERSION_STATUS_NEEDS_AUTHOR_ACTION,
|
||||
} from './gameVersionHistoryModel';
|
||||
import { useGameAuthorDisplayName } from './useGameAuthorName';
|
||||
import { useGameReviews } from './useGameReviews';
|
||||
|
||||
type GameDetailPageProps = {
|
||||
@@ -292,6 +293,15 @@ function GameDetailContent({
|
||||
*/
|
||||
const publiclyVisible = game.status === 'published';
|
||||
const reviews = useGameReviews(game.id, canReview, publiclyVisible);
|
||||
/**
|
||||
* 作者展示名:公开投影只会退化成占位角色词,真实昵称按作者 ID 补查一次
|
||||
* (见 useGameAuthorName);查不到时给空串,展示层就不渲染「由 X 制作」那一行。
|
||||
*/
|
||||
const authorName = useGameAuthorDisplayName(game.author);
|
||||
const displayGame = {
|
||||
...game,
|
||||
author: { ...game.author, name: authorName ?? '' },
|
||||
};
|
||||
/** 作者视角的条目没有公开投影 `currentVersion`,可玩版本取版本历史里的已发布版本。 */
|
||||
const publishedVersion =
|
||||
ownerGame?.versions.find((item) => item.status === 'published') ?? null;
|
||||
@@ -314,7 +324,7 @@ function GameDetailContent({
|
||||
<ArrowLeft /> 返回
|
||||
</button>
|
||||
<GameDetailDisplay
|
||||
game={game}
|
||||
game={displayGame}
|
||||
coverObjectKey={game.coverObjectKey}
|
||||
screenshotObjectKeys={game.screenshots}
|
||||
coverColor={game.coverColor}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { getPublicAuthUserById } from '../../services/authService';
|
||||
|
||||
/**
|
||||
* 作者展示名缺失时服务端会退化成角色词,不是用户名:
|
||||
* api-server 的 `game_payload`(公开目录/公开详情/作者侧)写的是
|
||||
* `author_name.as_deref().unwrap_or("创作者")`,后台行 `admin_game_payload` 写的是
|
||||
* `unwrap_or("未知作者")`。这两个词一律当成「没有名字」,否则会把占位词当昵称渲染出来。
|
||||
*/
|
||||
const GAME_AUTHOR_PLACEHOLDER_NAMES: Record<string, true> = {
|
||||
创作者: true,
|
||||
未知作者: true,
|
||||
};
|
||||
|
||||
export type GameAuthorIdentity = {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
};
|
||||
|
||||
function isRealAuthorName(value: string | null | undefined) {
|
||||
const name = (value ?? '').trim();
|
||||
return name.length > 0 && GAME_AUTHOR_PLACEHOLDER_NAMES[name] !== true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏作者的对外展示名。
|
||||
*
|
||||
* 游戏行上的 `author_name` 由创建接口固定写空(api-server `create_game` 传 None),
|
||||
* 所以公开投影里的 `author.name` 只会是占位角色词,不是用户名。真实昵称只能按
|
||||
* `author.id`(owner_user_id)走公开用户查询补一次:`GET /api/auth/public-users/by-id/{userId}`,
|
||||
* 与陶泥号查询 `/api/auth/public-users/by-code/{code}` 同一族接口,不需要登录态。
|
||||
*
|
||||
* 返回 `null` 表示还在查询——展示层此时不要渲染名字,避免先闪一下占位词;
|
||||
* 返回空串表示查不到(账号失效 / 网络失败),由展示层降级。
|
||||
* 回包名本来就是真实昵称时不再多发请求:后端补上作者名后这里自然退化为普通渲染。
|
||||
*/
|
||||
export function useGameAuthorDisplayName(
|
||||
author: GameAuthorIdentity | null | undefined,
|
||||
): string | null {
|
||||
const fallbackName = (author?.name ?? '').trim();
|
||||
const hasFallbackName = isRealAuthorName(fallbackName);
|
||||
const authorId = (author?.id ?? '').trim();
|
||||
const [lookedUp, setLookedUp] = useState<{
|
||||
id: string;
|
||||
name: string;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasFallbackName || !authorId) return;
|
||||
let alive = true;
|
||||
void getPublicAuthUserById(authorId)
|
||||
.then((user) => {
|
||||
if (alive) setLookedUp({ id: authorId, name: user.displayName.trim() });
|
||||
})
|
||||
.catch(() => {
|
||||
// 作者账号查不到(已失效 / 非公开)时不编造名字,按空名交给展示层降级。
|
||||
if (alive) setLookedUp({ id: authorId, name: '' });
|
||||
});
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [authorId, hasFallbackName]);
|
||||
|
||||
if (hasFallbackName) return fallbackName;
|
||||
if (!authorId) return '';
|
||||
if (!lookedUp || lookedUp.id !== authorId) return null;
|
||||
return lookedUp.name;
|
||||
}
|
||||
Reference in New Issue
Block a user