From da0c7edfe228c38ebb92d62b804dedf7f5d147e3 Mon Sep 17 00:00:00 2001 From: zyc <1439655764@qq.com> Date: Wed, 17 Jun 2026 14:52:19 +0800 Subject: [PATCH] =?UTF-8?q?perf(core):=20=E8=B5=84=E4=BA=A7/=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E3=80=8C=E5=8F=96=E5=85=A8=E9=83=A8=E3=80=8D=E6=94=B9?= =?UTF-8?q?=E5=A4=A7=E9=A1=B5+=E5=B9=B6=E8=A1=8C,=E6=A0=B9=E6=B2=BB?= =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E5=88=B7=E6=96=B0=E4=B8=B2=E5=8D=81=E5=87=A0?= =?UTF-8?q?=E4=B8=AA=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 资产接口原固定 20/页且不支持 page_size,前端 allAssets 逐页串行翻完(190 资产=10 个 串行请求,每个 0.5~1.7s 依次等)→ 整页刷新好几秒。 - 后端:AssetViewSet 加 AssetPagination(page_size_query_param,上限 200) - 前端:allAssets/allNotifications 改「首页大页(200/100)+ 剩余页 Promise.all 并行」 资产 ≤200 时 1 个请求取全;再多也并行不串行。 Co-Authored-By: Claude Opus 4.8 --- core/backend/apps/assets/views.py | 10 +++++++ core/frontend/src/api.ts | 49 +++++++++++++++++++------------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/core/backend/apps/assets/views.py b/core/backend/apps/assets/views.py index 1c0fb77..6f45634 100644 --- a/core/backend/apps/assets/views.py +++ b/core/backend/apps/assets/views.py @@ -6,6 +6,7 @@ from django.db import transaction from django.http import StreamingHttpResponse from rest_framework import status from rest_framework.decorators import action +from rest_framework.pagination import PageNumberPagination from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.response import Response from rest_framework.views import APIView @@ -18,9 +19,18 @@ from .serializers import AssetSerializer, AssetUploadSerializer from .storage import TosStorage +class AssetPagination(PageNumberPagination): + """允许前端用 ?page_size= 覆盖(默认 20,上限 200)。前端「取全部资产」一次大页拉完, + 不再逐页串行(原来 20/页 → 资产多时每次刷新要串十几个请求,整页很慢)。""" + + page_size_query_param = "page_size" + max_page_size = 200 + + class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet): queryset = Asset.objects.prefetch_related("files").all() serializer_class = AssetSerializer + pagination_class = AssetPagination search_fields = ["name", "description"] ordering_fields = ["created_at", "updated_at", "name"] diff --git a/core/frontend/src/api.ts b/core/frontend/src/api.ts index 2209b27..3850a4f 100644 --- a/core/frontend/src/api.ts +++ b/core/frontend/src/api.ts @@ -373,13 +373,21 @@ export const api = { // 跟随 DRF 分页 next 取全部资产 —— 商品图/AI 素材/资产库都靠 asset.id 在这份列表里查 preview_url, // 只取第 1 页(20 条)会让第 20 条之后的资产解析不到图、渲染成空占位。 async allAssets(): Promise { - const out: Asset[] = []; - let path = "/api/assets/"; - for (let guard = 0; guard < 50 && path; guard += 1) { - const page: Paginated = await request>(path); - out.push(...page.results); - path = page.next ? new URL(page.next).pathname + new URL(page.next).search : ""; - } + // 一次大页(page_size=200)尽量取全;若还有多页,并行取剩余页(不再逐页串行等,根治整页刷新慢) + const SIZE = 200; + const first = await request>(`/api/assets/?page_size=${SIZE}`); + const out: Asset[] = [...first.results]; + const pageSize = first.results.length || SIZE; // 后端若忽略 page_size,按实际页大小算页数,逻辑仍正确 + const totalPages = pageSize > 0 ? Math.ceil((first.count || out.length) / pageSize) : 1; + if (totalPages <= 1) return out; + const rest = await Promise.all( + Array.from({ length: totalPages - 1 }, (_, i) => + request>(`/api/assets/?page_size=${SIZE}&page=${i + 2}`) + .then((p) => p.results) + .catch(() => [] as Asset[]) + ) + ); + rest.forEach((r) => out.push(...r)); return out; }, uploadAsset(formData: FormData) { @@ -423,18 +431,23 @@ export const api = { }, // 跟着 next 把所有消息取全(给侧边栏徽标 + 团队动态用,不参与收件箱滚动渲染),与 allAssets 同套路 async allNotifications(): Promise { - const out: Notification[] = []; - let path = "/api/ops/notifications/?page_size=100"; - let unreadCount = 0; - let typeCounts: NotificationList["type_counts"]; - for (let guard = 0; guard < 100 && path; guard += 1) { - const page = await request(path); - out.push(...page.results); - unreadCount = page.unread_count; - typeCounts = page.type_counts ?? typeCounts; - path = page.next ? new URL(page.next).pathname + new URL(page.next).search : ""; + // 首页拿计数,剩余页并行(原串行逐页:消息多时侧边栏徽标加载也拖慢整页) + const SIZE = 100; + const first = await request(`/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(`/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: unreadCount, type_counts: typeCounts }; + return { count: out.length, next: null, previous: null, results: out, unread_count: first.unread_count, type_counts: first.type_counts }; }, markAllNotificationsRead() { return request<{ updated: number; unread_count: number }>("/api/ops/notifications/mark-all-read/", {