perf(core): 资产预览改 TOS 公读直链 + 通知只取首页,减刷新开销与缓存失效

- 资产 preview_url 从「逐图签发预签名 URL」改为虚拟主机式公读直链
  (https://{bucket}.{host}/{key})。桶公读已验证免签可访问;直链稳定可被浏览器/CDN
  缓存,而签名链每次都变、1h 过期反而打不到缓存。boto3 也移出序列化热路径。
- allNotifications 不再逐页翻全部(原 O(N) 随消息增长拖慢每次刷新),只取首页 100 条
  (侧边栏徽标 unread_count + 团队动态仅展示最近 6 条已足够);「共 N」改用后端真实 count。
- perf-probe:page_size 由 API 级硬闸确定性守住后,浏览器端「资产翻页」降为提示
  (翻 1~2 页是资产真超 200 的合法分页,仅 ≥3 页才疑似 page_size 失效)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-17 16:58:48 +08:00
co-authored by Claude Opus 4.8
parent 1ff7be2b56
commit 8ba76e5bb7
6 changed files with 37 additions and 26 deletions
+10 -19
View File
@@ -312,7 +312,8 @@ export const api = {
return request(`/api/projects/${projectId}/adopt-base-asset/`, { method: "POST", body: JSON.stringify(payload) });
},
// 流程步骤4 · 用演员库现有资产替换基础资产卡(挂为候选并采用)
attachBaseAsset(projectId: string, payload: { group_id: string; asset_id: string }) {
// seed 占位卡还没 group:不传 group_id,改传 kind+label,后端按 label 命中/新建实体组再挂(不出图)
attachBaseAsset(projectId: string, payload: { group_id?: string; asset_id: string; kind?: "product" | "person" | "scene"; label?: string }) {
return request(`/api/projects/${projectId}/attach-base-asset/`, { method: "POST", body: JSON.stringify(payload) });
},
// 流程步骤4 · 据某一版立绘资产生成它配套的三视图(image_edit 锁角色一致性,绑定该立绘 asset)
@@ -401,6 +402,10 @@ export const api = {
uploadAsset(formData: FormData) {
return request<Asset>("/api/assets/upload/", { method: "POST", body: formData });
},
// 流程步骤4 · 给人物/资产改名(添加人物工作台命名 → PATCH 写回 name)
updateAsset(id: string, payload: { name?: string; description?: string }) {
return request<Asset>(`/api/assets/${id}/`, { method: "PATCH", body: JSON.stringify(payload) });
},
billingSummary() {
return request<BillingSummary>("/api/billing/summary/");
},
@@ -437,25 +442,11 @@ export const api = {
const qs = query.toString();
return request<NotificationList>(`/api/ops/notifications/${qs ? `?${qs}` : ""}`);
},
// 跟着 next 把所有消息取全(给侧边栏徽标 + 团队动态用,不参与收件箱滚动渲染),与 allAssets 同套路
// 侧边栏徽标(unread_count)+ 团队动态(只展示最近 6 条)用。只取第 1 页 100 条即足够,
// count 用后端真实总数(给「共 N」显示),不再随消息增长逐页翻全部(原来 O(N) 拖慢每次刷新)。
// 注:不参与收件箱滚动渲染——收件箱走 listNotifications 自己的服务端分页。
async allNotifications(): Promise<NotificationList> {
// 首页拿计数,剩余页并行(原串行逐页:消息多时侧边栏徽标加载也拖慢整页)
const SIZE = 100;
const first = await request<NotificationList>(`/api/ops/notifications/?page_size=${SIZE}`);
const out: Notification[] = [...first.results];
const pageSize = first.results.length || SIZE;
const totalPages = pageSize > 0 ? Math.ceil((first.count || out.length) / pageSize) : 1;
if (totalPages > 1) {
const rest = await Promise.all(
Array.from({ length: totalPages - 1 }, (_, i) =>
request<NotificationList>(`/api/ops/notifications/?page_size=${SIZE}&page=${i + 2}`)
.then((p) => p.results)
.catch(() => [] as Notification[])
)
);
rest.forEach((r) => out.push(...r));
}
return { count: out.length, next: null, previous: null, results: out, unread_count: first.unread_count, type_counts: first.type_counts };
return request<NotificationList>(`/api/ops/notifications/?page_size=100`);
},
markAllNotificationsRead() {
return request<{ updated: number; unread_count: number }>("/api/ops/notifications/mark-all-read/", {