Pārlūkot izejas kodu

数据总览页面

Ethanfly 12 stundas atpakaļ
vecāks
revīzija
7dc3fc2b01

+ 4 - 0
client/src/components.d.ts

@@ -19,6 +19,8 @@ declare module 'vue' {
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
     ElContainer: typeof import('element-plus/es')['ElContainer']
     ElDialog: typeof import('element-plus/es')['ElDialog']
+    ElDivider: typeof import('element-plus/es')['ElDivider']
+    ElDrawer: typeof import('element-plus/es')['ElDrawer']
     ElDropdown: typeof import('element-plus/es')['ElDropdown']
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -31,6 +33,8 @@ declare module 'vue' {
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElOption: typeof import('element-plus/es')['ElOption']
+    ElPagination: typeof import('element-plus/es')['ElPagination']
+    ElProgress: typeof import('element-plus/es')['ElProgress']
     ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElSelect: typeof import('element-plus/es')['ElSelect']

+ 39 - 35
client/src/views/Analytics/Overview/index.vue

@@ -79,22 +79,22 @@
         </el-table-column>
         <el-table-column prop="totalIncome" label="总收益" width="90" align="center">
           <template #default="{ row }">
-            <span>{{ row.totalIncome || '未支持' }}</span>
+            <span>{{ row.totalIncome !== null && row.totalIncome !== undefined ? row.totalIncome.toFixed(2) : '未支持' }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="yesterdayIncome" label="昨日收益" width="90" align="center">
           <template #default="{ row }">
-            <span>{{ row.yesterdayIncome || '未支持' }}</span>
+            <span>{{ row.yesterdayIncome !== null && row.yesterdayIncome !== undefined ? row.yesterdayIncome.toFixed(2) : '未支持' }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="totalViews" label="总播放(阅读)" width="110" align="center">
           <template #default="{ row }">
-            <span>{{ row.totalViews || '未支持' }}</span>
+            <span>{{ row.totalViews !== null && row.totalViews !== undefined ? formatNumber(row.totalViews) : '未支持' }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="yesterdayViews" label="昨日播放(阅读)" width="120" align="center">
           <template #default="{ row }">
-            <span>{{ row.yesterdayViews || '未支持' }}</span>
+            <span>{{ row.yesterdayViews !== null && row.yesterdayViews !== undefined ? formatNumber(row.yesterdayViews) : '未支持' }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="fansCount" label="粉丝数" width="90" align="center">
@@ -109,7 +109,7 @@
         </el-table-column>
         <el-table-column prop="yesterdayLikes" label="昨日点赞" width="90" align="center">
           <template #default="{ row }">
-            <span>{{ row.yesterdayLikes ?? 0 }}</span>
+            <span>{{ formatNumber(row.yesterdayLikes ?? 0) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="yesterdayFansIncrease" label="昨日涨粉" width="90" align="center">
@@ -233,35 +233,17 @@ const summaryData = ref<SummaryData>({
   yesterdayFansIncrease: 0,
 });
 
-// 基于过滤后账号列表的汇总统计(只统计抖音、百家号、视频号和小红书)
-const filteredSummaryData = computed(() => {
-  const allowedPlatforms: PlatformType[] = ['douyin', 'baijiahao', 'weixin_video', 'xiaohongshu'];
-  const filtered = accounts.value.filter(a => allowedPlatforms.includes(a.platform));
-  
-  return {
-    totalAccounts: filtered.length,
-    totalIncome: filtered.reduce((sum, a) => sum + (a.totalIncome || 0), 0),
-    yesterdayIncome: filtered.reduce((sum, a) => sum + (a.yesterdayIncome || 0), 0),
-    totalViews: filtered.reduce((sum, a) => sum + (a.totalViews || 0), 0),
-    yesterdayViews: filtered.reduce((sum, a) => sum + (a.yesterdayViews || 0), 0),
-    totalFans: filtered.reduce((sum, a) => sum + (a.fansCount || 0), 0),
-    yesterdayComments: filtered.reduce((sum, a) => sum + (a.yesterdayComments || 0), 0),
-    yesterdayLikes: filtered.reduce((sum, a) => sum + (a.yesterdayLikes || 0), 0),
-    yesterdayFansIncrease: filtered.reduce((sum, a) => sum + (a.yesterdayFansIncrease || 0), 0),
-  };
-});
-
-// 统计卡片数据(使用过滤后的汇总数据)
+// 统计卡片数据(使用后端返回的汇总数据)
 const summaryStats = computed(() => [
-  { label: '账号总数', value: filteredSummaryData.value.totalAccounts },
-  { label: '总收益(元)', value: filteredSummaryData.value.totalIncome },
-  { label: '昨日收益(元)', value: filteredSummaryData.value.yesterdayIncome },
-  { label: '总播放(阅读)', value: filteredSummaryData.value.totalViews },
-  { label: '昨日播放(阅读)', value: filteredSummaryData.value.yesterdayViews },
-  { label: '总粉丝', value: filteredSummaryData.value.totalFans, highlight: true },
-  { label: '昨日评论', value: filteredSummaryData.value.yesterdayComments },
-  { label: '昨日点赞', value: filteredSummaryData.value.yesterdayLikes },
-  { label: '昨日涨粉', value: filteredSummaryData.value.yesterdayFansIncrease },
+  { label: '账号总数', value: summaryData.value.totalAccounts },
+  { label: '总收益(元)', value: summaryData.value.totalIncome || 0 },
+  { label: '昨日收益(元)', value: summaryData.value.yesterdayIncome || 0 },
+  { label: '总播放(阅读)', value: formatNumber(summaryData.value.totalViews || 0) },
+  { label: '昨日播放(阅读)', value: formatNumber(summaryData.value.yesterdayViews || 0) },
+  { label: '总粉丝', value: formatNumber(summaryData.value.totalFans || 0), highlight: true },
+  { label: '昨日评论', value: summaryData.value.yesterdayComments || 0 },
+  { label: '昨日点赞', value: formatNumber(summaryData.value.yesterdayLikes || 0) },
+  { label: '昨日涨粉', value: summaryData.value.yesterdayFansIncrease || 0 },
 ]);
 
 // 过滤后的账号列表(只显示抖音、百家号、视频号和小红书)
@@ -341,11 +323,33 @@ async function loadData() {
     const result = await response.json();
     
     if (result.success && result.data) {
-      accounts.value = result.data.accounts || [];
-      summaryData.value = result.data.summary || summaryData.value;
+      // 确保只保留支持的平台
+      const allowedPlatforms: PlatformType[] = ['douyin', 'baijiahao', 'weixin_video', 'xiaohongshu'];
+      accounts.value = (result.data.accounts || []).filter((a: AccountData) => 
+        allowedPlatforms.includes(a.platform)
+      );
+      
+      // 使用后端返回的汇总数据
+      if (result.data.summary) {
+        summaryData.value = {
+          totalAccounts: result.data.summary.totalAccounts || 0,
+          totalIncome: result.data.summary.totalIncome || 0,
+          yesterdayIncome: result.data.summary.yesterdayIncome || 0,
+          totalViews: result.data.summary.totalViews || 0,
+          yesterdayViews: result.data.summary.yesterdayViews || 0,
+          totalFans: result.data.summary.totalFans || 0,
+          yesterdayComments: result.data.summary.yesterdayComments || 0,
+          yesterdayLikes: result.data.summary.yesterdayLikes || 0,
+          yesterdayFansIncrease: result.data.summary.yesterdayFansIncrease || 0,
+        };
+      }
+    } else {
+      console.error('加载数据失败:', result.error || '未知错误');
+      ElMessage.error(result.error || '加载数据失败');
     }
   } catch (error) {
     console.error('加载数据失败:', error);
+    ElMessage.error('加载数据失败,请稍后重试');
   } finally {
     loading.value = false;
   }

BIN
server/python/__pycache__/app.cpython-311.pyc


+ 114 - 4
server/python/app.py

@@ -160,6 +160,15 @@ INTERNAL_API_KEY = os.environ.get('INTERNAL_API_KEY', 'internal-api-key-default'
 print(f"[API Config] Node.js API: {NODEJS_API_BASE_URL}", flush=True)
 
 
+class NodeApiError(Exception):
+    """用于把 Node 内部接口的错误状态码/内容透传给调用方。"""
+
+    def __init__(self, status_code: int, payload: dict):
+        super().__init__(payload.get("error") or payload.get("message") or "Node API error")
+        self.status_code = status_code
+        self.payload = payload
+
+
 def call_nodejs_api(method: str, endpoint: str, data: dict = None, params: dict = None) -> dict:
     """调用 Node.js 内部 API"""
     url = f"{NODEJS_API_BASE_URL}/api/internal{endpoint}"
@@ -175,12 +184,47 @@ def call_nodejs_api(method: str, endpoint: str, data: dict = None, params: dict
             response = requests.post(url, headers=headers, json=data, timeout=30)
         else:
             raise ValueError(f"Unsupported HTTP method: {method}")
-        
-        response.raise_for_status()
-        return response.json()
+
+        # 兼容 Node 可能返回非 JSON 的情况
+        try:
+            payload = response.json()
+        except Exception:
+            payload = {
+                "success": False,
+                "error": "Node.js API 返回非 JSON 响应",
+                "status": response.status_code,
+                "text": (response.text or "")[:2000],
+                "url": url,
+                "endpoint": endpoint,
+            }
+
+        if response.status_code >= 400:
+            # 把真实状态码/返回体抛出去,由路由决定如何返回给前端
+            if isinstance(payload, dict):
+                payload.setdefault("success", False)
+                payload.setdefault("status", response.status_code)
+                payload.setdefault("url", url)
+                payload.setdefault("endpoint", endpoint)
+            raise NodeApiError(response.status_code, payload if isinstance(payload, dict) else {
+                "success": False,
+                "error": "Node.js API 调用失败",
+                "status": response.status_code,
+                "data": payload,
+                "url": url,
+                "endpoint": endpoint,
+            })
+
+        return payload
     except requests.exceptions.RequestException as e:
+        # 连接失败/超时等(此时通常拿不到 response)
         print(f"[API Error] 调用 Node.js API 失败: {e}", flush=True)
-        raise
+        raise NodeApiError(502, {
+            "success": False,
+            "error": f"无法连接 Node.js API: {str(e)}",
+            "status": 502,
+            "url": url,
+            "endpoint": endpoint,
+        })
 
 
 # ==================== 签名相关(小红书专用) ====================
@@ -879,6 +923,72 @@ def get_work_statistics_history():
         return jsonify({"success": False, "error": str(e)}), 500
 
 
+@app.route("/work_day_statistics/overview", methods=["GET"])
+def get_overview():
+    """
+    获取数据总览(账号列表和汇总统计)
+    
+    查询参数:
+        user_id: 用户ID (必填)
+    
+    响应:
+    {
+        "success": true,
+        "data": {
+            "accounts": [
+                {
+                    "id": 1,
+                    "nickname": "账号名称",
+                    "username": "账号ID",
+                    "avatarUrl": "头像URL",
+                    "platform": "douyin",
+                    "groupId": 1,
+                    "fansCount": 1000,
+                    "totalIncome": null,
+                    "yesterdayIncome": null,
+                    "totalViews": 5000,
+                    "yesterdayViews": 100,
+                    "yesterdayComments": 10,
+                    "yesterdayLikes": 50,
+                    "yesterdayFansIncrease": 5,
+                    "updateTime": "2025-01-26T10:00:00Z",
+                    "status": "active"
+                },
+                ...
+            ],
+            "summary": {
+                "totalAccounts": 5,
+                "totalIncome": 0,
+                "yesterdayIncome": 0,
+                "totalViews": 10000,
+                "yesterdayViews": 200,
+                "totalFans": 5000,
+                "yesterdayComments": 20,
+                "yesterdayLikes": 100,
+                "yesterdayFansIncrease": 10
+            }
+        }
+    }
+    """
+    try:
+        user_id = request.args.get("user_id")
+        
+        if not user_id:
+            return jsonify({"success": False, "error": "缺少 user_id 参数"}), 400
+        
+        # 调用 Node.js API 获取数据
+        params = {"user_id": user_id}
+        result = call_nodejs_api('GET', '/work-day-statistics/overview', params=params)
+        
+        return jsonify(result)
+    except NodeApiError as e:
+        # 透传 Node 的真实状态码/错误内容,避免所有错误都变成 500
+        return jsonify(e.payload), e.status_code
+    except Exception as e:
+        traceback.print_exc()
+        return jsonify({"success": False, "error": str(e)}), 500
+
+
 # ==================== 获取评论列表接口 ====================
 
 @app.route("/comments", methods=["POST"])

BIN
server/python/platforms/__pycache__/baijiahao.cpython-311.pyc


BIN
server/python/platforms/__pycache__/base.cpython-311.pyc


BIN
server/python/platforms/__pycache__/douyin.cpython-311.pyc


BIN
server/python/platforms/__pycache__/kuaishou.cpython-311.pyc


BIN
server/python/platforms/__pycache__/weixin.cpython-311.pyc


BIN
server/python/platforms/__pycache__/xiaohongshu.cpython-311.pyc


+ 21 - 0
server/src/routes/internal.ts

@@ -153,4 +153,25 @@ router.post(
   })
 );
 
+/**
+ * GET /api/internal/work-day-statistics/overview
+ * 获取数据总览(账号列表和汇总统计)
+ */
+router.get(
+  '/work-day-statistics/overview',
+  [
+    query('user_id').notEmpty().withMessage('user_id 不能为空'),
+    validateRequest,
+  ],
+  asyncHandler(async (req, res) => {
+    const userId = parseInt(req.query.user_id as string);
+    const data = await workDayStatisticsService.getOverview(userId);
+
+    res.json({
+      success: true,
+      data,
+    });
+  })
+);
+
 export default router;

+ 318 - 0
server/src/services/WorkDayStatisticsService.ts

@@ -1,5 +1,6 @@
 import { AppDataSource, WorkDayStatistics, Work, PlatformAccount } from '../models/index.js';
 import { Between, In } from 'typeorm';
+import { logger } from '../utils/logger.js';
 
 interface StatisticsItem {
   workId: number;
@@ -388,4 +389,321 @@ export class WorkDayStatisticsService {
 
     return groupedData;
   }
+
+  /**
+   * 获取数据总览
+   * 返回账号列表和汇总统计数据
+   */
+  async getOverview(userId: number): Promise<{
+    accounts: Array<{
+      id: number;
+      nickname: string;
+      username: string;
+      avatarUrl: string | null;
+      platform: string;
+      groupId: number | null;
+      fansCount: number;
+      totalIncome: number | null;
+      yesterdayIncome: number | null;
+      totalViews: number | null;
+      yesterdayViews: number | null;
+      yesterdayComments: number;
+      yesterdayLikes: number;
+      yesterdayFansIncrease: number;
+      updateTime: string;
+      status: string;
+    }>;
+    summary: {
+      totalAccounts: number;
+      totalIncome: number;
+      yesterdayIncome: number;
+      totalViews: number;
+      yesterdayViews: number;
+      totalFans: number;
+      yesterdayComments: number;
+      yesterdayLikes: number;
+      yesterdayFansIncrease: number;
+    };
+  }> {
+    // 只查询支持的平台:抖音、百家号、视频号、小红书
+    const allowedPlatforms = ['douyin', 'baijiahao', 'weixin_video', 'xiaohongshu'];
+    
+    // 获取用户的所有账号(只包含支持的平台)
+    const accounts = await this.accountRepository.find({
+      where: {
+        userId,
+        platform: In(allowedPlatforms),
+      },
+    });
+
+    // 使用中国时区(UTC+8)计算“今天/昨天”的业务日期
+    // 思路:在当前 UTC 时间基础上 +8 小时,再取 ISO 日期部分,即为中国日历日期
+    const now = new Date();
+    const chinaNow = new Date(now.getTime() + 8 * 60 * 60 * 1000);
+    const chinaYesterday = new Date(chinaNow.getTime() - 24 * 60 * 60 * 1000);
+
+    // 格式化为 YYYY-MM-DD,与 MySQL DATE 字段匹配
+    const todayStr = chinaNow.toISOString().split('T')[0];
+    const yesterdayStr = chinaYesterday.toISOString().split('T')[0];
+    
+    logger.info(`[WorkDayStatistics] getOverview - userId: ${userId}, today: ${todayStr}, yesterday: ${yesterdayStr}`);
+
+    const accountList: Array<{
+      id: number;
+      nickname: string;
+      username: string;
+      avatarUrl: string | null;
+      platform: string;
+      groupId: number | null;
+      fansCount: number;
+      totalIncome: number | null;
+      yesterdayIncome: number | null;
+      totalViews: number | null;
+      yesterdayViews: number | null;
+      yesterdayComments: number;
+      yesterdayLikes: number;
+      yesterdayFansIncrease: number;
+      updateTime: string;
+      status: string;
+    }> = [];
+
+    // 汇总统计数据
+    let totalAccounts = 0;
+    let totalIncome = 0;
+    let yesterdayIncome = 0;
+    let totalViews = 0;
+    let yesterdayViews = 0;
+    let totalFans = 0;
+    let yesterdayComments = 0;
+    let yesterdayLikes = 0;
+    let yesterdayFansIncrease = 0;
+
+    for (const account of accounts) {
+      // 获取该账号的所有作品ID
+      const works = await this.workRepository.find({
+        where: { accountId: account.id },
+        select: ['id'],
+      });
+
+      if (works.length === 0) {
+        // 如果没有作品,只返回账号基本信息
+        const accountFansCount = account.fansCount || 0;
+        accountList.push({
+          id: account.id,
+          nickname: account.accountName || '',
+          username: account.accountId || '',
+          avatarUrl: account.avatarUrl,
+          platform: account.platform,
+          groupId: account.groupId,
+          fansCount: accountFansCount,
+          totalIncome: null,
+          yesterdayIncome: null,
+          totalViews: null,
+          yesterdayViews: null,
+          yesterdayComments: 0,
+          yesterdayLikes: 0,
+          yesterdayFansIncrease: 0,
+          updateTime: account.updatedAt.toISOString(),
+          status: account.status,
+        });
+        
+        // 即使没有作品,也要累加账号的粉丝数到总粉丝数
+        totalAccounts++;
+        totalFans += accountFansCount;
+        continue;
+      }
+
+      const workIds = works.map(w => w.id);
+
+      // 获取每个作品的最新日期统计数据(总播放量等)
+      const latestStatsQuery = this.statisticsRepository
+        .createQueryBuilder('wds')
+        .select('wds.work_id', 'workId')
+        .addSelect('MAX(wds.record_date)', 'latestDate')
+        .addSelect('MAX(wds.play_count)', 'playCount')
+        .addSelect('MAX(wds.like_count)', 'likeCount')
+        .addSelect('MAX(wds.comment_count)', 'commentCount')
+        .addSelect('MAX(wds.fans_count)', 'fansCount')
+        .where('wds.work_id IN (:...workIds)', { workIds })
+        .groupBy('wds.work_id');
+
+      const latestStats = await latestStatsQuery.getRawMany();
+
+      // 计算总播放量(所有作品最新日期的play_count总和)
+      let accountTotalViews = 0;
+      const latestDateMap = new Map<number, string>();
+      for (const stat of latestStats) {
+        accountTotalViews += parseInt(stat.playCount) || 0;
+        latestDateMap.set(stat.workId, stat.latestDate);
+      }
+
+      // 获取昨天和今天的数据来计算增量
+      // 使用日期字符串直接比较(DATE 类型会自动转换)
+      const yesterdayStatsQuery = this.statisticsRepository
+        .createQueryBuilder('wds')
+        .select('wds.work_id', 'workId')
+        .addSelect('SUM(wds.play_count)', 'playCount')
+        .addSelect('SUM(wds.like_count)', 'likeCount')
+        .addSelect('SUM(wds.comment_count)', 'commentCount')
+        .addSelect('MAX(wds.fans_count)', 'fansCount')
+        .where('wds.work_id IN (:...workIds)', { workIds })
+        .andWhere('wds.record_date = :yesterday', { yesterday: yesterdayStr })
+        .groupBy('wds.work_id');
+
+      const todayStatsQuery = this.statisticsRepository
+        .createQueryBuilder('wds')
+        .select('wds.work_id', 'workId')
+        .addSelect('SUM(wds.play_count)', 'playCount')
+        .addSelect('SUM(wds.like_count)', 'likeCount')
+        .addSelect('SUM(wds.comment_count)', 'commentCount')
+        .addSelect('MAX(wds.fans_count)', 'fansCount')
+        .where('wds.work_id IN (:...workIds)', { workIds })
+        .andWhere('wds.record_date = :today', { today: todayStr })
+        .groupBy('wds.work_id');
+
+      const [yesterdayStats, todayStats] = await Promise.all([
+        yesterdayStatsQuery.getRawMany(),
+        todayStatsQuery.getRawMany(),
+      ]);
+      
+      logger.info(`[WorkDayStatistics] Account ${account.id} (${account.accountName}) - workIds: ${workIds.length}, yesterdayStats: ${yesterdayStats.length}, todayStats: ${todayStats.length}`);
+      
+      if (yesterdayStats.length > 0 || todayStats.length > 0) {
+        logger.debug(`[WorkDayStatistics] yesterdayStats:`, JSON.stringify(yesterdayStats.slice(0, 3)));
+        logger.debug(`[WorkDayStatistics] todayStats:`, JSON.stringify(todayStats.slice(0, 3)));
+      }
+
+      // 计算昨日增量
+      let accountYesterdayViews = 0;
+      let accountYesterdayComments = 0;
+      let accountYesterdayLikes = 0;
+      let accountYesterdayFansIncrease = 0;
+
+      // 按作品ID汇总
+      const yesterdayMap = new Map<number, { play: number; like: number; comment: number; fans: number }>();
+      const todayMap = new Map<number, { play: number; like: number; comment: number; fans: number }>();
+
+      for (const stat of yesterdayStats) {
+        const workId = parseInt(String(stat.workId)) || 0;
+        yesterdayMap.set(workId, {
+          play: Number(stat.playCount) || 0,
+          like: Number(stat.likeCount) || 0,
+          comment: Number(stat.commentCount) || 0,
+          fans: Number(stat.fansCount) || 0,
+        });
+      }
+
+      for (const stat of todayStats) {
+        const workId = parseInt(String(stat.workId)) || 0;
+        todayMap.set(workId, {
+          play: Number(stat.playCount) || 0,
+          like: Number(stat.likeCount) || 0,
+          comment: Number(stat.commentCount) || 0,
+          fans: Number(stat.fansCount) || 0,
+        });
+      }
+      
+      logger.debug(`[WorkDayStatistics] Account ${account.id} - yesterdayMap size: ${yesterdayMap.size}, todayMap size: ${todayMap.size}`);
+
+      // 计算增量(今天 - 昨天)
+      for (const workId of workIds) {
+        const todayData = todayMap.get(workId) || { play: 0, like: 0, comment: 0, fans: 0 };
+        const yesterdayData = yesterdayMap.get(workId) || { play: 0, like: 0, comment: 0, fans: 0 };
+        
+        const viewsDiff = todayData.play - yesterdayData.play;
+        const commentsDiff = todayData.comment - yesterdayData.comment;
+        const likesDiff = todayData.like - yesterdayData.like;
+        
+        accountYesterdayViews += Math.max(0, viewsDiff);
+        accountYesterdayComments += Math.max(0, commentsDiff);
+        accountYesterdayLikes += Math.max(0, likesDiff);
+      }
+      
+      logger.info(`[WorkDayStatistics] Account ${account.id} - Calculated: views=${accountYesterdayViews}, comments=${accountYesterdayComments}, likes=${accountYesterdayLikes}`);
+
+      // 获取账号的最新粉丝数(从最新日期的统计数据中取最大值)
+      let accountFansCount = account.fansCount || 0;
+      if (latestStats.length > 0) {
+        const maxFans = Math.max(...latestStats.map(s => parseInt(s.fansCount) || 0));
+        if (maxFans > 0) {
+          accountFansCount = maxFans;
+        }
+      }
+
+      // 计算昨日涨粉(今天最新粉丝数 - 昨天最新粉丝数)
+      // 如果今天有统计数据,用今天数据中的最大粉丝数;否则用账号表的当前粉丝数
+      const todayMaxFans = todayStats.length > 0
+        ? Math.max(...todayStats.map(s => parseInt(s.fansCount) || 0))
+        : accountFansCount;
+      
+      // 如果昨天有统计数据,用昨天数据中的最大粉丝数;否则需要找最近一天的数据作为基准
+      let yesterdayMaxFans: number;
+      if (yesterdayStats.length > 0) {
+        // 昨天有数据,直接用昨天的最大粉丝数
+        yesterdayMaxFans = Math.max(...yesterdayStats.map(s => parseInt(s.fansCount) || 0));
+      } else {
+        // 昨天没有数据,需要找最近一天的数据作为基准
+        // 查询该账号最近一天(早于今天)的统计数据
+        const recentStatsQuery = this.statisticsRepository
+          .createQueryBuilder('wds')
+          .select('MAX(wds.fans_count)', 'fansCount')
+          .where('wds.work_id IN (:...workIds)', { workIds })
+          .andWhere('wds.record_date < :today', { today: todayStr });
+        
+        const recentStat = await recentStatsQuery.getRawOne();
+        if (recentStat && recentStat.fansCount) {
+          // 找到了最近一天的数据,用它作为基准
+          yesterdayMaxFans = parseInt(recentStat.fansCount) || accountFansCount;
+        } else {
+          // 完全没有历史数据,用账号表的当前粉丝数作为基准(但这样计算出来的增量可能不准确)
+          yesterdayMaxFans = accountFansCount;
+        }
+      }
+      
+      accountYesterdayFansIncrease = todayMaxFans - yesterdayMaxFans;
+
+      accountList.push({
+        id: account.id,
+        nickname: account.accountName || '',
+        username: account.accountId || '',
+        avatarUrl: account.avatarUrl,
+        platform: account.platform,
+        groupId: account.groupId,
+        fansCount: accountFansCount,
+        totalIncome: null, // 收益数据需要从其他表获取,暂时为null
+        yesterdayIncome: null,
+        totalViews: accountTotalViews > 0 ? accountTotalViews : null,
+        yesterdayViews: accountYesterdayViews > 0 ? accountYesterdayViews : null,
+        yesterdayComments: accountYesterdayComments,
+        yesterdayLikes: accountYesterdayLikes,
+        yesterdayFansIncrease: accountYesterdayFansIncrease,
+        updateTime: account.updatedAt.toISOString(),
+        status: account.status,
+      });
+
+      // 累加汇总数据
+      totalAccounts++;
+      totalViews += accountTotalViews;
+      yesterdayViews += accountYesterdayViews;
+      totalFans += accountFansCount;
+      yesterdayComments += accountYesterdayComments;
+      yesterdayLikes += accountYesterdayLikes;
+      yesterdayFansIncrease += accountYesterdayFansIncrease;
+    }
+
+    return {
+      accounts: accountList,
+      summary: {
+        totalAccounts,
+        totalIncome,
+        yesterdayIncome,
+        totalViews,
+        yesterdayViews,
+        totalFans,
+        yesterdayComments,
+        yesterdayLikes,
+        yesterdayFansIncrease,
+      },
+    };
+  }
 }