From c6677e1323834de5203885f323c9c180a3a54485 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Wed, 7 Oct 2026 15:51:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86=EF=BC=9A=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=9B=B6=E5=BC=95=E7=94=A8=E7=9A=84=20CoCreationHotDerived=20?= =?UTF-8?q?=E5=8F=8A=E5=85=B6=20.co-creation-hot*=20=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=EF=BC=88=E7=83=AD=E9=97=A8=E8=A1=8D=E7=94=9F=E6=A0=8F=E5=B7=B2?= =?UTF-8?q?=E5=B9=B6=E5=85=A5=E5=9B=BE=E8=B0=B1=E9=A1=B5=E5=AD=90=E4=BB=A3?= =?UTF-8?q?=E5=9D=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...�技术方案】游戏共创与作品Fork-2026-10-03.md | 2 +- .../CoCreationHotDerived.tsx | 81 ------------------- .../GameDistributionPages.test.tsx | 3 +- .../game-distribution/gameDistribution.css | 44 ---------- 4 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 src/components/game-distribution/CoCreationHotDerived.tsx diff --git a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md index 584ee85dd..05b5b46d0 100644 --- a/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md +++ b/docs/【技术方案】游戏共创与作品Fork-2026-10-03.md @@ -482,7 +482,7 @@ A 路线里那个必须提前知道的互斥点已经按既有做法绕开:`cr - **移动端门限豁免(2026-10-06)**:平台原有的「移动端仅支持作品展示」欢迎弹窗只看 `!isDesktopLayout`、忽略路由,会把公开只读的族谱树 / 共创主题页首屏盖住。现在按**显式白名单**豁免这几个只读浏览 stage:`game-lineage` / `game-themes` / `game-theme` / `game-co-creation`(清单在 `PlatformActiveMobileWelcomeDialog.tsx` 的 `MOBILE_WELCOME_EXEMPT_STAGES`,键名即 stage 名,不做前缀/范围匹配)。**创作 / 项目 / 发布 / 我的 / 平台首页仍保持门限**——那些才是「移动端不支持体验创作工具」的部分;弹窗文案未改(与豁免后的事实不矛盾:族谱与主题页本身就是作品展示)。 - **画布交互硬化(2026-10-07,用户口径)**:① **滚轮只缩放画布**——React 的 `onWheel` 挂在根容器上且是 **passive** 的(`preventDefault` 无效 ⇒ 页面跟着滚),因此改为在**画布元素本身**挂原生 `wheel` 监听并显式 `{ passive: false }`(不绑 `window`);② **拖动不选中/不拖走文本**——画布容器 `user-select: none` + `touch-action: none` + `overscroll-behavior: contain`,拖动期间把 `user-select: none` 压到 `document.body`(跨出画布也有效,松手恢复),并对 `selectstart` / `dragstart` 挂原生阻止、画布内图片 `-webkit-user-drag: none; pointer-events: none`;指针捕获(`setPointerCapture`)保证移出容器仍跟手;③ **平移边界**:`clampLineagePan`(纯函数)保证任何时刻至少 `LINEAGE_PAN_KEEP_PX = 96` 像素内容可见,拖到底/拖到顶不会把内容拖飞。真机实测(Chromium,1440×900):滚轮后缩放 60%→65%、`window.scrollY` 与平台滚动容器 `scrollTop` **都不变**;两次连续拖动各位移 +120px 且 `window.getSelection()` 始终为空字符串。 - **「一键开始共创」位置与未安装检测(2026-10-07,用户口径)**:按钮从共创信息卡**搬到主操作行**(与「立即玩 / 收藏」并列,保持主操作视觉权重;卡内不再重复出现)。未安装检测:纯 web **拿不到系统级安装信息**,只能靠「唤起后浏览器是否被接管」间接判断(`blur` / `visibilitychange`,超时 `FORK_LAUNCH_FALLBACK_DELAY_MS = 1200ms`);**未接管 / 移动端视为「没有客户端」→ 打开与顶栏「下载客户端」同一个下载入口**(`requestClientDownload()` 事件 → `ClientDownloadEntry` 弹窗),并保留手动复制作品 ID 的降级路径与一句人话提示。**局限**:慢启动的客户端/被用户取消的系统确认框/某些不派发 blur 的环境都会误判成「未安装」,代价只是多打开一个下载入口(用户可关掉重试),不会阻断正常路径;老客户端(不响应深链)走同一条兜底。 -- **共创信息卡只留「标签 + 数据 + 入口」(2026-10-07,用户口径)**:卡里保留世代/授权标签、父代文案、数据概览(游玩合计与分解、累计衍生物、累计创作世代数)、「查看创作族谱」与所属主题 tag;**派生作品卡列表(含原「热门衍生栏」)整块移除**,改由族谱页**图下方**的「父代 / 子代」两块清单承担(父代 = 祖先链根→直接父;子代 = **直接子代**、按**热度(自身 `playCount`)降序**、同热度保持服务端创建序)。选「直接子代」的理由:与 `forkCount`/「被改编 N」同口径(数字与卡片数一致)、与画布「只看父代与子代」开关同义、更深的层级已由上面的树表达。 +- **共创信息卡只留「标签 + 数据 + 入口」(2026-10-07,用户口径)**:卡里保留世代/授权标签、父代文案、数据概览(游玩合计与分解、累计衍生物、累计创作世代数)、「查看创作族谱」与所属主题 tag;**派生作品卡列表(含原「热门衍生栏」)整块移除**(`CoCreationHotDerived` 组件与其 `.co-creation-hot*` 样式已删除,零引用),改由族谱页**图下方**的「父代 / 子代」两块清单承担(父代 = 祖先链根→直接父;子代 = **直接子代**、按**热度(自身 `playCount`)降序**、同热度保持服务端创建序)。选「直接子代」的理由:与 `forkCount`/「被改编 N」同口径(数字与卡片数一致)、与画布「只看父代与子代」开关同义、更深的层级已由上面的树表达。 - **截断 / 空树 / 不可用父节点**在两种形态下都有降级:截断提示、空态、以及「原作品已不可用」的占位节点(可聚焦、不可点进详情、不展示被删作品标题与作者名)。 - **「共创」页签口径(2026-10-07,用户口径)**:页签与「游戏」页签**复用同一个 `GameGalleryPage`**(`scope="forkable"`),数据来自公开目录并带 `forkable=1`(分页/游标/去重/末页/失败态全部沿用已实现的「加载更多」);**服务端尚未生效期间客户端再兜一层同义过滤**(`forkAuthorization !== 'forbidden'`,未知按禁止失败关闭),两条口径一致、服务端上线后等价幂等。页签不再指向共创主题列表(主题列表页与路由保留,无页签入口);页面标题 `game-co-creation` → 「共创 - 陶泥儿」,路由 `/games/co-creation`,移动端门限同样按「只读浏览 = 作品展示」豁免。空态为「暂时没有开放共创的作品」。 - **初始适配口径(2026-10-07 复核)**:初次进入按**宽高同时 fit**(`min(scaleX, scaleY)` + `FIT_PADDING`)并**水平垂直都居中**;若这个比例低于可读下限 `READABLE_MIN_SCALE = 0.6`,就**不强行全览**,改用可读缩放 + 平移,并给出明确提示「树较大:已按可读缩放显示(其余代际在视口外),拖拽平移查看,或点「适应窗口」看全树。」——两种情况下都**不会**在没有提示的前提下把最深的代际丢在视口外。窄屏「图谱视图」另按**宽度**适配(见上文)。 diff --git a/src/components/game-distribution/CoCreationHotDerived.tsx b/src/components/game-distribution/CoCreationHotDerived.tsx deleted file mode 100644 index 4ae64f71b..000000000 --- a/src/components/game-distribution/CoCreationHotDerived.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import type { GameDistributionLineageNode } from '../../services/gameDistributionClient'; -import { coCreationGenerationShortLabel } from './coCreationCopy'; -import { GameCover } from './GameCover'; -import type { LineagePlayCountBreakdown } from './lineageTreeLayout'; - -/** - * 热门衍生栏:当前作品下热度最高的 5 个分支作品(横向卡片)。 - * - * 热度口径:**`playCount` 倒序**,同热度按创建顺序(服务端顺序)稳定兜底。 - * `/derived` 目前不保证服务端排序,所以排序在客户端做;后端若补了排序, - * 这段逻辑仍然成立(只是排序变成幂等)。 - * - * 显示口径(2026-10-06):「游玩次数」的**显示**一律是**子树合计**(父代展示自己 + 全部后代)。 - * 传了 `playCountTotals` 时排序与显示都用合计(这样列表顺序与卡片上的数字一致); - * 传不进来(拿不到树)时回退服务端的**自身值**,不暗示含衍生。 - */ -export const CO_CREATION_HOT_DERIVED_LIMIT = 5; - -export function CoCreationHotDerived({ - nodes, - playCountTotals, - onOpenGameDetail, -}: { - nodes: readonly GameDistributionLineageNode[]; - /** - * 每个节点的游玩次数分解(详情页用同一棵 `/lineage` 在前端算好的,零额外请求)。 - * 缺失时显示自身值。 - */ - playCountTotals?: ReadonlyMap | null; - onOpenGameDetail: (gameId: string) => void; -}) { - const shownPlayCount = (gameId: string, own: number) => - playCountTotals?.get(gameId)?.total ?? own; - const hottest = [...nodes] - .sort( - (left, right) => - shownPlayCount(right.gameId, right.playCount) - - shownPlayCount(left.gameId, left.playCount), - ) - .slice(0, CO_CREATION_HOT_DERIVED_LIMIT); - if (hottest.length === 0) return null; - - return ( -
- {hottest.map((node) => ( - - ))} -
- ); -} - -/** `GameCover` 只读 title / coverColor / icon / coverObjectKey;族谱节点没有完整游戏行。 */ -function gameForCover(node: GameDistributionLineageNode) { - return { - title: node.title, - coverColor: '#F3E4D0', - icon: '🎮', - coverObjectKey: node.coverObjectKey, - } as never; -} - -export default CoCreationHotDerived; diff --git a/src/components/game-distribution/GameDistributionPages.test.tsx b/src/components/game-distribution/GameDistributionPages.test.tsx index b62d1cff3..b43fcbe5a 100644 --- a/src/components/game-distribution/GameDistributionPages.test.tsx +++ b/src/components/game-distribution/GameDistributionPages.test.tsx @@ -4818,7 +4818,8 @@ describe('GameDetailPage 共创模块(文档对齐)', () => { expect( within(card).getByRole('button', { name: '查看创作族谱' }), ).toBeTruthy(); - // 卡里**不再有**派生作品卡列表(热门衍生栏 / 任何父代子代清单)。 + // 卡里**不再有**派生作品卡列表:`CoCreationHotDerived` 组件与它的 `.co-creation-hot*` 样式 + // 已于 2026-10-07 删除(零引用),这里钉住「它不会再回来」。 expect(card.querySelector('.co-creation-hot')).toBeNull(); expect(screen.queryByLabelText('热门衍生作品')).toBeNull(); expect(within(card).queryByText('被改编出来的作品')).toBeNull(); diff --git a/src/components/game-distribution/gameDistribution.css b/src/components/game-distribution/gameDistribution.css index a776c4360..3dba9fc11 100644 --- a/src/components/game-distribution/gameDistribution.css +++ b/src/components/game-distribution/gameDistribution.css @@ -812,50 +812,6 @@ color: var(--platform-text-soft); cursor: not-allowed; } -/* 热门衍生栏:横向卡片。 */ -.game-detail-info-card .co-creation-hot { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(min(7rem, 100%), 1fr)); - gap: 0.4rem; -} -.game-detail-info-card .co-creation-hot__card { - display: flex; - flex-direction: column; - gap: 0.2rem; - min-width: 0; - border: 1px solid var(--platform-surface-border); - border-radius: 0.6rem; - padding: 0.3rem; - background: var(--platform-surface, #fff); - color: var(--platform-text-base); - font: inherit; - text-align: left; - cursor: pointer; -} -.game-detail-info-card .co-creation-hot__cover { - display: block; - width: 100%; - aspect-ratio: 16 / 9; - overflow: hidden; - border-radius: 0.4rem; - background: var(--platform-surface-border); -} -.game-detail-info-card .co-creation-hot__cover .game-cover { - width: 100%; - height: 100%; - border-radius: 0; -} -.game-detail-info-card .co-creation-hot__title { - color: var(--platform-text-strong); - font-size: 0.74rem; - font-weight: 600; - overflow-wrap: anywhere; -} -.game-detail-info-card .co-creation-hot__meta { - color: var(--platform-text-soft); - font-size: 0.68rem; - overflow-wrap: anywhere; -} /* 画布内悬浮控件(右下角):圆角胶囊、浅色底 + 毛玻璃、内边距统一、轻阴影。 */ .game-lineage-controls { position: absolute;