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(); + }); });