From c4a1409b3adfd6b560ef15a52fe73e2664fbae1a Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Wed, 7 Oct 2026 13:37:58 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BD=91=E9=A1=B5=E4=BE=A7=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=94=B9=E5=90=8D=EF=BC=9AFork=20=E5=8A=A8=E4=BD=9C=E5=90=8D?= =?UTF-8?q?=E4=B8=80=E5=BE=8B=E5=8F=AB=E5=BC=80=E5=A7=8B=E5=85=B1=E5=88=9B?= =?UTF-8?q?=EF=BC=88=E8=AF=A6=E6=83=85=E9=A1=B5=E4=B8=BB=E6=93=8D=E4=BD=9C?= =?UTF-8?q?/=E4=BF=A1=E6=81=AF=E9=9D=A2=E6=9D=BF=E6=AD=A4=E7=89=88?= =?UTF-8?q?=E6=9C=AC/=E6=8F=90=E7=A4=BA=E6=96=87=E6=A1=88/=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E5=8F=A3=E5=BE=84=20+=20=E7=BB=84=E4=BB=B6=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../game-distribution/GameDetailPage.tsx | 15 +++-- .../GameDistributionPages.test.tsx | 61 +++++++++++-------- .../game-distribution/GameLineagePage.tsx | 12 ++-- .../game-distribution/LineageInfoPanel.tsx | 2 +- .../game-distribution/LineageOutlineList.tsx | 2 +- .../game-distribution/MyGamesPage.test.tsx | 6 +- .../game-distribution/MyGamesPage.tsx | 2 +- .../game-distribution/gameDistribution.css | 2 +- 8 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/components/game-distribution/GameDetailPage.tsx b/src/components/game-distribution/GameDetailPage.tsx index 1a167edd3..ca0b01579 100644 --- a/src/components/game-distribution/GameDetailPage.tsx +++ b/src/components/game-distribution/GameDetailPage.tsx @@ -751,7 +751,7 @@ function GameDetailContent({ if (isMobileViewport) { setIsForkPanelOpen(true); setForkLaunchNotice( - '当前设备无法直接唤起客户端,请用下面的作品 ID 或作品链接在客户端里 Fork。', + '当前设备无法直接唤起客户端,请用下面的作品 ID 或作品链接在客户端里开始共创。', ); return; } @@ -761,7 +761,7 @@ function GameDetailContent({ const handOffToClient = () => { clearForkLaunch(); setForkLaunchNotice( - '已唤起陶泥儿客户端。若没有自动打开,请再点一次「Fork 这个作品」手动获取作品 ID。', + '已唤起陶泥儿客户端。若没有自动打开,请再点一次「开始共创」手动获取作品 ID。', ); }; forkLaunchHandlersRef.current = handOffToClient; @@ -982,7 +982,7 @@ function GameDetailContent({ ) : null} {publiclyVisible ? ( /* - 主操作「一键 Fork 共创」(文档口径):**始终显示**。 + 主操作「一键开始共创」(文档口径):**始终显示**。 - 授权为「禁止共创」时置灰(aria-disabled + 视觉弱化)并在悬停/点击给出原因, 不再静默隐藏; - 未登录按仓库既有登录门禁:入口照常出现,点击走登录弹窗; @@ -1009,13 +1009,13 @@ function GameDetailContent({ } title={ forkAuthorization === 'forbidden' - ? '作者未开放共创授权(禁止共创),无法 Fork 这部作品' + ? '作者未开放共创授权(禁止共创),无法开始共创这部作品' : undefined } onClick={() => { if (forkAuthorization === 'forbidden') { setForkLaunchNotice( - '作者未开放共创授权(禁止共创),无法 Fork 这部作品。', + '作者未开放共创授权(禁止共创),无法开始共创这部作品。', ); return; } @@ -1026,7 +1026,7 @@ function GameDetailContent({ handleForkLaunch(); }} > - {isAuthenticated ? '一键 Fork 共创' : '登录后一键 Fork'} + {isAuthenticated ? '一键开始共创' : '登录后一键开始共创'} {(isAuthenticated || forkAuthorization === 'forbidden') && forkLaunchNotice ? ( @@ -1068,8 +1068,7 @@ function GameDetailContent({ /> - Fork - 会把这部作品的整个工程(含素材与文档)复制到你的项目里继续改造; + 开始共创会把这部作品的整个工程(含素材与文档)复制到你的项目里继续改造; 复制后对他人可见,且不可撤销。 {forkIdCopyState === 'copied' ? ( diff --git a/src/components/game-distribution/GameDistributionPages.test.tsx b/src/components/game-distribution/GameDistributionPages.test.tsx index c717b8026..d7e555eb3 100644 --- a/src/components/game-distribution/GameDistributionPages.test.tsx +++ b/src/components/game-distribution/GameDistributionPages.test.tsx @@ -118,7 +118,7 @@ vi.mock('../../services/clipboard', () => ({ copyTextToClipboard: copyTextToClipboardMock, })); -/** Fork 唤起:只打桩「发出唤起」这一动作,协议常量仍用真实值(测试按同一阈值推进定时器)。 */ +/** 开始共创唤起:只打桩「发出唤起」这一动作,协议常量仍用真实值(测试按同一阈值推进定时器)。 */ const openForkProtocolMock = vi.hoisted(() => vi.fn()); vi.mock('./gameForkProtocol', async (original) => ({ @@ -511,7 +511,7 @@ describe('GameDetailPage', () => { expect(await screen.findByText('暂未发布可玩版本')).toBeTruthy(); }); - it('展示共创授权、代际与溯源信息,并给出 Fork 入口', async () => { + it('展示共创授权、代际与溯源信息,并给出「一键开始共创」入口', async () => { getGameMock.mockResolvedValueOnce( createGame({ forkAuthorization: 'nonCommercial', @@ -534,8 +534,8 @@ describe('GameDetailPage', () => { expect(screen.getByText('2 代三创 · 改编自父代《原初之光》')).toBeTruthy(); expect(screen.getByText('父代作者 原作者')).toBeTruthy(); expect(screen.getByText('累计衍生作品 3')).toBeTruthy(); - // 已公开 + 授权开放 → 「一键 Fork 共创」入口出现(M1 时它还不存在,这里钉住新契约)。 - expect(screen.getByRole('button', { name: '一键 Fork 共创' })).toBeTruthy(); + // 已公开 + 授权开放 → 「一键开始共创」入口出现(M1 时它还不存在,这里钉住新契约)。 + expect(screen.getByRole('button', { name: '一键开始共创' })).toBeTruthy(); }); it('旧数据缺少授权与血缘字段时只按禁止共创渲染', async () => { @@ -1879,7 +1879,7 @@ describe('GameLineagePage', () => { }); }); -describe('GameDetailPage Fork 入口', () => { +describe('GameDetailPage 开始共创入口', () => { it('已公开且授权开放时出现入口;唤起没被接手时降级给出可复制的作品 ID 与指引', async () => { getGameMock.mockResolvedValueOnce( createGame({ forkAuthorization: 'nonCommercial' }), @@ -1888,7 +1888,7 @@ describe('GameDetailPage Fork 入口', () => { , ); - const entry = await screen.findByRole('button', { name: '一键 Fork 共创' }); + const entry = await screen.findByRole('button', { name: '一键开始共创' }); // 未展开前不露出获取方式。 expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull(); @@ -1912,7 +1912,7 @@ describe('GameDetailPage Fork 入口', () => { ).toBeTruthy(); expect(screen.getByText('game-1')).toBeTruthy(); expect(screen.getByText(/\/games\/detail\?id=game-1$/u)).toBeTruthy(); - // 冻结口径:Fork = 复制整个工程继续改造;只保留一句话告知(对他人可见 + 不可撤销)。 + // 冻结口径:开始共创 = 复制整个工程继续改造;只保留一句话告知(对他人可见 + 不可撤销)。 expect( screen.getByText(/整个工程(含素材与文档)复制到你的项目里继续改造/u), ).toBeTruthy(); @@ -1940,7 +1940,7 @@ describe('GameDetailPage Fork 入口', () => { , ); - const entry = await screen.findByRole('button', { name: '一键 Fork 共创' }); + const entry = await screen.findByRole('button', { name: '一键开始共创' }); vi.useFakeTimers(); fireEvent.click(entry); expect(openForkProtocolMock).toHaveBeenCalledWith('game-1'); @@ -1970,7 +1970,7 @@ describe('GameDetailPage Fork 入口', () => { ); fireEvent.click( - await screen.findByRole('button', { name: '一键 Fork 共创' }), + await screen.findByRole('button', { name: '一键开始共创' }), ); expect(openForkProtocolMock).not.toHaveBeenCalled(); @@ -1988,7 +1988,7 @@ describe('GameDetailPage Fork 入口', () => { ); const blocked = await screen.findByRole('button', { - name: '一键 Fork 共创', + name: '一键开始共创', }); // 不静默隐藏:按钮在,但是 aria-disabled + 有原因(悬停 title)。 expect(blocked.getAttribute('aria-disabled')).toBe('true'); @@ -1997,13 +1997,13 @@ describe('GameDetailPage Fork 入口', () => { fireEvent.click(blocked); expect( await screen.findByText( - /作者未开放共创授权(禁止共创),无法 Fork 这部作品。/u, + /作者未开放共创授权(禁止共创),无法开始共创这部作品。/u, ), ).toBeTruthy(); expect(openForkProtocolMock).not.toHaveBeenCalled(); }); - it('未公开作品不出现 Fork 入口', async () => { + it('未公开作品不出现开始共创入口', async () => { getMyGameMock.mockResolvedValueOnce({ ...createGame({ status: 'unpublished', forkAuthorization: 'full' }), latestVersion: null, @@ -2019,13 +2019,13 @@ describe('GameDetailPage Fork 入口', () => { ); await screen.findByText('作品状态'); - expect(screen.queryByRole('button', { name: '一键 Fork 共创' })).toBeNull(); + expect(screen.queryByRole('button', { name: '一键开始共创' })).toBeNull(); expect( - screen.queryByRole('button', { name: '登录后一键 Fork' }), + screen.queryByRole('button', { name: '登录后一键开始共创' }), ).toBeNull(); }); - it('未登录时入口显示「登录后一键 Fork」,点击走仓库既有登录门禁而不是静默隐藏', async () => { + it('未登录时入口显示「登录后一键开始共创」,点击走仓库既有登录门禁而不是静默隐藏', async () => { authUiMock.value.user = null; authUiMock.value.canAccessProtectedData = false; getGameMock.mockResolvedValueOnce( @@ -2035,7 +2035,9 @@ describe('GameDetailPage Fork 入口', () => { , ); - const gate = await screen.findByRole('button', { name: '登录后一键 Fork' }); + const gate = await screen.findByRole('button', { + name: '登录后一键开始共创', + }); expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull(); fireEvent.click(gate); @@ -3198,7 +3200,7 @@ describe('GameLineagePage 可视化与交互', () => { expect(outlineCss).toMatch( /\.lineage-outline__action--co-create \{[\s\S]{0,200}?--platform-button-primary-solid/u, ); - // 全树的 Fork 旧文案在两种形态里都已不存在(信息面板的「Fork 此版本」不在树节点上,另见报告)。 + // 全树的 Fork 旧文案在两种形态里都已不存在(信息面板的「开始共创此版本」不在树节点上)。 expect(outline.textContent).not.toContain('Fork'); }); @@ -3473,7 +3475,7 @@ describe('GameLineagePage 可视化与交互', () => { }); expect(screen.getByText(/没有检测到陶泥儿客户端/u)).toBeTruthy(); fireEvent.click( - screen.getByRole('button', { name: /复制 game-leaf 去 Fork/u }), + screen.getByRole('button', { name: /复制 game-leaf 去开始共创/u }), ); await act(async () => {}); expect(copyTextToClipboardMock).toHaveBeenCalledWith('game-leaf'); @@ -3508,7 +3510,7 @@ describe('GameLineagePage 可视化与交互', () => { expect(stubCard.getAttribute('tabindex')).toBe('0'); expect(stubCard.getAttribute('role')).toBeNull(); expect(stubCard.getAttribute('aria-disabled')).toBe('true'); - // 被删作品只以「不可用」形式出现:没有标题、没有作者名、也没有 Fork/对比动作。 + // 被删作品只以「不可用」形式出现:没有标题、没有作者名、也没有开始共创/对比动作。 expect(document.querySelector('.game-lineage-node--stub')).toBeTruthy(); expect(screen.queryByText(/作者 game-deleted/u)).toBeNull(); expect(within(stubCard).queryByRole('button')).toBeNull(); @@ -3694,7 +3696,7 @@ describe('GameLineagePage 筛选器 / 主干线 / 信息面板(文档对齐) ).toBe(2); }); - it('选中节点后右侧信息面板给出简介/授权与「前往体验 / Fork 此版本」', async () => { + it('选中节点后右侧信息面板给出简介/授权与「前往体验 / 开始共创此版本」', async () => { getGameLineageMock.mockResolvedValueOnce(tree); getGameMock.mockResolvedValueOnce( createGame({ @@ -3735,7 +3737,14 @@ describe('GameLineagePage 筛选器 / 主干线 / 信息面板(文档对齐) fireEvent.click(within(panel).getByRole('button', { name: '前往体验' })); expect(onOpenDetail).toHaveBeenCalledWith('hot'); - fireEvent.click(within(panel).getByRole('button', { name: 'Fork 此版本' })); + expect( + within(panel).getByRole('button', { name: '开始共创此版本' }), + ).toBeTruthy(); + // 面板内不得再把该动作叫 Fork(与按钮可见名一致)。 + expect(panel.textContent).not.toContain('Fork'); + fireEvent.click( + within(panel).getByRole('button', { name: '开始共创此版本' }), + ); expect(openForkProtocolMock).toHaveBeenCalledWith('hot'); }); @@ -3846,8 +3855,10 @@ describe('GameLineagePage 筛选器 / 主干线 / 信息面板(文档对齐) within(sheet).getByRole('button', { name: '前往体验' }), ).toBeTruthy(); expect( - within(sheet).getByRole('button', { name: 'Fork 此版本' }), + within(sheet).getByRole('button', { name: '开始共创此版本' }), ).toBeTruthy(); + // 窄屏 bottom sheet 与桌面浮层同一组件:同样不得再出现 Fork 动作名。 + expect(sheet.textContent).not.toContain('Fork'); // 遮罩点击关闭。 fireEvent.click(screen.getByLabelText('关闭信息面板')); await waitFor(() => @@ -4210,8 +4221,8 @@ describe('GameDetailPage 共创模块(文档对齐)', () => { expect(screen.getByText('允许非商用共创')).toBeTruthy(); expect(screen.getByText('累计衍生作品 3')).toBeTruthy(); expect(screen.getByText('2 代三创 · 改编自父代《原初之光》')).toBeTruthy(); - // 主操作按文档叫「一键 Fork 共创」。 - expect(screen.getByRole('button', { name: '一键 Fork 共创' })).toBeTruthy(); + // 主操作按文档叫「一键开始共创」。 + expect(screen.getByRole('button', { name: '一键开始共创' })).toBeTruthy(); }); it('累计创作世代数字段缺失时不渲染该概览项(不拿别的数字凑数)', async () => { @@ -4265,7 +4276,7 @@ describe('GameDetailPage 共创模块(文档对齐)', () => { within(card).getByRole('button', { name: '查看创作族谱' }), ).toBeTruthy(); expect( - within(card).getByRole('button', { name: '一键 Fork 共创' }), + within(card).getByRole('button', { name: '一键开始共创' }), ).toBeTruthy(); // ② 图在**正文之后**:`.game-detail-page` 的直接子级,且排在正文容器之后。 const page = document.querySelector('.game-detail-page') as HTMLElement; diff --git a/src/components/game-distribution/GameLineagePage.tsx b/src/components/game-distribution/GameLineagePage.tsx index 015daf1b5..569fb5831 100644 --- a/src/components/game-distribution/GameLineagePage.tsx +++ b/src/components/game-distribution/GameLineagePage.tsx @@ -88,7 +88,7 @@ type ViewportState = { scale: number; offsetX: number; offsetY: number }; /** * 一个树节点:真实 HTML 卡片(可聚焦、可点击、有可访问名称),不是 SVG 矩形。 * - * 交互分工:主点击(含键盘 Enter/Space)进作品详情;次要动作 Fork 放在卡片内部的独立 + * 交互分工:主点击(含键盘 Enter/Space)进作品详情;次要动作「开始共创」放在卡片内部的独立 * ` diff --git a/src/components/game-distribution/LineageOutlineList.tsx b/src/components/game-distribution/LineageOutlineList.tsx index d857ee763..686b346f6 100644 --- a/src/components/game-distribution/LineageOutlineList.tsx +++ b/src/components/game-distribution/LineageOutlineList.tsx @@ -12,7 +12,7 @@ import type { * 节点内容复用与画布/血缘链同一个 `LineageNodeContent`(只有一套封面色块与省略规则)。 * * 语义与画布一致:点击行 = **选中**(高亮祖先链 + 面包屑),行内「打开」= 进详情, - * 「Fork」= 唤起/降级链路;主干链的导线加粗并用主题色。 + * 「开始共创」= 唤起/降级链路;主干链的导线加粗并用主题色。 */ export function LineageOutlineList({ nodes, diff --git a/src/components/game-distribution/MyGamesPage.test.tsx b/src/components/game-distribution/MyGamesPage.test.tsx index ee001b650..5eaa91bf5 100644 --- a/src/components/game-distribution/MyGamesPage.test.tsx +++ b/src/components/game-distribution/MyGamesPage.test.tsx @@ -801,7 +801,7 @@ test('衍生作品的授权入口改为只读展示(授权继承父作品, expect(screen.queryByRole('button', { name: '共创授权' })).toBeNull(); }); -test('分类标签按文档口径过滤,并给出总作品数 / 累计被 Fork 次数 / 世代数', async () => { +test('分类标签按文档口径过滤,并给出总作品数 / 累计被共创次数 / 世代数', async () => { vi.mocked(listMyGames).mockResolvedValue([ createGame({ id: 'g-original', @@ -833,9 +833,9 @@ test('分类标签按文档口径过滤,并给出总作品数 / 累计被 Fork renderPage(createAuthValue()); - // 数据概览:总作品数 + 累计被 Fork 次数(世代数字段缺失 → 该项不渲染)。 + // 数据概览:总作品数 + 累计被共创次数(世代数字段缺失 → 该项不渲染)。 expect(await screen.findByText('总作品数 3')).toBeTruthy(); - expect(screen.getByText('累计被 Fork 次数 3')).toBeTruthy(); + expect(screen.getByText('累计被共创次数 3')).toBeTruthy(); expect(screen.queryByText(/累计衍生世代数/u)).toBeNull(); // 卡片上的世代数与子衍生作品数量。 diff --git a/src/components/game-distribution/MyGamesPage.tsx b/src/components/game-distribution/MyGamesPage.tsx index 12aac0b08..8caca6051 100644 --- a/src/components/game-distribution/MyGamesPage.tsx +++ b/src/components/game-distribution/MyGamesPage.tsx @@ -716,7 +716,7 @@ export function MyGamesPage({
总作品数 {games.length} - 累计被 Fork 次数 {totalForkCount} + 累计被共创次数 {totalForkCount} {totalDerivedGenerations === null ? null : ( diff --git a/src/components/game-distribution/gameDistribution.css b/src/components/game-distribution/gameDistribution.css index b8921e30d..e0162cee1 100644 --- a/src/components/game-distribution/gameDistribution.css +++ b/src/components/game-distribution/gameDistribution.css @@ -2448,7 +2448,7 @@ /* * 移动端「大纲式缩进树」(默认形态):全宽行 + 左缩进 + 肘形导线,**无横向滚动**。 - * 每行 ≥44px 触控目标;点击行=选中,行内「打开 / Fork」是次要动作。 + * 每行 ≥44px 触控目标;点击行=选中,行内「打开 / 开始共创」是次要动作。 */ .lineage-outline { display: flex;