fix(游戏共创): 工程源码公开改成作者的主动知情确认,文案如实告知外发范围
背景(对抗性复核 D.7):工程源包的拒绝规则是**黑名单**,作者的 `docs/`、`*.pdf`、`notes.txt`、 截图、设计稿、会议记录等不在任何拒绝集内,会被原样外发。也就是说「授权非禁止 + 上传工程源码」 = **整个项目目录(除少数排除项)对任何登录用户公开**。原来那句「同时公开工程源码……取消勾选则 只能被试玩」是免责声明式措辞,不足以构成知情同意。 文案(最终措辞): - 授权三态说明统一口径:「授权非「禁止共创」即允许他人改编(**没有单独的源码可见性开关**); 是否随本次发布把工程源码一起交出去,由下面的确认决定。上架后授权只能单向提升,不能撤销。」 - 确认项(仅授权非「禁止共创」且首次发布时出现): 「我已确认项目里没有不愿公开的内容,同意随本次发布公开**整个项目目录**:源码、素材与其中 其它文件(含文档、截图、设计稿、笔记等)都会对任何登录用户公开;只会自动排除依赖目录、 版本库、构建产物与常见凭据文件。一经发布**不可撤销**:即使之后把授权降级,也无法收回别人 已经下载的内容。」 - 紧随其后写清不勾选的后果:「不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到 已构建成品试玩与参考,不能直接重新发布。」 交互选择:**默认不勾选 + 不勾选就不上传**(不是禁用发布按钮)。 - 选它的理由:① 它把第 3 点做成了真正的「主动确认动作」;② 发布本身不被阻断——工程包缺失本来 就不影响发布(服务端与原生链路都按「可选资产」设计),禁用按钮反而会把「禁止共创 / 只想先发 出去」的作者一起挡住;③ 勾选状态只作用于本次版本上传,不引入平台侧的独立开关,授权语义不变。 - 注意:这一条**取代**了早先「授权非禁止时默认帮作者上传」的默认值——知情同意优先于默认帮忙; 若要把默认值改回勾选,只需把 `projectPackageConfirmed` 初值改成 true 并翻转对应断言。 测试:改写两条面板用例——授权开放后确认项出现且**默认未勾选**、九处告知文案逐条断言(整个项目 目录 / 含文档截图设计稿笔记 / 只自动排除四类 / 不可撤销 / 无法收回已下载内容 / 不勾选则不上传 / 没有单独的源码可见性开关)、未确认时显式传 `includeProjectBundle: false`(不上传但照常发布)、 确认后不带该字段(由原生按授权执行上传)。
This commit is contained in:
+25
-14
@@ -254,12 +254,13 @@ export function GameDistributionPublishPanel({
|
||||
GAME_DISTRIBUTION_DEFAULT_FORK_AUTHORIZATION,
|
||||
);
|
||||
/**
|
||||
* 是否随版本公开工程源包(默认勾选)。
|
||||
* 作者是否已**主动确认**随本次发布公开整个项目目录(默认不勾选)。
|
||||
*
|
||||
* 口径(与产品一致):授权非「禁止共创」时默认帮作者把工程源码一起传上去,他人即可取走;
|
||||
* 作者可以取消——取消后作品仍照常发布,只是退化为「只能被产物级改编(试玩 + 参考)」。
|
||||
* 产品口径:授权非「禁止共创」即允许他人改编,但工程源码的拒绝规则是黑名单——作者的
|
||||
* `docs/`、截图、设计稿、笔记等不在任何拒绝集内,会随包原样外发。所以这里不做「默认帮作者
|
||||
* 上传」,而是要求作者确认后才能上传;不确认则作品照常发布,只是退化为「只能被产物级改编」。
|
||||
*/
|
||||
const [includeProjectBundle, setIncludeProjectBundle] = useState(true);
|
||||
const [projectBundleConfirmed, setProjectBundleConfirmed] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [result, setResult] = useState<GameDistributionPublishResult | null>(
|
||||
@@ -772,9 +773,8 @@ export function GameDistributionPublishPanel({
|
||||
publicationState === 'update'
|
||||
? (publicationBinding?.revision ?? null)
|
||||
: null,
|
||||
// 只在「授权已开放但作者取消勾选」时显式关掉工程源包上传;其余情况由原生按
|
||||
// 「授权非禁止即上传」判断,避免面板与原生各写一套默认值。
|
||||
...(forkAuthorization !== 'forbidden' && !includeProjectBundle
|
||||
// 只有作者主动确认后才让原生上传工程源码;未确认时显式关掉,作品照常发布。
|
||||
...(forkAuthorization !== 'forbidden' && !projectBundleConfirmed
|
||||
? { includeProjectBundle: false }
|
||||
: {}),
|
||||
});
|
||||
@@ -971,27 +971,38 @@ export function GameDistributionPublishPanel({
|
||||
<p className="game-distribution-publish-panel__fork-hint">
|
||||
{forkAuthorizationLocked
|
||||
? '共创授权只在首次发布时生效;上架后只能在网页端「我的作品」单向提升。'
|
||||
: '开启后可被他人复刻改编(仅能拿到已构建成品,用于试玩与参考);上架后只能单向提升,不能撤销。'}
|
||||
: '授权非「禁止共创」即允许他人改编(没有单独的源码可见性开关);是否随本次发布把工程源码一起交出去,由下面的确认决定。上架后授权只能单向提升,不能撤销。'}
|
||||
</p>
|
||||
{/*
|
||||
工程源码是否随版本公开:授权非「禁止共创」时才有意义(禁止共创谁也取不走)。
|
||||
默认勾选,且必须把「不可撤销」说清楚——源码一旦公开就无法收回。
|
||||
工程源码外发是**知情同意**:工程源包的拒绝规则是黑名单,作者的 docs/、截图、
|
||||
设计稿、笔记等不在任何拒绝集内,会随包一起对任何登录用户公开。因此这里不是
|
||||
一句免责声明,而是必须由作者主动确认的动作(默认不勾选,不勾选就不上传源码)。
|
||||
*/}
|
||||
{forkAuthorization !== 'forbidden' && !forkAuthorizationLocked ? (
|
||||
<label className="game-distribution-publish-panel__bundle-consent">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={includeProjectBundle}
|
||||
checked={projectBundleConfirmed}
|
||||
onChange={(event) =>
|
||||
setIncludeProjectBundle(event.target.checked)
|
||||
setProjectBundleConfirmed(event.target.checked)
|
||||
}
|
||||
/>
|
||||
<span>
|
||||
同时公开工程源码:允许他人下载并使用你的工程源文件(含素材),
|
||||
且不可撤销;取消勾选则本作品只能被他人试玩与参考(不能直接重新发布)
|
||||
我已确认项目里没有不愿公开的内容,同意随本次发布公开
|
||||
<strong>整个项目目录</strong>
|
||||
:源码、素材与其中其它文件(含文档、截图、设计稿、笔记等)都会对任何登录用户公开;
|
||||
只会自动排除依赖目录、版本库、构建产物与常见凭据文件。一经发布
|
||||
<strong>不可撤销</strong>
|
||||
:即使之后把授权降级,也无法收回别人已经下载的内容。
|
||||
</span>
|
||||
</label>
|
||||
) : null}
|
||||
{forkAuthorization !== 'forbidden' && !forkAuthorizationLocked ? (
|
||||
<p className="game-distribution-publish-panel__fork-hint">
|
||||
不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到已构建成品试玩与
|
||||
参考,不能直接重新发布。
|
||||
</p>
|
||||
) : null}
|
||||
</fieldset>
|
||||
<label>
|
||||
<span className="game-distribution-publish-panel__version-label">
|
||||
|
||||
@@ -382,7 +382,7 @@ describe('GameDistributionPublishPanel', () => {
|
||||
expect(args?.metadata?.forkAuthorization).toBe('full');
|
||||
});
|
||||
|
||||
test('授权开放后默认勾选公开工程源码,勾选时不重复传默认值', async () => {
|
||||
test('授权开放后才出现源码公开确认,默认不勾选并如实告知后果', async () => {
|
||||
installTauriInvoke(async () => undefined);
|
||||
vi.mocked(publishLocalProjectGame).mockResolvedValue({
|
||||
gameId: 'game_1',
|
||||
@@ -395,31 +395,41 @@ describe('GameDistributionPublishPanel', () => {
|
||||
});
|
||||
renderPanel();
|
||||
|
||||
// 默认「禁止共创」时源码不会被任何人取走,因此不出现这个勾选。
|
||||
// 默认「禁止共创」时源码不会被任何人取走,因此不出现这个确认。
|
||||
expect(screen.queryByRole('checkbox')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('radio', { name: '允许非商用共创' }));
|
||||
const consent = await screen.findByRole('checkbox');
|
||||
// 默认勾选:授权非禁止时默认帮作者把工程源码一起传上去。
|
||||
expect(consent).toHaveProperty('checked', true);
|
||||
// 文案必须把「不可撤销」与「含素材」说清楚。
|
||||
// 知情同意:必须由作者主动确认,默认不勾选。
|
||||
expect(consent).toHaveProperty('checked', false);
|
||||
// 告知必须如实:整个项目目录会外发、只自动排除少数几类、且不可撤销。
|
||||
expect(screen.getByText(/我已确认项目里没有不愿公开的内容/u)).not.toBeNull();
|
||||
expect(screen.getByText(/整个项目目录/u)).not.toBeNull();
|
||||
expect(screen.getByText(/源码、素材与其中其它文件/u)).not.toBeNull();
|
||||
expect(screen.getByText(/只会自动排除依赖目录、版本库、构建产物与常见凭据文件/u)).not.toBeNull();
|
||||
expect(screen.getByText(/不可撤销/u)).not.toBeNull();
|
||||
expect(screen.getByText(/无法收回别人已经下载的内容/u)).not.toBeNull();
|
||||
// 不勾选的后果也要在发布前就说清楚。
|
||||
expect(
|
||||
screen.getByText(/允许他人下载并使用你的工程源文件(含素材)/u),
|
||||
screen.getByText(/不勾选则本次不上传工程源码/u),
|
||||
).not.toBeNull();
|
||||
// 授权说明与确认动作同一口径:没有独立的源码可见性开关。
|
||||
expect(
|
||||
screen.getByText(/没有单独的源码可见性开关/u),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText(/且不可撤销/u)).not.toBeNull();
|
||||
|
||||
// 未确认 → 明确告诉原生不要把工程源码交出去(作品照常发布)。
|
||||
await selectCover();
|
||||
fireEvent.click(screen.getByRole('button', { name: '发布游戏' }));
|
||||
await waitFor(() =>
|
||||
expect(publishLocalProjectGame).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
// 勾选状态留给原生按默认值判断:这里不带 includeProjectBundle。
|
||||
expect(
|
||||
vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0],
|
||||
).not.toHaveProperty('includeProjectBundle');
|
||||
).toMatchObject({ includeProjectBundle: false });
|
||||
});
|
||||
|
||||
test('取消勾选公开工程源码时显式传 includeProjectBundle=false', async () => {
|
||||
test('确认公开整个项目目录后才上传工程源码', async () => {
|
||||
installTauriInvoke(async () => undefined);
|
||||
vi.mocked(publishLocalProjectGame).mockResolvedValue({
|
||||
gameId: 'game_1',
|
||||
@@ -435,7 +445,7 @@ describe('GameDistributionPublishPanel', () => {
|
||||
fireEvent.click(screen.getByRole('radio', { name: '允许全开放共创' }));
|
||||
fireEvent.click(await screen.findByRole('checkbox'));
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', false),
|
||||
expect(screen.queryByRole('checkbox')).toHaveProperty('checked', true),
|
||||
);
|
||||
|
||||
await selectCover();
|
||||
@@ -443,10 +453,10 @@ describe('GameDistributionPublishPanel', () => {
|
||||
await waitFor(() =>
|
||||
expect(publishLocalProjectGame).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
// 显式 false:源码不外发,作品退化为产物级改编。
|
||||
// 已确认时不带 includeProjectBundle:由原生按「授权非禁止」执行上传。
|
||||
expect(
|
||||
vi.mocked(publishLocalProjectGame).mock.calls[0]?.[0],
|
||||
).toMatchObject({ includeProjectBundle: false });
|
||||
).not.toHaveProperty('includeProjectBundle');
|
||||
});
|
||||
|
||||
test('工程源包未上传时成功页如实提示只能产物级改编', async () => {
|
||||
|
||||
Reference in New Issue
Block a user