fix(product-create): 新建商品上传健壮性 — loading/失败提示/等齐再关抽屉

- 原 close() 在图片上传之前执行 + 上传无 catch → 失败静默,商品建了却无图
- 改:create 后用 Promise.all 等全部图传完,saving 态贯穿整个上传(抽屉不提前关,
  「创建中…」可见)
- 上传失败 → flash「图片上传失败 · 已保留可重试」,不关抽屉
- createdRef 暂存已建商品:重试只补传图,不重复建商品
- onCreate 失败也给提示(不再静默)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-20 19:03:30 +08:00
co-authored by Claude Opus 4.8
parent 2e880714b1
commit b8fc1a0749
@@ -48,6 +48,8 @@ export function ProductCreateDrawer({ open, close, onCreate, onUploadImage, onCr
const [toast, setToast] = useState<{ title: string; sub: string } | null>(null);
const imgInputRef = useRef<HTMLInputElement | null>(null);
const toastTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
// 已建商品句柄:onCreate 成功但图片上传失败时暂存,重试只补传图不重复建商品
const createdRef = useRef<Product | null>(null);
function flash(title: string, sub: string) {
if (toastTimer.current) clearTimeout(toastTimer.current);
@@ -68,6 +70,7 @@ export function ProductCreateDrawer({ open, close, onCreate, onUploadImage, onCr
setImages((list) => { list.forEach((item) => URL.revokeObjectURL(item.url)); return []; });
setDragOver(false);
setTitleError(false);
createdRef.current = null;
}
// 每次打开抽屉时复位表单(含商品库「继续创建商品」二次打开)
useEffect(() => { if (open) resetForm(); }, [open]);
@@ -132,23 +135,35 @@ export function ProductCreateDrawer({ open, close, onCreate, onUploadImage, onCr
setSaving(true);
try {
const files = images.map((item) => item.file);
const created = await onCreate({
// 若上次已建好商品(仅图片上传失败),本次重试只补传图,不重复建商品
let created = createdRef.current;
if (!created) {
created = (await onCreate({
title: name,
category: category || catOptions[0],
target_audience: target.trim() || undefined,
selling_points: finalBullets.map((item, index) => ({ title: item, detail: item, sort_order: index }))
});
close();
if (!created) return;
// 把抽屉里选的主图逐张随商品一起上传持久化(否则卡片/详情看不到图)
})) || null;
if (!created) { flash("商品创建失败", "请稍后重试"); return; } // 不静默:建库失败也给提示
createdRef.current = created;
}
// 主图随商品上传持久化:等全部传完再关抽屉(原先 close 在上传前 + 无 catch → 图悄悄传失败,商品却已建)
if (files.length && onUploadImage) {
for (let i = 0; i < files.length; i += 1) {
try {
await Promise.all(files.map((file, i) => {
const fd = new FormData();
fd.append("file", files[i]);
fd.append("file", file);
fd.append("name", `${name}-主图${files.length > 1 ? `-${i + 1}` : ""}`);
await onUploadImage(created.id, fd);
return onUploadImage(created!.id, fd);
}));
} catch {
// 不关抽屉、不重复建商品:留着让用户点「创建商品」重试上传(网络恢复后即成)
flash("图片上传失败", "已保留 · 点「创建商品」可重试上传");
return;
}
}
createdRef.current = null;
close();
onCreated?.(created);
} finally {
setSaving(false);