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