Просмотр исходного кода

perf(web): combine dashboard 7-API calls into single POST /api/stats/dashboard

Backend: StatsController.getDashboardSummary() runs 7 counts + task list in
parallel via CompletableFuture (ThreadPoolExecutor). Frontend: Dashboard.vue
loads all data in one request instead of Promise.all(7).
Xiaogang Liao 2 месяцев назад
Родитель
Сommit
35b2dd0205

+ 61 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/stats/StatsController.java

@@ -1,8 +1,15 @@
 package com.etotem.cfc.controller.stats;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.Family;
+import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMapper;
+import com.etotem.cfc.mapper.GuidePackageMapper;
+import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.TaskStatsService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -13,7 +20,11 @@ import javax.annotation.Resource;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
 
 @Tag(name = "任务统计", description = "任务完成率分析")
 @RestController
@@ -29,6 +40,56 @@ public class StatsController {
     @Resource
     private ChildMapper childMapper;
 
+    @Resource
+    private FamilyMapper familyMapper;
+
+    @Resource
+    private TaskMapper taskMapper;
+
+    @Resource
+    private GuidePackageMapper guidePackageMapper;
+
+    @Resource
+    private ThreadPoolExecutor taskExecutor;
+
+    @Operation(summary = "管理后台仪表盘汇总")
+    @PostMapping("/dashboard")
+    public Result<Map<String, Object>> getDashboardSummary() {
+        Map<String, Object> data = new HashMap<>();
+
+        CompletableFuture<Long> familyFuture = CompletableFuture.supplyAsync(() ->
+            familyMapper.selectCount(null), taskExecutor);
+        CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
+            userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
+        CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
+            childMapper.selectCount(null), taskExecutor);
+        CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
+            userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
+        CompletableFuture<Long> pendingGuideFuture = CompletableFuture.supplyAsync(() ->
+            userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher").eq("status", "pending")), taskExecutor);
+        CompletableFuture<Long> pendingPackageFuture = CompletableFuture.supplyAsync(() ->
+            guidePackageMapper.selectCount(new QueryWrapper<com.etotem.cfc.entity.GuidePackage>()
+                .eq("status", "pending")), taskExecutor);
+        CompletableFuture<List<Task>> recentTasksFuture = CompletableFuture.supplyAsync(() -> {
+            QueryWrapper<Task> q = new QueryWrapper<Task>().orderByDesc("created_at").last("LIMIT 5");
+            return taskMapper.selectList(q);
+        }, taskExecutor);
+
+        try {
+            data.put("totalFamilies", familyFuture.get());
+            data.put("totalParents", parentFuture.get());
+            data.put("totalChildren", childFuture.get());
+            data.put("totalTeachers", teacherFuture.get());
+            data.put("pendingGuideCount", pendingGuideFuture.get());
+            data.put("pendingPackageCount", pendingPackageFuture.get());
+            data.put("recentTasks", recentTasksFuture.get());
+        } catch (Exception e) {
+            return Result.error("获取仪表盘数据失败");
+        }
+
+        return Result.success(data);
+    }
+
     @Operation(summary = "孩子任务完成率")
     @PostMapping("/child-completion")
     public Result<Map<String, Object>> childCompletion(

+ 9 - 0
cfc-web/src/api/admin.js

@@ -404,6 +404,15 @@ export function updateAdminInfo(data) {
   })
 }
 
+// ========== 管理后台仪表盘汇总 API ==========
+
+export function getDashboardStats() {
+  return request({
+    url: '/api/stats/dashboard',
+    method: 'post'
+  })
+}
+
 // ========== 商品管理 API ==========
 
 export function getProductList(params) {

+ 17 - 67
cfc-web/src/views/Dashboard.vue

@@ -99,7 +99,7 @@
               查看全部 →
             </el-button>
           </div>
-          <el-table :data="recentTasks" size="small" v-loading="loadingTasks" class="custom-table">
+          <el-table :data="recentTasks" size="small" class="custom-table">
             <el-table-column prop="title" label="任务名称" min-width="180"></el-table-column>
             <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
             <el-table-column prop="status" label="状态" width="100">
@@ -182,7 +182,7 @@
 </template>
 
 <script>
-import { getUserList, getFamilyList, getChildrenList, getTasksList, getPendingGuideCount, getPendingPackageCount } from '@/api/admin'
+import { getDashboardStats } from '@/api/admin'
 
 export default {
   name: 'Dashboard',
@@ -212,73 +212,23 @@ export default {
   },
   methods: {
     async loadDashboardData() {
-      await Promise.all([
-        this.loadFamilyStats(),
-        this.loadParentStats(),
-        this.loadChildrenStats(),
-        this.loadTeacherStats(),
-        this.loadRecentTasks(),
-        this.loadPendingGuideCount(),
-        this.loadPendingPackageCount()
-      ])
-    },
-    async loadFamilyStats() {
       try {
-        const res = await getFamilyList({ page: 1, size: 1 })
-        this.stats.totalFamilies = res.data.total || 0
+        const res = await getDashboardStats()
+        const d = res.data || {}
+        this.stats.totalFamilies = d.totalFamilies || 0
+        this.stats.totalParents = d.totalParents || 0
+        this.stats.totalChildren = d.totalChildren || 0
+        this.stats.totalTeachers = d.totalTeachers || 0
+        this.stats.pendingGuideCount = d.pendingGuideCount || 0
+        this.stats.pendingPackageCount = d.pendingPackageCount || 0
+        this.recentTasks = (d.recentTasks || []).map(t => ({
+          title: t.title,
+          childName: t.childName || '',
+          status: t.status,
+          points: t.points || 0
+        }))
       } catch (error) {
-        console.error('加载家庭统计失败:', error)
-      }
-    },
-    async loadParentStats() {
-      try {
-        const res = await getUserList({ page: 1, size: 1, role: 'parent' })
-        this.stats.totalParents = res.data.total || 0
-      } catch (error) {
-        console.error('加载家长统计失败:', error)
-      }
-    },
-    async loadChildrenStats() {
-      try {
-        const res = await getChildrenList({ page: 1, size: 1 })
-        this.stats.totalChildren = res.data.total || 0
-      } catch (error) {
-        console.error('加载孩子统计失败:', error)
-      }
-    },
-    async loadTeacherStats() {
-      try {
-        const res = await getUserList({ page: 1, size: 1, role: 'teacher' })
-        this.stats.totalTeachers = res.data.total || 0
-      } catch (error) {
-        console.error('加载规划师统计失败:', error)
-      }
-    },
-    async loadRecentTasks() {
-      this.loadingTasks = true
-      try {
-        const res = await getTasksList({ page: 1, size: 5 })
-        this.recentTasks = (res.data.records || res.data || []).slice(0, 5)
-      } catch (error) {
-        console.error('加载最近任务失败:', error)
-      } finally {
-        this.loadingTasks = false
-      }
-    },
-    async loadPendingGuideCount() {
-      try {
-        const res = await getPendingGuideCount()
-        this.stats.pendingGuideCount = res.data.count || 0
-      } catch (error) {
-        console.error('加载待审核规划师统计失败:', error)
-      }
-    },
-    async loadPendingPackageCount() {
-      try {
-        const res = await getPendingPackageCount()
-        this.stats.pendingPackageCount = res.data.count || 0
-      } catch (error) {
-        console.error('加载待审核套餐统计失败:', error)
+        console.error('加载仪表盘数据失败:', error)
       }
     }
   }