perf(core): 全站资产改服务端懒加载,撤掉 bootstrap 全量 allAssets

不再在启动时一次性拉全部资产(数据多时随之变慢/串多页),各页按需服务端分页/过滤:
- 生图(ImageWorkbench 模特库按 category=person、AssetFactory 按 ai 生成图)页面内懒加载。
- 商品库/项目向导封面图改用后端内嵌 cover_preview_url / images[].preview_url,不再反查全局 assets。
- 商品详情 AI 素材按 ?product 懒加载(上传/生成/采用三视图后刷新)。
- 工作台资产总数走轻量 /assets/summary/。
- App bootstrap 删除 api.allAssets() 与全局 assets state;loadData 仅留必要全局数据。
- 后端 ?product 过滤并集补上 ProductImage 关联资产,等价前端原 belongsToProduct。
- pipeline 用项目详情已内嵌的 *_url 显示,调用处传空 assets(不再依赖全局全量)。

闸验证:9 页功能正常、无分页瀑布、无接口报错;首屏不再含资产全量拉取。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-17 18:25:05 +08:00
co-authored by Claude Opus 4.8
parent 59ab3c0404
commit 2f6d3f5823
7 changed files with 92 additions and 71 deletions
+5 -14
View File
@@ -28,11 +28,9 @@ type WizProductPayload = {
selling_points?: Array<{ title: string; detail: string; sort_order: number }>;
};
export function ProjectWizardPage({ products, projects = [], assets = [], preselectProductId, onBack, onCreate, onCreateProduct }: {
export function ProjectWizardPage({ products, projects = [], preselectProductId, onBack, onCreate, onCreateProduct }: {
products: Product[];
projects?: Project[];
// 团队 assets:商品的 cover_asset/images[].asset 是资产 id,需在此反查真 preview_url
assets?: Asset[];
// 从商品详情页「立即生成」进入时,预勾选该商品(而非默认第一个/耳机);id 不存在则回落默认。
preselectProductId?: string;
onBack: () => void;
@@ -202,18 +200,11 @@ export function ProjectWizardPage({ products, projects = [], assets = [], presel
}
}
// cover_asset / images[].asset 是资产 id,在团队 assets 里反查真 preview_url(对齐 products.tsx)
const assetById = useMemo(() => {
const map = new Map<string, Asset>();
assets.forEach((a) => map.set(a.id, a));
return map;
}, [assets]);
// 商品封面图:用后端内嵌的 preview_url(cover_preview_url / images[].preview_url),不再反查全局 assets
const productCoverUrl = (p: Product): string => {
const firstImage = [...(p.images || [])].sort((a, b) => a.sort_order - b.sort_order)[0];
const id = p.cover_asset || p.images?.find((img) => img.is_primary)?.asset || firstImage?.asset;
if (!id) return "";
const asset = assetById.get(id);
return asset?.files?.find((f) => f.is_primary)?.preview_url || asset?.files?.[0]?.preview_url || "";
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 || "";
};
return (