refactor(core): 抽出共享 ProductCreateDrawer · 商品/项目新建复用同一流程

- 新增 components/product-create-drawer.tsx,products/projects 改用同一右侧 Drawer 组件(products -188 / projects -116)
- ai-tools 配套调整;附 perf-report 产物

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-17 20:13:40 +08:00
co-authored by Claude Opus 4.8
parent b714976d38
commit c9fb7139ac
6 changed files with 308 additions and 325 deletions
+43 -17
View File
@@ -523,6 +523,19 @@ export function ImageWorkbenchPage({
const [searchOpen, setSearchOpen] = useState(false);
// 模特网格默认只露 6 个;「全部模特 →」展开后露全部,再点收起(超过 6 个时第 7 个之后本来选不到)
const [showAllModels, setShowAllModels] = useState(false);
// 左侧商品空间搜索(原 input 无 state/无过滤=死框,输入无效);按名称/分类过滤
const [prodQuery, setProdQuery] = useState("");
const visibleProducts = products.filter((item) => {
const q = prodQuery.trim().toLowerCase();
if (!q) return true;
return `${item.title} ${item.category || ""}`.toLowerCase().includes(q);
});
// 商品主图:用后端内嵌的 preview_url(cover_preview_url / images[].preview_url),不再反查全局 assets
const productCoverUrl = (p: Product): string => {
const sorted = [...(p.images || [])].sort((a, b) => a.sort_order - b.sort_order);
const primary = p.images?.find((img) => img.is_primary) || sorted[0];
return p.cover_preview_url || primary?.preview_url || "";
};
function pickReference(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) return;
@@ -1052,7 +1065,7 @@ export function ImageWorkbenchPage({
</div>
<div className="iw-ps-search">
<Search size={13} />
<input placeholder="搜索商品 / 分类" />
<input placeholder="搜索商品 / 分类" value={prodQuery} onChange={(event) => setProdQuery(event.target.value)} />
</div>
<div className="iw-list-h">
<span className="mono">// 商品空间</span>
@@ -1070,23 +1083,36 @@ export function ImageWorkbenchPage({
<br />
// NO PRODUCTS
</div>
) : visibleProducts.length === 0 ? (
<div className="iw-ps-empty">
没有匹配的商品
<br />
// NO MATCH
</div>
) : (
products.slice(0, 12).map((item) => (
<button
className={`iw-prod-item ${productId === item.id ? "active" : ""}`}
type="button"
key={item.id}
onClick={() => setProductId(item.id)}
>
<div className="placeholder thumb">
<span className="ph-frame">{item.title.slice(0, 2)}</span>
</div>
<div className="body">
<div className="nm">{item.title}</div>
<div className="sub">// {item.category || "未分类"}</div>
</div>
</button>
))
visibleProducts.slice(0, 12).map((item) => {
const cover = productCoverUrl(item);
return (
<button
className={`iw-prod-item ${productId === item.id ? "active" : ""}`}
type="button"
key={item.id}
onClick={() => setProductId(item.id)}
>
{cover ? (
<div className="thumb has-real-media"><img src={cover} alt={item.title} loading="lazy" /></div>
) : (
<div className="placeholder thumb">
<span className="ph-frame">{item.title.slice(0, 2)}</span>
</div>
)}
<div className="body">
<div className="nm">{item.title}</div>
<div className="sub">// {item.category || "未分类"}</div>
</div>
</button>
);
})
)}
</div>
</aside>