fix: 平台套图/新建商品 YYX 第二轮(清单4 未完成项)
平台套图: - 商品列表无真封面时回退 mock 图(与商品库一致显图) - 商品分类去掉前缀「//」 - 左侧商品栏可折叠(收起只剩商品图) - 结果搜索框加 × 清空 + 平台筛选悬浮弹窗(可清空) - 结果卡精简:去容器/引号/平台/小标题,提示词可折叠展开、张数灰色 - 删悬浮「更多」里重复的加入资产库入口 - 去掉加入资产库后的中央遮罩,只保留左上角已入库标签 - 重跑/单图再生成的新批次置底并自动滚动到底 新建商品抽屉: - 上传失败:黑色遮罩 + 感叹号 + 提示 + 重新上传胶囊 - 填了内容中途退出:二次确认(复用 ConfirmModal) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
||||
import type { ChangeEvent, DragEvent } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { api } from "../api";
|
||||
import { useBodyScrollLock } from "./overlays";
|
||||
import { useBodyScrollLock, ConfirmModal } from "./overlays";
|
||||
import type { Product } from "../types";
|
||||
import "../product-create-page.css";
|
||||
|
||||
@@ -51,6 +51,8 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
const [saving, setSaving] = useState(false);
|
||||
// 抽屉内就地 toast(组件无全局 Shell.toast,这里自给一条,镜像 V1 反馈文案)
|
||||
const [toast, setToast] = useState<{ title: string; sub: string } | null>(null);
|
||||
// YYX#24:填了内容中途退出 → 二次确认
|
||||
const [confirmExit, setConfirmExit] = useState(false);
|
||||
const imgInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const toastTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
// 始终指向最新 images:addImages 里同步算余量 + 发上传,不靠会滞后的 effect/闭包
|
||||
@@ -80,6 +82,16 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
// 每次打开抽屉时复位表单(含商品库「继续创建商品」二次打开)
|
||||
useEffect(() => { if (open) resetForm(); }, [open]);
|
||||
|
||||
// YYX#24:表单是否已填内容(有任一内容即视为「编辑中」)
|
||||
function isDirty() {
|
||||
return Boolean(title.trim() || target.trim() || bulletDraft.trim() || bullets.length || images.length);
|
||||
}
|
||||
// 关闭入口统一走这里:有内容则先二次确认,否则直接关
|
||||
function guardedClose() {
|
||||
if (isDirty()) setConfirmExit(true);
|
||||
else close();
|
||||
}
|
||||
|
||||
// upload-first:任一图处于「uploading」且未在途 → 立刻传进桶(/assets/upload/),拿到 assetId 标 done
|
||||
async function uploadOne(entry: PfImage) {
|
||||
try {
|
||||
@@ -119,6 +131,13 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
return list.filter((item) => item.id !== id);
|
||||
});
|
||||
}
|
||||
// YYX#22:上传失败后「重新上传」—— 把该图置回 uploading 再发一次
|
||||
function retryUpload(id: string) {
|
||||
const hit = imagesRef.current.find((item) => item.id === id);
|
||||
if (!hit) return;
|
||||
setImages((list) => list.map((it) => (it.id === id ? { ...it, status: "uploading" } : it)));
|
||||
void uploadOne({ ...hit, status: "uploading" });
|
||||
}
|
||||
function openPicker() {
|
||||
if (images.length >= PF_MAX) { flash("已达上限", `${PF_MAX} / ${PF_MAX} 张`); return; }
|
||||
imgInputRef.current?.click();
|
||||
@@ -176,11 +195,11 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<div className={`drawer-bg${open ? " show" : ""}`} onClick={close} />
|
||||
<div className={`drawer-bg${open ? " show" : ""}`} onClick={guardedClose} />
|
||||
<aside className={`drawer pc-drawer${open ? " show" : ""}`} role="dialog" aria-label="新建商品" aria-hidden={!open}>
|
||||
<div className="drawer-h">
|
||||
<h3>新建商品</h3>
|
||||
<button className="x" type="button" onClick={close} aria-label="关闭">
|
||||
<button className="x" type="button" onClick={guardedClose} aria-label="关闭">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M6 6l12 12M6 18L18 6" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -242,7 +261,19 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
<img src={item.url} alt="商品主图预览" />
|
||||
{idx === 0 && <span className="pf-cover">主图</span>}
|
||||
{item.status === "uploading" && <span className="pf-state"><span className="spinner" aria-hidden="true" /></span>}
|
||||
{item.status === "error" && <span className="pf-state pf-state-err">上传失败</span>}
|
||||
{/* YYX#22:上传失败 —— 黑色遮罩 + 感叹号 + 提示 + 重新上传胶囊 */}
|
||||
{item.status === "error" && (
|
||||
<div className="pf-fail-mask">
|
||||
<span className="pf-fail-ic" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><path d="M12 8v4" /><path d="M12 16h.01" /></svg>
|
||||
</span>
|
||||
<span className="pf-fail-t">上传失败</span>
|
||||
<button className="pf-retry" type="button" onClick={() => retryUpload(item.id)}>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36" /><path d="M21 3v6h-6" /></svg>
|
||||
重新上传
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button className="pf-x" type="button" title="删除" aria-label="删除该图" onClick={() => removeImage(item.id)}>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M4 4l8 8M12 4l-8 8" /></svg>
|
||||
</button>
|
||||
@@ -285,7 +316,7 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9" /><path d="M9.5 9a2.5 2.5 0 015 0c0 1.5-2.5 2-2.5 4M12 17h.01" /></svg>
|
||||
使用指南
|
||||
</button>
|
||||
<button className="btn" type="button" onClick={close}>取消</button>
|
||||
<button className="btn" type="button" onClick={guardedClose}>取消</button>
|
||||
<button className="btn btn-primary" type="button" disabled={saving || images.some((im) => im.status === "uploading")} onClick={submit}>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 12l5 5L20 6" /></svg>
|
||||
{saving ? "创建中…" : images.some((im) => im.status === "uploading") ? "图片上传中…" : "创建商品"}
|
||||
@@ -299,6 +330,16 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
{/* YYX#24:填了内容中途退出 → 二次确认(复用通用 ConfirmModal) */}
|
||||
<ConfirmModal
|
||||
open={confirmExit}
|
||||
title="商品未保存"
|
||||
subtitle="// UNSAVED"
|
||||
detail="当前商品信息尚未保存,确定要退出编辑吗?"
|
||||
confirmText="退出编辑"
|
||||
onCancel={() => setConfirmExit(false)}
|
||||
onConfirm={() => { setConfirmExit(false); close(); }}
|
||||
/>
|
||||
</>,
|
||||
document.body,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user