AGC 打包并发布不再按必填项变灰,改为前端校验给可读错误
- 「打包并发布」只在打包 / 上传进行中禁用,不再因为名称或简介为空而变灰 - 文案必填项在打包前就地校验,封面与在途上传在打包成功后校验,错误都留在表单里 - 简介兜底改用 ||,项目 goal 为空时不再留下空简介把唯一入口卡死 - 用户一改名称 / 简介即清除上一次的校验错误 - 补「项目还没起名时按钮仍可点并给出必填提示」的回归用例
This commit is contained in:
+7
-4
@@ -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"
|
||||
|
||||
+37
-10
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user