From b6fe63b5513544dad3c28bd2456ebb78fa786538 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Tue, 6 Oct 2026 00:48:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=88=E6=94=B6=E6=95=9B=EF=BC=9A?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E5=90=8D=E7=BB=9F=E4=B8=80=20Fork=EF=BC=8C?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E3=80=8C=E5=8F=AF=E7=8E=A9=E5=8F=82=E8=80=83?= =?UTF-8?q?=20+=20=E7=B4=A0=E6=9D=90=20/=20=E4=B8=8D=E8=83=BD=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=8F=91=E5=B8=83=E3=80=8D=E9=82=A3=E5=A5=97=E8=A1=A8?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 动作名统一:「改造这个作品」→「Fork 这个作品」,「登录后可改造」→「登录后可 Fork」 - 面板指引改为「在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 ID 或作品链接。」 - 对外口径统一为「Fork = 把整个工程复制一份到你的项目里继续改造(与 git fork 同理)」,并保留一句话必要告知:整个工程(含素材与文档)复制后对他人可见,且不可撤销 - 删除「成品包只能可玩参考 + 素材」「不能直接再次发布」「参考产物在 reference/<作品ID>/」等展开说明(冻结口径 1) - 测试同步:断言新文案与「不可撤销」告知,测试名与注释不再出现「改造」 --- .../game-distribution/GameDetailPage.tsx | 22 +++++----- .../GameDistributionPages.test.tsx | 40 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/components/game-distribution/GameDetailPage.tsx b/src/components/game-distribution/GameDetailPage.tsx index f37b35049..081adf130 100644 --- a/src/components/game-distribution/GameDetailPage.tsx +++ b/src/components/game-distribution/GameDetailPage.tsx @@ -344,8 +344,8 @@ function GameDetailContent({ ); const lineage = game.lineage ?? null; const forkCount = game.forkCount ?? 0; - // 「改造这个作品」的获取方式:网页不能唤起客户端(未注册 deep link),所以给出可复制的 - // 作品 ID 与完整作品链接,让用户到客户端的「从平台作品开始创作」里粘贴。 + // Fork 的获取方式:主路径是唤起客户端(见下方 handleForkLaunch),失败才降级为 + // 「复制作品 ID / 链接 → 到客户端粘贴」。 const [isForkPanelOpen, setIsForkPanelOpen] = useState(false); const { copyState: forkIdCopyState, copyText: copyForkId } = useCopyFeedback(); const { copyState: forkLinkCopyState, copyText: copyForkLink } = @@ -445,10 +445,9 @@ function GameDetailContent({ ) : null} {canRemix ? ( /* - 「改造这个作品」:服务端取件通道要求 Bearer + 已公开 + 授权非禁止。 - 未登录按仓库既有登录门禁处理——入口照常出现(显示「登录后可改造」), + Fork 入口:服务端取件通道要求 Bearer + 已公开 + 授权非禁止。 + 未登录按仓库既有登录门禁处理——入口照常出现(显示「登录后可 Fork」), 点击走登录弹窗,而不是静默消失。 - 网页不能唤起客户端(未注册 deep link),所以展开的是「复制 ID/链接 → 到客户端粘贴」。 */ <> {isAuthenticated && isForkPanelOpen ? ( - 打开陶泥儿客户端 → 首页「从平台作品开始创作」→ - 粘贴下面的作品 ID 或作品链接。 + 在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 + ID 或作品链接。 作品 ID:{game.id} @@ -502,11 +501,8 @@ function GameDetailContent({ /> - 客户端会把成品包作为可玩参考与素材 - 放进新项目的 reference/{game.id}/{' '} - 目录;成品包里没有源码, - 不能直接再次发布 - ,请在客户端的新工程里改造后发布。 + Fork 会把这部作品的整个工程(含素材与文档)复制到你的项目里继续改造; + 复制后对他人可见,且不可撤销。 ) : null} diff --git a/src/components/game-distribution/GameDistributionPages.test.tsx b/src/components/game-distribution/GameDistributionPages.test.tsx index a0a87df52..cbeaa2647 100644 --- a/src/components/game-distribution/GameDistributionPages.test.tsx +++ b/src/components/game-distribution/GameDistributionPages.test.tsx @@ -344,7 +344,7 @@ describe('GameDetailPage', () => { expect(await screen.findByText('暂未发布可玩版本')).toBeTruthy(); }); - it('展示共创授权、代际与溯源信息,并给出改造入口', async () => { + it('展示共创授权、代际与溯源信息,并给出 Fork 入口', async () => { getGameMock.mockResolvedValueOnce( createGame({ forkAuthorization: 'nonCommercial', @@ -367,9 +367,9 @@ describe('GameDetailPage', () => { expect(screen.getByText('第 2 代作品 · 改编自《原初之光》')).toBeTruthy(); expect(screen.getByText('由 原作者 制作')).toBeTruthy(); expect(screen.getByText('已被改编 3 次')).toBeTruthy(); - // 已公开 + 授权开放 → 「改造这个作品」入口出现(M1 时它还不存在,这里钉住新契约)。 + // 已公开 + 授权开放 → 「Fork 这个作品」入口出现(M1 时它还不存在,这里钉住新契约)。 expect( - screen.getByRole('button', { name: '改造这个作品' }), + screen.getByRole('button', { name: 'Fork 这个作品' }), ).toBeTruthy(); }); @@ -1355,7 +1355,7 @@ describe('GameLineagePage', () => { }); }); -describe('GameDetailPage 改造入口', () => { +describe('GameDetailPage Fork 入口', () => { it('已公开且授权开放时出现入口,展开后给出可复制的作品 ID 与操作指引', async () => { getGameMock.mockResolvedValueOnce( createGame({ forkAuthorization: 'nonCommercial' }), @@ -1364,21 +1364,21 @@ describe('GameDetailPage 改造入口', () => { , ); - const entry = await screen.findByRole('button', { name: '改造这个作品' }); + const entry = await screen.findByRole('button', { name: 'Fork 这个作品' }); // 未展开前不露出获取方式。 - expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull(); + expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull(); fireEvent.click(entry); expect( screen.getByText( - '打开陶泥儿客户端 → 首页「从平台作品开始创作」→ 粘贴下面的作品 ID 或作品链接。', + '在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 ID 或作品链接。', ), ).toBeTruthy(); expect(screen.getByText('game-1')).toBeTruthy(); expect(screen.getByText(/\/games\/detail\?id=game-1$/u)).toBeTruthy(); - // 诚实口径:可玩参考 + 素材、参考产物目录、不能直接再次发布。 - expect(screen.getByText(/reference\/game-1\//u)).toBeTruthy(); - expect(screen.getByText(/不能直接再次发布/u)).toBeTruthy(); + // 冻结口径:Fork = 复制整个工程继续改造;只保留一句话告知(对他人可见 + 不可撤销)。 + expect(screen.getByText(/整个工程(含素材与文档)复制到你的项目里继续改造/u)).toBeTruthy(); + expect(screen.getByText(/复制后对他人可见,且不可撤销/u)).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: '复制作品 ID' })); await waitFor(() => @@ -1399,7 +1399,7 @@ describe('GameDetailPage 改造入口', () => { ).toBeTruthy(); }); - it('授权为禁止共创时不出现改造入口', async () => { + it('授权为禁止共创时不出现 Fork 入口', async () => { getGameMock.mockResolvedValueOnce( createGame({ forkAuthorization: 'forbidden' }), ); @@ -1408,11 +1408,11 @@ describe('GameDetailPage 改造入口', () => { ); await screen.findByRole('heading', { name: '星轨防线' }); - expect(screen.queryByRole('button', { name: '改造这个作品' })).toBeNull(); - expect(screen.queryByRole('button', { name: '登录后可改造' })).toBeNull(); + expect(screen.queryByRole('button', { name: 'Fork 这个作品' })).toBeNull(); + expect(screen.queryByRole('button', { name: '登录后可 Fork' })).toBeNull(); }); - it('未公开作品不出现改造入口', async () => { + it('未公开作品不出现 Fork 入口', async () => { getMyGameMock.mockResolvedValueOnce({ ...createGame({ status: 'unpublished', forkAuthorization: 'full' }), latestVersion: null, @@ -1428,11 +1428,11 @@ describe('GameDetailPage 改造入口', () => { ); await screen.findByText('作品状态'); - expect(screen.queryByRole('button', { name: '改造这个作品' })).toBeNull(); - expect(screen.queryByRole('button', { name: '登录后可改造' })).toBeNull(); + expect(screen.queryByRole('button', { name: 'Fork 这个作品' })).toBeNull(); + expect(screen.queryByRole('button', { name: '登录后可 Fork' })).toBeNull(); }); - it('未登录时入口显示「登录后可改造」,点击走仓库既有登录门禁而不是静默隐藏', async () => { + it('未登录时入口显示「登录后可 Fork」,点击走仓库既有登录门禁而不是静默隐藏', async () => { authUiMock.value.user = null; authUiMock.value.canAccessProtectedData = false; getGameMock.mockResolvedValueOnce( @@ -1442,12 +1442,12 @@ describe('GameDetailPage 改造入口', () => { , ); - const gate = await screen.findByRole('button', { name: '登录后可改造' }); - expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull(); + const gate = await screen.findByRole('button', { name: '登录后可 Fork' }); + expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull(); fireEvent.click(gate); expect(authUiMock.value.openLoginModal).toHaveBeenCalledTimes(1); // 未登录不得展开获取方式(也就不会给出取不到的说明)。 - expect(screen.queryByText(/打开陶泥儿客户端/u)).toBeNull(); + expect(screen.queryByText(/从平台作品开始创作/u)).toBeNull(); }); });