AGC 打包并发布不再按必填项变灰,改为前端校验给可读错误

- 「打包并发布」只在打包 / 上传进行中禁用,不再因为名称或简介为空而变灰

- 文案必填项在打包前就地校验,封面与在途上传在打包成功后校验,错误都留在表单里

- 简介兜底改用 ||,项目 goal 为空时不再留下空简介把唯一入口卡死

- 用户一改名称 / 简介即清除上一次的校验错误

- 补「项目还没起名时按钮仍可点并给出必填提示」的回归用例
This commit is contained in:
2026-10-06 18:02:08 +08:00
parent 779857342e
commit 163564a811
3 changed files with 64 additions and 14 deletions
@@ -40,6 +40,7 @@ export function GameDistributionPublishFormView({
setCategory,
busy,
error,
clearError,
result,
cover,
setCover,
@@ -77,7 +78,10 @@ export function GameDistributionPublishFormView({
<input
value={title}
maxLength={40}
onChange={(event) => setTitle(event.target.value)}
onChange={(event) => {
setTitle(event.target.value);
clearError();
}}
/>
</label>
<label>
@@ -89,6 +93,7 @@ export function GameDistributionPublishFormView({
onChange={(event) => {
summaryTouchedRef.current = true;
setSummary(event.target.value);
clearError();
}}
/>
</label>
@@ -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"
@@ -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,
@@ -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();
});
});