From 163564a811a6c6bf79d93f7da393792fe90482fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 6 Oct 2026 18:02:08 +0800 Subject: [PATCH] =?UTF-8?q?AGC=20=E6=89=93=E5=8C=85=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E4=B8=8D=E5=86=8D=E6=8C=89=E5=BF=85=E5=A1=AB=E9=A1=B9?= =?UTF-8?q?=E5=8F=98=E7=81=B0=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E7=BB=99=E5=8F=AF=E8=AF=BB=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 「打包并发布」只在打包 / 上传进行中禁用,不再因为名称或简介为空而变灰 - 文案必填项在打包前就地校验,封面与在途上传在打包成功后校验,错误都留在表单里 - 简介兜底改用 ||,项目 goal 为空时不再留下空简介把唯一入口卡死 - 用户一改名称 / 简介即清除上一次的校验错误 - 补「项目还没起名时按钮仍可点并给出必填提示」的回归用例 --- .../GameDistributionPublishFormView.tsx | 11 +++-- .../useGameDistributionPublishForm.ts | 47 +++++++++++++++---- .../tests/gamePublishFeedback.test.tsx | 20 ++++++++ 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishFormView.tsx b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishFormView.tsx index 680203c86..077341612 100644 --- a/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishFormView.tsx +++ b/apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishFormView.tsx @@ -40,6 +40,7 @@ export function GameDistributionPublishFormView({ setCategory, busy, error, + clearError, result, cover, setCover, @@ -77,7 +78,10 @@ export function GameDistributionPublishFormView({ setTitle(event.target.value)} + onChange={(event) => { + setTitle(event.target.value); + clearError(); + }} /> @@ -389,9 +394,7 @@ export function GameDistributionPublishFormView({ generatingCover || screenshotsUploading || Boolean(uploadingLabel) || - Boolean(packaging) || - !title.trim() || - !summary.trim() + Boolean(packaging) } onClick={() => void handlePackageAndPublish()} className="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-[#c7653d] bg-[#c7653d] px-6 py-4 text-lg font-bold tracking-wide text-white shadow-md transition enabled:hover:bg-[#b4562f] disabled:cursor-not-allowed disabled:opacity-45" diff --git a/apps/ai-game-creator-shell/src/components/game-distribution/useGameDistributionPublishForm.ts b/apps/ai-game-creator-shell/src/components/game-distribution/useGameDistributionPublishForm.ts index 2c9090b31..aa0387c38 100644 --- a/apps/ai-game-creator-shell/src/components/game-distribution/useGameDistributionPublishForm.ts +++ b/apps/ai-game-creator-shell/src/components/game-distribution/useGameDistributionPublishForm.ts @@ -218,10 +218,8 @@ export function useGameDistributionPublishForm({ const currentManifest = manifestRef.current; if (!currentManifest) return; const fallbackSummary = ( - currentManifest.goal ?? '由陶泥儿创作的可在线游玩游戏' - ) - .trim() - .slice(0, 120); + currentManifest.goal?.trim() || '由陶泥儿创作的可在线游玩游戏' + ).slice(0, 120); setTitle(currentManifest.name.trim()); setSummary(fallbackSummary); setCategory('其他'); @@ -491,6 +489,27 @@ export function useGameDistributionPublishForm({ if (screenshotInputRef.current) screenshotInputRef.current.value = ''; } + /** + * 提交前的本地门禁。按钮不因为表单没填好就变灰(同小红书「打包」):点下去当场校验、 + * 当场给可读错误。封面与在途上传都在这一刻判定,不再把「能不能点」的判断推给用户。 + */ + function resolvePublishFormError(): string { + if (!title.trim()) return '请先填写游戏名称'; + if (!summary.trim()) return '请先填写一句话简介'; + if ( + uploadingLabel || + screenshots.some((item) => item.status === 'uploading') + ) { + return '素材还在上传中,请稍候再发布'; + } + return ''; + } + + /** 用户一改字段就把上一次的校验错误收起来,别让已经修好的字段继续挂着红字。 */ + function clearError() { + setError(''); + } + /** 真正提交发布:包由调用方传入,不依赖 prop 更新时机。 */ async function submitPackage(pkg: LocalProjectExportPackageResult) { if (!projectPath.trim()) { @@ -510,11 +529,9 @@ export function useGameDistributionPublishForm({ setError('请先选择游戏封面(JPG/PNG/WebP)'); return; } - if ( - uploadingLabel || - screenshots.some((item) => item.status === 'uploading') - ) { - setError('素材还在上传中,请稍候再发布'); + const formError = resolvePublishFormError(); + if (formError) { + setError(formError); return; } setBusy(true); @@ -565,9 +582,18 @@ export function useGameDistributionPublishForm({ * 内联主行动:一次点击完成「打包 → 发布」。 * * 顺序沿用旧入口的「点击即构建打包」:先按需构建试玩包(进度 / 阻断由父层弹窗承载), - * 打包成功后再走提交前的表单门禁(封面、必填项、在途上传),失败只把错误留在表单里。 + * 打包成功后再走提交前的表单门禁,失败只把错误留在表单里。文案必填项在打包之前就拦, + * 免得为了一份注定提交不了的资料白跑一次构建;封面 / 在途上传留到打包成功后判定。 */ async function handlePackageAndPublish() { + if (!title.trim()) { + setError('请先填写游戏名称'); + return; + } + if (!summary.trim()) { + setError('请先填写一句话简介'); + return; + } let pkg = packageResult; if (!pkg) { if (!onEnsurePackage) { @@ -599,6 +625,7 @@ export function useGameDistributionPublishForm({ setCategory, busy, error, + clearError, result, cover, setCover, diff --git a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx b/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx index f7192fba6..350e5a482 100644 --- a/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx +++ b/apps/ai-game-creator-shell/tests/gamePublishFeedback.test.tsx @@ -295,4 +295,24 @@ describe('客户端发布入口的可见反馈', () => { expect(screen.queryByText('project.export_package')).toBeNull(); expect(screen.queryByText('导出试玩包需要确认,确认后继续。')).toBeNull(); }); + + it('项目还没起名时「打包并发布」不变灰,点击后给出可读的必填提示', async () => { + const exportPackage = vi.fn(createExportPackageResult); + const manifest: GameCreationAppManifest = { + ...createFixtureManifest(), + name: '', + goal: '', + }; + installTauri({ exportPackage, manifest }); + renderPublishProject(manifest); + + const publish = await openTaonierPublishEntry(); + expect(publish).toHaveProperty('disabled', false); + + fireEvent.click(publish); + + // 按钮始终可点,校验在前端当场给出;必填文案不通过时不为一次注定失败的发布白跑构建。 + expect(await screen.findByText('请先填写游戏名称')).not.toBeNull(); + expect(exportPackage).not.toHaveBeenCalled(); + }); });