فهرست منبع

Merge branch 'main' of http://gitlab.pubdata.cn/hlm/multi-platform-media-manage

Ethanfly 1 روز پیش
والد
کامیت
5a757612a1

+ 13 - 13
client/src/views/Analytics/Overview/index.vue

@@ -77,7 +77,7 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="totalIncome" label="总收益" width="90" align="center">
+        <!-- <el-table-column prop="totalIncome" label="总收益" width="90" align="center">
           <template #default="{ row }">
             <span>{{ row.totalIncome !== null && row.totalIncome !== undefined ? row.totalIncome.toFixed(2) : '未支持' }}</span>
           </template>
@@ -86,7 +86,7 @@
           <template #default="{ row }">
             <span>{{ row.yesterdayIncome !== null && row.yesterdayIncome !== undefined ? row.yesterdayIncome.toFixed(2) : '未支持' }}</span>
           </template>
-        </el-table-column>
+        </el-table-column> -->
         <el-table-column prop="totalViews" label="总播放(阅读)" width="110" align="center">
           <template #default="{ row }">
             <span>{{ row.totalViews !== null && row.totalViews !== undefined ? formatNumber(row.totalViews) : '未支持' }}</span>
@@ -194,8 +194,8 @@ interface AccountData {
   platform: PlatformType;
   groupId?: number;
   fansCount: number;
-  totalIncome: number | null;
-  yesterdayIncome: number | null;
+  // totalIncome: number | null;
+  // yesterdayIncome: number | null;
   totalViews: number | null;
   yesterdayViews: number | null;
   yesterdayComments: number;
@@ -211,8 +211,8 @@ const accounts = ref<AccountData[]>([]);
 // 汇总统计
 interface SummaryData {
   totalAccounts: number;
-  totalIncome: number;
-  yesterdayIncome: number;
+  // totalIncome: number;
+  // yesterdayIncome: number;
   totalViews: number;
   yesterdayViews: number;
   totalFans: number;
@@ -223,8 +223,8 @@ interface SummaryData {
 
 const summaryData = ref<SummaryData>({
   totalAccounts: 0,
-  totalIncome: 0,
-  yesterdayIncome: 0,
+  // totalIncome: 0,
+  // yesterdayIncome: 0,
   totalViews: 0,
   yesterdayViews: 0,
   totalFans: 0,
@@ -236,8 +236,8 @@ const summaryData = ref<SummaryData>({
 // 统计卡片数据(使用后端返回的汇总数据)
 const summaryStats = computed(() => [
   { label: '账号总数', value: summaryData.value.totalAccounts },
-  { label: '总收益(元)', value: summaryData.value.totalIncome || 0 },
-  { label: '昨日收益(元)', value: summaryData.value.yesterdayIncome || 0 },
+  // { 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 },
@@ -331,8 +331,8 @@ async function loadData() {
       if (data.summary) {
         summaryData.value = {
           totalAccounts: data.summary.totalAccounts || 0,
-          totalIncome: data.summary.totalIncome || 0,
-          yesterdayIncome: data.summary.yesterdayIncome || 0,
+          // totalIncome: data.summary.totalIncome || 0,
+          // yesterdayIncome: data.summary.yesterdayIncome || 0,
           totalViews: data.summary.totalViews || 0,
           yesterdayViews: data.summary.yesterdayViews || 0,
           totalFans: data.summary.totalFans || 0,
@@ -478,7 +478,7 @@ onMounted(() => {
   
   .stats-row {
     display: grid;
-    grid-template-columns: repeat(9, 1fr);
+    grid-template-columns: repeat(7, 1fr);
     gap: 0;
     margin-bottom: 20px;
     background: #fff;

+ 24 - 30
client/src/views/Analytics/Platform/index.vue

@@ -173,14 +173,13 @@ const serverStore = useServerStore();
 const loading = ref(false);
 const chartLoading = ref(false);
 
-// 日期筛选
-const startDate = ref(dayjs().format('YYYY-MM-DD'));
-const endDate = ref(dayjs().format('YYYY-MM-DD'));
+// 日期筛选(默认昨天)
+const startDate = ref(dayjs().subtract(1, 'day').format('YYYY-MM-DD'));
+const endDate = ref(dayjs().subtract(1, 'day').format('YYYY-MM-DD'));
 const activeQuickBtn = ref('yesterday');
 
-// 快捷日期按钮(今天/昨天/前天/近三天/近七天/近一个月
+// 快捷日期按钮(去掉“今天”
 const quickDateBtns = [
-  { label: '今天', value: 'today' },
   { label: '昨天', value: 'yesterday' },
   { label: '前天', value: 'beforeYesterday' },
   { label: '近三天', value: 'last3days' },
@@ -249,10 +248,6 @@ function handleQuickDate(type: string) {
   const today = dayjs();
   
   switch (type) {
-    case 'today':
-      startDate.value = today.format('YYYY-MM-DD');
-      endDate.value = today.format('YYYY-MM-DD');
-      break;
     case 'yesterday':
       startDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
       endDate.value = today.subtract(1, 'day').format('YYYY-MM-DD');
@@ -277,6 +272,9 @@ function handleQuickDate(type: string) {
       endDate.value = today.format('YYYY-MM-DD');
       break;
   }
+
+  // 点击时间选项后直接刷新列表(无需再点“查询”)
+  loadData();
 }
 
 // 查询
@@ -311,28 +309,24 @@ function handleDetail(row: PlatformData) {
   console.log('[Platform] handleDetail called, row:', row);
   console.log('[Platform] platform:', row.platform);
   console.log('[Platform] startDate:', startDate.value, 'endDate:', endDate.value);
-  
-  try {
-    // 跳转到平台详情页面
-    router.push({
-      name: 'AnalyticsPlatformDetail',
-      params: {
-        platform: row.platform,
-      },
-      query: {
-        startDate: startDate.value,
-        endDate: endDate.value,
-      },
-    }).then(() => {
-      console.log('[Platform] 路由跳转成功');
-    }).catch((error) => {
-      console.error('[Platform] 路由跳转失败:', error);
+
+  // 直接按完整路径跳转,避免动态路由参数解析问题
+  const path = `/analytics/platform-detail/${row.platform}`;
+  router.push({
+    path,
+    query: {
+      startDate: startDate.value,
+      endDate: endDate.value,
+    },
+  }).then(() => {
+    console.log('[Platform] 路由跳转成功, path:', path);
+  }).catch((error) => {
+    // 仅在真正的错误时提示,重复导航等忽略
+    console.error('[Platform] 路由跳转失败:', error);
+    if (error && error.name !== 'NavigationDuplicated') {
       ElMessage.error('跳转失败: ' + (error?.message || '未知错误'));
-    });
-  } catch (error: any) {
-    console.error('[Platform] handleDetail 错误:', error);
-    ElMessage.error('跳转失败: ' + (error?.message || '未知错误'));
-  }
+    }
+  });
 }
 
 // 加载平台详情

+ 11 - 5
client/src/views/Analytics/PlatformDetail/index.vue

@@ -205,12 +205,15 @@ const quickDateBtns = [
   { label: '近一个月', value: 'lastMonth' },
 ];
 
-// 可用平台
+// 可用平台(只保留与总览列表一致的 4 个:抖音、百家号、视频号、小红书)
 const availablePlatforms = computed(() => {
-  return Object.entries(PLATFORMS).map(([key, value]) => ({
-    value: key,
-    label: value.name,
-  }));
+  const allowed: PlatformType[] = ['douyin', 'baijiahao', 'weixin_video', 'xiaohongshu'];
+  return Object.entries(PLATFORMS)
+    .filter(([key]) => allowed.includes(key as PlatformType))
+    .map(([key, value]) => ({
+      value: key,
+      label: value.name,
+    }));
 });
 
 // 汇总数据
@@ -281,6 +284,9 @@ function handleQuickDate(type: string) {
       endDate.value = today.format('YYYY-MM-DD');
       break;
   }
+
+  // 选择快捷日期后,自动刷新数据,无需再点“查询”
+  loadData();
 }
 
 // 查询

+ 2 - 14
server/python/export_work_day_overview_xlsx.py

@@ -11,8 +11,6 @@
     {
       "account": "xxx",
       "platform": "douyin",
-      "totalIncome": 0.0,
-      "yesterdayIncome": 0.0,
       "totalViews": 0,
       "yesterdayViews": 0,
       "fansCount": 0,
@@ -53,8 +51,6 @@ except Exception as e:  # pragma: no cover - 仅作为运行时保护
 HEADERS = [
   "账号",
   "平台",
-  "总收益",
-  "昨日收益",
   "总播放量",
   "昨日播放量",
   "粉丝数",
@@ -64,7 +60,7 @@ HEADERS = [
   "更新时间",
 ]
 
-COL_WIDTHS = [22, 12, 12, 12, 12, 12, 10, 10, 10, 10, 20]
+COL_WIDTHS = [22, 12, 12, 12, 10, 10, 10, 10, 20]
 
 
 def _safe_int(v):
@@ -173,8 +169,6 @@ def build_xlsx(accounts):
     ws.append([
       _safe_str(a.get("account")),
       _safe_str(platform_cn),
-      _safe_float(a.get("totalIncome")),
-      _safe_float(a.get("yesterdayIncome")),
       _safe_int(a.get("totalViews")),
       _safe_int(a.get("yesterdayViews")),
       _safe_int(a.get("fansCount")) or 0,
@@ -184,8 +178,7 @@ def build_xlsx(accounts):
       _safe_str(_format_date_only(a.get("updateTime"))),
     ])
 
-  money_cols = {"C", "D"}
-  int_cols = {"E", "F", "G", "H", "I", "J"}
+  int_cols = {"C", "D", "E", "F", "G", "H"}
 
   for row in range(2, ws.max_row + 1):
     for col in range(1, len(HEADERS) + 1):
@@ -196,11 +189,6 @@ def build_xlsx(accounts):
       else:
         c.alignment = center
 
-    for c_letter in money_cols:
-      c = ws[f"{c_letter}{row}"]
-      if c.value is not None:
-        c.number_format = "0.00"
-
     for c_letter in int_cols:
       c = ws[f"{c_letter}{row}"]
       if c.value is not None:

+ 7 - 16
server/src/routes/analytics.ts

@@ -1,12 +1,14 @@
 import { Router } from 'express';
 import { query } from 'express-validator';
 import { AnalyticsService } from '../services/AnalyticsService.js';
+import { WorkDayStatisticsService } from '../services/WorkDayStatisticsService.js';
 import { authenticate } from '../middleware/auth.js';
 import { asyncHandler } from '../middleware/error.js';
 import { validateRequest } from '../middleware/validate.js';
 
 const router = Router();
 const analyticsService = new AnalyticsService();
+const workDayStatisticsService = new WorkDayStatisticsService();
 
 router.use(authenticate);
 
@@ -171,24 +173,13 @@ router.get(
     const { startDate, endDate } = req.query;
 
     try {
-      const pythonResult = await callPythonAnalyticsApi('/work_day_statistics/platforms', {
-        user_id: req.user!.userId,
-        start_date: String(startDate),
-        end_date: String(endDate),
+      // 口径切换:平台数据不再转发 Python,直接使用 Node 本地的 user_day_statistics 统计
+      const data = await workDayStatisticsService.getStatisticsByPlatform(req.user!.userId, {
+        startDate: String(startDate),
+        endDate: String(endDate),
       });
 
-      if (!pythonResult || pythonResult.success === false) {
-        return res.status(500).json({
-          success: false,
-          error: pythonResult?.error || '获取平台数据失败',
-          message: pythonResult?.error || '获取平台数据失败',
-        });
-      }
-
-      return res.json({
-        success: true,
-        data: pythonResult.data,
-      });
+      return res.json({ success: true, data });
     } catch (error: any) {
       // 捕获并返回详细的错误信息
       console.error('[platforms-from-python] 调用 Python API 失败:', error);

+ 20 - 3
server/src/routes/workDayStatistics.ts

@@ -7,6 +7,7 @@ import { authenticate } from '../middleware/auth.js';
 import { asyncHandler } from '../middleware/error.js';
 import { validateRequest } from '../middleware/validate.js';
 import { WorkDayStatisticsService } from '../services/WorkDayStatisticsService.js';
+import { logger } from '../utils/logger.js';
 
 /**
  * Work day statistics(原 Python 统计接口的 Node 版本)
@@ -210,8 +211,6 @@ router.get(
       accounts: accounts.map(a => ({
         account: a.nickname || a.username || '',
         platform: a.platform || '',
-        totalIncome: a.totalIncome,
-        yesterdayIncome: a.yesterdayIncome,
         totalViews: a.totalViews,
         yesterdayViews: a.yesterdayViews,
         fansCount: a.fansCount,
@@ -259,10 +258,28 @@ router.get(
   ],
   asyncHandler(async (req, res) => {
     const { platform, startDate, endDate } = req.query;
-    const data = await workDayStatisticsService.getPlatformDetail(req.user!.userId, platform as string, {
+    const userId = req.user!.userId;
+
+    const data = await workDayStatisticsService.getPlatformDetail(userId, platform as string, {
       startDate: startDate as string,
       endDate: endDate as string,
     });
+
+    // 调试日志:确认接口是否有每日汇总数据返回
+    try {
+      logger.info('[WorkDayStatistics] /platform-detail response preview', {
+        userId,
+        platform,
+        startDate,
+        endDate,
+        dailyCount: data?.dailyData?.length ?? 0,
+        firstDailyItems: (data?.dailyData || []).slice(0, 5),
+        summary: data?.summary,
+      });
+    } catch {
+      // 日志失败不影响正常返回
+    }
+
     res.json({ success: true, data });
   })
 );

+ 353 - 524
server/src/services/WorkDayStatisticsService.ts

@@ -1,5 +1,5 @@
 import { AppDataSource, WorkDayStatistics, Work, PlatformAccount, UserDayStatistics } from '../models/index.js';
-import { Between, In } from 'typeorm';
+import { In } from 'typeorm';
 import { logger } from '../utils/logger.js';
 
 interface StatisticsItem {
@@ -306,154 +306,87 @@ export class WorkDayStatisticsService {
 
     const endDateStr = endDate ? endDate : this.formatDate(dateEnd);
     const startDateStr = startDate ? startDate : this.formatDate(dateStart);
-    const isSingleDay = startDateStr === endDateStr;
-    // 单日查询(如“昨天”)按“当日增量”口径:当日粉丝 - 前一日粉丝
-    const startBaselineStr = (() => {
-      if (!isSingleDay) return startDateStr;
-      const d = new Date(endDateStr);
-      d.setDate(d.getDate() - 1);
-      return this.formatDate(d);
-    })();
-
-    // 获取用户的所有账号
-    const accounts = await this.accountRepository.find({
-      where: { userId },
-    });
 
-    // 按平台聚合数据:Map<platform, { fansCount, fansIncrease, viewsCount, likesCount, commentsCount, collectsCount, latestUpdateTime }>
-    const platformMap = new Map<string, {
-      fansCount: number;
-      fansIncrease: number;
+    /**
+     * 口径变更:user_day_statistics 的 play/comment/like/collect/fans_increase 等字段为“每日单独值”
+     * 因此:
+     * - 区间统计:直接按日期范围 SUM
+     * - 单日统计:startDate=endDate 时,也同样按该日 SUM(无需再做“累计差”)
+     * 粉丝数:使用 platform_accounts.fans_count(当前值)
+     */
+    const [fansRows, udsRows] = await Promise.all([
+      this.accountRepository
+        .createQueryBuilder('pa')
+        .select('pa.platform', 'platform')
+        .addSelect('COALESCE(SUM(pa.fansCount), 0)', 'fansCount')
+        .where('pa.userId = :userId', { userId })
+        .groupBy('pa.platform')
+        .getRawMany(),
+      this.userDayStatisticsRepository
+        .createQueryBuilder('uds')
+        .innerJoin(PlatformAccount, 'pa', 'pa.id = uds.account_id')
+        .select('pa.platform', 'platform')
+        .addSelect('COALESCE(SUM(uds.play_count), 0)', 'viewsCount')
+        .addSelect('COALESCE(SUM(uds.comment_count), 0)', 'commentsCount')
+        .addSelect('COALESCE(SUM(uds.like_count), 0)', 'likesCount')
+        .addSelect('COALESCE(SUM(uds.collect_count), 0)', 'collectsCount')
+        .addSelect('COALESCE(SUM(uds.fans_increase), 0)', 'fansIncrease')
+        .addSelect('MAX(uds.updated_at)', 'latestUpdateTime')
+        .where('pa.user_id = :userId', { userId })
+        .andWhere('DATE(uds.record_date) >= :startDate', { startDate: startDateStr })
+        .andWhere('DATE(uds.record_date) <= :endDate', { endDate: endDateStr })
+        .groupBy('pa.platform')
+        .getRawMany(),
+    ]);
+
+    const fansMap = new Map<string, number>();
+    for (const row of fansRows || []) {
+      const platform = String(row.platform || '');
+      if (!platform) continue;
+      fansMap.set(platform, Number(row.fansCount) || 0);
+    }
+
+    const statMap = new Map<string, {
       viewsCount: number;
-      likesCount: number;
       commentsCount: number;
+      likesCount: number;
       collectsCount: number;
-      latestUpdateTime: Date | null;
+      fansIncrease: number;
+      latestUpdateTime?: string | Date | null;
     }>();
-
-    // 遍历每个账号,计算该账号的数据,然后累加到对应平台
-    for (const account of accounts) {
-      // 获取该账号的作品列表(用于按“所有作品累计值之和”计算)
-      const works = await this.workRepository.find({
-        where: { accountId: account.id },
-        select: ['id'],
+    for (const row of udsRows || []) {
+      const platform = String(row.platform || '');
+      if (!platform) continue;
+      statMap.set(platform, {
+        viewsCount: Number(row.viewsCount) || 0,
+        commentsCount: Number(row.commentsCount) || 0,
+        likesCount: Number(row.likesCount) || 0,
+        collectsCount: Number(row.collectsCount) || 0,
+        fansIncrease: Number(row.fansIncrease) || 0,
+        latestUpdateTime: row.latestUpdateTime ?? null,
       });
-      const workIds = works.map(w => w.id);
-
-      // ===== 粉丝口径修正 =====
-      // 取 endDate 当天粉丝(若当天没有记录,则取 <= endDate 的最近一条)
-      const endUserStat =
-        (await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :d', { d: endDateStr })
-          .getOne()) ??
-        (await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) <= :d', { d: endDateStr })
-          .orderBy('uds.record_date', 'DESC')
-          .getOne());
-
-      const endFans = endUserStat?.fansCount ?? account.fansCount ?? 0;
-
-      // 取 baseline 当天粉丝(若没有记录,则取 <= baseline 的最近一条)
-      const baselineUserStat =
-        (await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :d', { d: startBaselineStr })
-          .getOne()) ??
-        (await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) <= :d', { d: startBaselineStr })
-          .orderBy('uds.record_date', 'DESC')
-          .getOne());
-
-      const baselineFans = baselineUserStat?.fansCount ?? endFans;
-      // 涨粉量允许为负数(掉粉),不做截断
-      const accountFansIncrease = endFans - baselineFans;
-
-      // ===== 播放/点赞/评论/收藏口径修正 =====
-      // 单日:当日累计 - 前一日累计
-      // 区间:end 日累计 - start 日累计
-      const [endSums, baseSums] = await Promise.all([
-        this.getWorkSumsAtDate(workIds, endDateStr),
-        this.getWorkSumsAtDate(workIds, startBaselineStr),
-      ]);
-
-      const accountViewsIncrease = endSums.views - baseSums.views;
-      const accountLikesIncrease = endSums.likes - baseSums.likes;
-      const accountCommentsIncrease = endSums.comments - baseSums.comments;
-      const accountCollectsIncrease = endSums.collects - baseSums.collects;
-
-      // 累加到平台聚合数据
-      const platformKey = account.platform;
-      if (!platformMap.has(platformKey)) {
-        platformMap.set(platformKey, {
-          fansCount: 0,
-          fansIncrease: 0,
-          viewsCount: 0,
-          likesCount: 0,
-          commentsCount: 0,
-          collectsCount: 0,
-          latestUpdateTime: null,
-        });
-      }
-
-      const platformStat = platformMap.get(platformKey)!;
-      platformStat.fansCount += endFans;
-      platformStat.fansIncrease += accountFansIncrease;
-      platformStat.viewsCount += accountViewsIncrease;
-      platformStat.likesCount += accountLikesIncrease;
-      platformStat.commentsCount += accountCommentsIncrease;
-      platformStat.collectsCount += accountCollectsIncrease;
-
-      // 查询该账号在当前时间段内的最晚 updated_at
-      const latestUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) >= :startDate', { startDate: startDateStr })
-        .andWhere('DATE(uds.record_date) <= :endDate', { endDate: endDateStr })
-        .orderBy('uds.updated_at', 'DESC')
-        .getOne();
-
-      if (latestUserStat && latestUserStat.updatedAt) {
-        // 更新平台的最晚更新时间
-        if (!platformStat.latestUpdateTime || latestUserStat.updatedAt > platformStat.latestUpdateTime) {
-          platformStat.latestUpdateTime = latestUserStat.updatedAt;
-        }
-      }
     }
 
-    // 转换为数组格式,按粉丝数降序排序
-    const platformData: PlatformStatItem[] = Array.from(platformMap.entries()).map(([platform, stat]) => {
-      // 格式化更新时间为 "MM-DD HH:mm" 格式
-      let updateTime: string | undefined;
-      if (stat.latestUpdateTime) {
-        const date = new Date(stat.latestUpdateTime);
-        const month = String(date.getMonth() + 1).padStart(2, '0');
-        const day = String(date.getDate()).padStart(2, '0');
-        const hours = String(date.getHours()).padStart(2, '0');
-        const minutes = String(date.getMinutes()).padStart(2, '0');
-        updateTime = `${month}-${day} ${hours}:${minutes}`;
-      }
+    const platforms = new Set<string>([...fansMap.keys(), ...statMap.keys()]);
+
+    const platformData: PlatformStatItem[] = Array.from(platforms).map((platform) => {
+      const stat = statMap.get(platform);
+      const fansCount = fansMap.get(platform) ?? 0;
+      const latestUpdate = stat?.latestUpdateTime ? new Date(stat.latestUpdateTime as any) : null;
 
       return {
         platform,
-        fansCount: stat.fansCount,
-        fansIncrease: stat.fansIncrease,
-        viewsCount: stat.viewsCount,
-        likesCount: stat.likesCount,
-        commentsCount: stat.commentsCount,
-        collectsCount: stat.collectsCount,
-        updateTime,
+        fansCount,
+        fansIncrease: stat?.fansIncrease ?? 0,
+        viewsCount: stat?.viewsCount ?? 0,
+        likesCount: stat?.likesCount ?? 0,
+        commentsCount: stat?.commentsCount ?? 0,
+        collectsCount: stat?.collectsCount ?? 0,
+        updateTime: latestUpdate ? latestUpdate.toISOString() : undefined,
       };
     });
 
-    platformData.sort((a, b) => b.fansCount - a.fansCount);
-
+    platformData.sort((a, b) => (b.fansCount || 0) - (a.fansCount || 0));
     return platformData;
   }
 
@@ -518,6 +451,86 @@ export class WorkDayStatisticsService {
   }
 
   /**
+   * 获取每个账号在指定日期(<= targetDate)时,user_day_statistics 的“最新一条”
+   * 主要用于:总粉丝、更新时间等需要“最新状态”的字段。
+   */
+  private async getLatestUserDayStatsAtDate(
+    accountIds: number[],
+    targetDate: string
+  ): Promise<Map<number, { fansCount: number; updatedAt: Date | null; recordDate: Date | null }>> {
+    const map = new Map<number, { fansCount: number; updatedAt: Date | null; recordDate: Date | null }>();
+    if (!accountIds.length) return map;
+
+    // MySQL: 派生表先取每个账号 <= targetDate 的最新日期,再回连取该日数据
+    const placeholders = accountIds.map(() => '?').join(',');
+    const sql = `
+      SELECT
+        uds.account_id AS accountId,
+        uds.fans_count AS fansCount,
+        uds.record_date AS recordDate,
+        uds.updated_at AS updatedAt
+      FROM user_day_statistics uds
+      INNER JOIN (
+        SELECT account_id, MAX(record_date) AS record_date
+        FROM user_day_statistics
+        WHERE account_id IN (${placeholders})
+          AND record_date <= ?
+        GROUP BY account_id
+      ) latest
+      ON latest.account_id = uds.account_id AND latest.record_date = uds.record_date
+    `;
+
+    const rows: any[] = await AppDataSource.query(sql, [...accountIds, targetDate]);
+    for (const row of rows || []) {
+      const accountId = Number(row.accountId) || 0;
+      if (!accountId) continue;
+      const fansCount = Number(row.fansCount) || 0;
+      const recordDate = row.recordDate ? new Date(row.recordDate) : null;
+      const updatedAt = row.updatedAt ? new Date(row.updatedAt) : null;
+      map.set(accountId, { fansCount, updatedAt, recordDate });
+    }
+
+    return map;
+  }
+
+  /**
+   * 获取指定日期(= dateStr)每个账号在 user_day_statistics 的数据(用于“昨日”口径的一一对应)。
+   */
+  private async getUserDayStatsByExactDate(
+    accountIds: number[],
+    dateStr: string
+  ): Promise<Map<number, { playCount: number; commentCount: number; likeCount: number; fansIncrease: number; updatedAt: Date | null }>> {
+    const map = new Map<number, { playCount: number; commentCount: number; likeCount: number; fansIncrease: number; updatedAt: Date | null }>();
+    if (!accountIds.length) return map;
+
+    const rows = await this.userDayStatisticsRepository
+      .createQueryBuilder('uds')
+      .select('uds.account_id', 'accountId')
+      .addSelect('uds.play_count', 'playCount')
+      .addSelect('uds.comment_count', 'commentCount')
+      .addSelect('uds.like_count', 'likeCount')
+      .addSelect('uds.fans_increase', 'fansIncrease')
+      .addSelect('uds.updated_at', 'updatedAt')
+      .where('uds.account_id IN (:...accountIds)', { accountIds })
+      .andWhere('DATE(uds.record_date) = :d', { d: dateStr })
+      .getRawMany();
+
+    for (const row of rows || []) {
+      const accountId = Number(row.accountId) || 0;
+      if (!accountId) continue;
+      map.set(accountId, {
+        playCount: Number(row.playCount) || 0,
+        commentCount: Number(row.commentCount) || 0,
+        likeCount: Number(row.likeCount) || 0,
+        fansIncrease: Number(row.fansIncrease) || 0,
+        updatedAt: row.updatedAt ? new Date(row.updatedAt) : null,
+      });
+    }
+
+    return map;
+  }
+
+  /**
    * 获取数据总览
    * 返回账号列表和汇总统计数据
    */
@@ -577,6 +590,32 @@ export class WorkDayStatisticsService {
     
     logger.info(`[WorkDayStatistics] getOverview - userId: ${userId}, today: ${todayStr}, yesterday: ${yesterdayStr}`);
 
+    const accountIds = accounts.map(a => a.id);
+
+    // 账号总数:platform_accounts 中 user_id 对应数量(等价于 accounts.length)
+    const totalAccounts = accounts.length;
+
+    // 列表“总播放/汇总总播放”:统一从 works.play_count 聚合(累计)
+    const worksPlayRows = accountIds.length
+      ? await this.workRepository
+          .createQueryBuilder('w')
+          .select('w.accountId', 'accountId')
+          .addSelect('COALESCE(SUM(w.playCount), 0)', 'playCount')
+          .where('w.userId = :userId', { userId })
+          .andWhere('w.accountId IN (:...accountIds)', { accountIds })
+          .groupBy('w.accountId')
+          .getRawMany()
+      : [];
+    const totalPlayMap = new Map<number, number>();
+    for (const row of worksPlayRows || []) {
+      totalPlayMap.set(Number(row.accountId) || 0, Number(row.playCount) || 0);
+    }
+
+    // “昨日”口径:只取 user_day_statistics 指定日期那一行,一一对应
+    const yesterdayUdsMap = await this.getUserDayStatsByExactDate(accountIds, yesterdayStr);
+
+    // 粉丝数口径:直接取 platform_accounts.fans_count(不跟随 user_day_statistics)
+
     const accountList: Array<{
       id: number;
       nickname: string;
@@ -584,6 +623,7 @@ export class WorkDayStatisticsService {
       avatarUrl: string | null;
       platform: string;
       groupId: number | null;
+      groupName?: string | null;
       fansCount: number;
       totalIncome: number | null;
       yesterdayIncome: number | null;
@@ -597,7 +637,6 @@ export class WorkDayStatisticsService {
     }> = [];
 
     // 汇总统计数据
-    let totalAccounts = 0;
     let totalIncome = 0;
     let yesterdayIncome = 0;
     let totalViews = 0;
@@ -608,250 +647,16 @@ export class WorkDayStatisticsService {
     let yesterdayFansIncrease = 0;
 
     for (const account of accounts) {
-      // 获取该账号的所有作品ID
-      const works = await this.workRepository.find({
-        where: { accountId: account.id },
-        select: ['id'],
-      });
+      const accountTotalViews = totalPlayMap.get(account.id) ?? 0;
+      const yesterdayUds = yesterdayUdsMap.get(account.id);
 
-      if (works.length === 0) {
-        // 如果没有作品,只返回账号基本信息
-        // 从 user_day_statistics 表获取账号的最新粉丝数
-        // 使用日期字符串直接查询(更可靠,避免时区问题)
-        const todayUserStat = await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :today', { today: todayStr })
-          .getOne();
-        
-        const yesterdayUserStat = await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :yesterday', { yesterday: yesterdayStr })
-          .getOne();
-        
-        // 获取今天的粉丝数:优先使用统计数据,如果没有则使用账号表的当前值
-        let accountFansCount: number;
-        if (todayUserStat) {
-          accountFansCount = todayUserStat.fansCount || 0;
-        } else {
-          accountFansCount = account.fansCount || 0;
-        }
-        
-        // 计算昨日涨粉(今天粉丝数 - 昨天粉丝数)
-        let accountYesterdayFansIncrease: number;
-        if (yesterdayUserStat) {
-          // 昨天有数据,直接使用
-          const yesterdayFans = yesterdayUserStat.fansCount || 0;
-          accountYesterdayFansIncrease = Math.max(0, accountFansCount - yesterdayFans);
-          logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - yesterday: ${yesterdayFans}, today: ${accountFansCount}, increase: ${accountYesterdayFansIncrease}`);
-        } else {
-          // 昨天没有数据,查找最近一天(早于今天)的数据作为基准
-          const recentUserStat = await this.userDayStatisticsRepository
-            .createQueryBuilder('uds')
-            .where('uds.account_id = :accountId', { accountId: account.id })
-            .andWhere('DATE(uds.record_date) < :today', { today: todayStr })
-            .orderBy('uds.record_date', 'DESC')
-            .getOne();
-          
-          if (recentUserStat) {
-            const recentFans = recentUserStat.fansCount || 0;
-            accountYesterdayFansIncrease = Math.max(0, accountFansCount - recentFans);
-            logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - using recent: ${recentFans}, today: ${accountFansCount}, increase: ${accountYesterdayFansIncrease}`);
-          } else {
-            // 完全没有历史数据,涨粉为 0
-            accountYesterdayFansIncrease = 0;
-            logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - no history data, increase: 0`);
-          }
-        }
-        
-        accountList.push({
-          id: account.id,
-          nickname: account.accountName || '',
-          username: account.accountId || '',
-          avatarUrl: account.avatarUrl,
-          platform: account.platform,
-          groupId: account.groupId,
-        groupName: account.group?.name ?? null,
-          fansCount: accountFansCount,
-          totalIncome: null,
-          yesterdayIncome: null,
-          totalViews: null,
-          yesterdayViews: null,
-          yesterdayComments: 0,
-          yesterdayLikes: 0,
-          yesterdayFansIncrease: accountYesterdayFansIncrease,
-          updateTime: account.updatedAt.toISOString(),
-          status: account.status,
-        });
-        
-        // 即使没有作品,也要累加账号的粉丝数到总粉丝数
-        totalAccounts++;
-        totalFans += accountFansCount;
-        yesterdayFansIncrease += accountYesterdayFansIncrease;
-        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')
-        .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);
-      }
-
-      // 获取昨天和今天的数据来计算增量(不再包含粉丝数,粉丝数从 user_day_statistics 表获取)
-      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')
-        .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')
-        .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)));
-      }
+      const accountFansCount = account.fansCount || 0;
+      const accountYesterdayViews = yesterdayUds?.playCount ?? 0;
+      const accountYesterdayComments = yesterdayUds?.commentCount ?? 0;
+      const accountYesterdayLikes = yesterdayUds?.likeCount ?? 0;
+      const accountYesterdayFansIncrease = yesterdayUds?.fansIncrease ?? 0;
 
-      // 计算昨日增量
-      let accountYesterdayViews = 0;
-      let accountYesterdayComments = 0;
-      let accountYesterdayLikes = 0;
-      let accountYesterdayFansIncrease = 0;
-
-      // 按作品ID汇总(不再包含粉丝数)
-      const yesterdayMap = new Map<number, { play: number; like: number; comment: number }>();
-      const todayMap = new Map<number, { play: number; like: number; comment: 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,
-        });
-      }
-
-      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,
-        });
-      }
-      
-      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 };
-        const yesterdayData = yesterdayMap.get(workId) || { play: 0, like: 0, comment: 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}`);
-
-      // 从 user_day_statistics 表获取账号的最新粉丝数和作品数
-      // 优先使用统计数据,如果没有则使用账号表的当前值
-      
-      // 使用日期字符串直接查询(更可靠,避免时区问题)
-      // todayStr 和 yesterdayStr 是中国时区的日期字符串(如 "2026-01-26")
-      // 直接使用字符串查询,TypeORM 会自动转换为 DATE 类型进行比较
-      const todayUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) = :today', { today: todayStr })
-        .getOne();
-      
-      const yesterdayUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) = :yesterday', { yesterday: yesterdayStr })
-        .getOne();
-      
-      // 获取今天的粉丝数:优先使用统计数据,如果没有则使用账号表的当前值
-      let accountFansCount: number;
-      let accountWorksCount: number;
-      if (todayUserStat) {
-        accountFansCount = todayUserStat.fansCount || 0;
-        accountWorksCount = todayUserStat.worksCount || 0;
-      } else {
-        // 今天没有统计数据,使用账号表的当前值
-        accountFansCount = account.fansCount || 0;
-        accountWorksCount = account.worksCount || 0;
-      }
-      
-      // 计算昨日涨粉(今天粉丝数 - 昨天粉丝数)
-      let yesterdayFans: number;
-      if (yesterdayUserStat) {
-        // 昨天有数据,直接使用
-        yesterdayFans = yesterdayUserStat.fansCount || 0;
-        logger.debug(`[WorkDayStatistics] Account ${account.id} - yesterday has data: ${yesterdayFans}, today: ${accountFansCount}`);
-      } else {
-        // 昨天没有数据,查找最近一天(早于今天)的数据作为基准
-        const recentUserStat = await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) < :today', { today: todayStr })
-          .orderBy('uds.record_date', 'DESC')
-          .getOne();
-        
-        if (recentUserStat) {
-          yesterdayFans = recentUserStat.fansCount || 0;
-          logger.debug(`[WorkDayStatistics] Account ${account.id} - using recent data: ${yesterdayFans}, today: ${accountFansCount}`);
-        } else {
-          // 完全没有历史数据,用账号表的当前粉丝数作为基准(涨粉为 0)
-          yesterdayFans = accountFansCount;
-          logger.debug(`[WorkDayStatistics] Account ${account.id} - no history data, using current: ${accountFansCount}`);
-        }
-      }
-      
-      // 计算涨粉数(今天 - 昨天),确保不为负数
-      accountYesterdayFansIncrease = Math.max(0, accountFansCount - yesterdayFans);
-      logger.info(`[WorkDayStatistics] Account ${account.id} - fans increase: ${accountFansCount} - ${yesterdayFans} = ${accountYesterdayFansIncrease}`);
+      const updateTime = (yesterdayUds?.updatedAt ?? account.updatedAt).toISOString();
 
       accountList.push({
         id: account.id,
@@ -862,22 +667,20 @@ export class WorkDayStatisticsService {
         groupId: account.groupId,
         groupName: account.group?.name ?? null,
         fansCount: accountFansCount,
-        totalIncome: null, // 收益数据需要从其他表获取,暂时为null
+        totalIncome: null,
         yesterdayIncome: null,
-        totalViews: accountTotalViews > 0 ? accountTotalViews : null,
-        yesterdayViews: accountYesterdayViews > 0 ? accountYesterdayViews : null,
+        totalViews: accountTotalViews,
+        yesterdayViews: accountYesterdayViews,
         yesterdayComments: accountYesterdayComments,
         yesterdayLikes: accountYesterdayLikes,
         yesterdayFansIncrease: accountYesterdayFansIncrease,
-        updateTime: account.updatedAt.toISOString(),
+        updateTime,
         status: account.status,
       });
 
-      // 累加汇总数据
-      totalAccounts++;
       totalViews += accountTotalViews;
-      yesterdayViews += accountYesterdayViews;
       totalFans += accountFansCount;
+      yesterdayViews += accountYesterdayViews;
       yesterdayComments += accountYesterdayComments;
       yesterdayLikes += accountYesterdayLikes;
       yesterdayFansIncrease += accountYesterdayFansIncrease;
@@ -973,22 +776,162 @@ export class WorkDayStatisticsService {
       };
     }
 
-    // 计算汇总统计
-    let totalAccounts = 0;
+    /**
+     * 口径变更:user_day_statistics 的各项数据为“每日单独值”,不再是累计值
+     * 因此平台详情:
+     * - 区间汇总:直接 SUM(user_day_statistics.*)(按账号、按天)
+     * - 每日汇总:按 record_date 分组 SUM
+     */
+    const accountIds = accounts.map(a => a.id);
+    const totalAccounts = accounts.length;
+
+    const [dailyRows, perAccountRows] = await Promise.all([
+      // 按日期维度汇总(每天所有账号数据之和)
+      // 这里直接使用原生 SQL,以保证和文档/手动验证时看到的 SQL 完全一致:
+      //
+      // SELECT
+      //   uds.record_date AS recordDate,
+      //   COALESCE(SUM(uds.play_count), 0)    AS viewsCount,
+      //   COALESCE(SUM(uds.comment_count), 0) AS commentsCount,
+      //   COALESCE(SUM(uds.like_count), 0)    AS likesCount,
+      //   COALESCE(SUM(uds.fans_increase), 0) AS fansIncrease
+      // FROM user_day_statistics uds
+      // WHERE uds.account_id IN (...)
+      //   AND uds.record_date >= ?
+      //   AND uds.record_date <= ?
+      // GROUP BY uds.record_date
+      // ORDER BY uds.record_date ASC;
+      (async () => {
+        if (!accountIds.length) return [];
+
+        const inPlaceholders = accountIds.map(() => '?').join(',');
+        const sql = `
+          SELECT
+            uds.record_date AS recordDate,
+            COALESCE(SUM(uds.play_count), 0)      AS viewsCount,
+            COALESCE(SUM(uds.comment_count), 0)   AS commentsCount,
+            COALESCE(SUM(uds.like_count), 0)      AS likesCount,
+            COALESCE(SUM(uds.fans_increase), 0)   AS fansIncrease
+          FROM user_day_statistics uds
+          WHERE uds.account_id IN (${inPlaceholders})
+            AND uds.record_date >= ?
+            AND uds.record_date <= ?
+          GROUP BY uds.record_date
+          ORDER BY uds.record_date ASC
+        `;
+
+        const params = [...accountIds, startDateStr, endDateStr];
+        return await AppDataSource.query(sql, params);
+      })(),
+      // 按账号维度汇总(区间内所有天的和)
+      this.userDayStatisticsRepository
+        .createQueryBuilder('uds')
+        .select('uds.account_id', 'accountId')
+        .addSelect('COUNT(1)', 'rowCount')
+        .addSelect('COALESCE(SUM(uds.play_count), 0)', 'viewsCount')
+        .addSelect('COALESCE(SUM(uds.comment_count), 0)', 'commentsCount')
+        .addSelect('COALESCE(SUM(uds.like_count), 0)', 'likesCount')
+        .addSelect('COALESCE(SUM(uds.fans_increase), 0)', 'fansIncrease')
+        .addSelect('MAX(uds.updated_at)', 'latestUpdateTime')
+        .where('uds.account_id IN (:...accountIds)', { accountIds })
+        .andWhere('DATE(uds.record_date) >= :startDate', { startDate: startDateStr })
+        .andWhere('DATE(uds.record_date) <= :endDate', { endDate: endDateStr })
+        .groupBy('uds.account_id')
+        .getRawMany(),
+    ]);
+
+    // ===== 按日期汇总:每日汇总数据 =====
+    const dailyMap = new Map<string, { views: number; comments: number; likes: number; fansIncrease: number }>();
+    for (const row of dailyRows || []) {
+      if (!row.recordDate) continue;
+
+      /**
+       * 注意:record_date 在实体里是 DATE 类型,TypeORM 读出来通常是 Date 对象。
+       * 之前用 String(row.recordDate).slice(0, 10) 会得到类似 "Wed Jan 28" 这样的字符串前 10 位,
+       * 导致 key 和下面 this.formatDate(cursor) 生成的 "YYYY-MM-DD" 不一致,从而 dailyMap 命中失败,全部变成 0。
+       *
+       * 这里改成显式按本地时间拼出 "YYYY-MM-DD",确保与 startDate/endDate 的格式一致。
+       */
+      let dateKey: string;
+      if (row.recordDate instanceof Date) {
+        const y = row.recordDate.getFullYear();
+        const m = String(row.recordDate.getMonth() + 1).padStart(2, '0');
+        const d = String(row.recordDate.getDate()).padStart(2, '0');
+        dateKey = `${y}-${m}-${d}`;
+      } else {
+        // 数据库如果已经返回字符串,例如 "2026-01-28",直接截前 10 位即可
+        dateKey = String(row.recordDate).slice(0, 10);
+      }
+
+      const prev = dailyMap.get(dateKey) ?? { views: 0, comments: 0, likes: 0, fansIncrease: 0 };
+
+      dailyMap.set(dateKey, {
+        views: prev.views + (Number(row.viewsCount) || 0),
+        comments: prev.comments + (Number(row.commentsCount) || 0),
+        likes: prev.likes + (Number(row.likesCount) || 0),
+        fansIncrease: prev.fansIncrease + (Number(row.fansIncrease) || 0),
+      });
+    }
+
+    // 补齐日期区间(没有数据也返回 0)
+    const dailyData: Array<{
+      date: string;
+      income: number;
+      recommendationCount: number | null;
+      viewsCount: number;
+      commentsCount: number;
+      likesCount: number;
+      fansIncrease: number;
+    }> = [];
+
+    const dStart = new Date(startDateStr);
+    const dEnd = new Date(endDateStr);
+    const cursor = new Date(dStart);
+    while (cursor <= dEnd) {
+      const dateKey = this.formatDate(cursor);
+      const v = dailyMap.get(dateKey) ?? { views: 0, comments: 0, likes: 0, fansIncrease: 0 };
+      dailyData.push({
+        date: dateKey,
+        income: 0,
+        recommendationCount: null,
+        viewsCount: v.views,
+        commentsCount: v.comments,
+        likesCount: v.likes,
+        fansIncrease: v.fansIncrease,
+      });
+      cursor.setDate(cursor.getDate() + 1);
+    }
+
+    // ===== 按账号汇总:账号列表 & 顶部汇总 =====
+    const perAccountMap = new Map<
+      number,
+      { rowCount: number; views: number; comments: number; likes: number; fansIncrease: number; latestUpdateTime: Date | null }
+    >();
+    for (const row of perAccountRows || []) {
+      const accountId = Number(row.accountId) || 0;
+      if (!accountId) continue;
+      perAccountMap.set(accountId, {
+        rowCount: Number(row.rowCount) || 0,
+        views: Number(row.viewsCount) || 0,
+        comments: Number(row.commentsCount) || 0,
+        likes: Number(row.likesCount) || 0,
+        fansIncrease: Number(row.fansIncrease) || 0,
+        latestUpdateTime: row.latestUpdateTime ? new Date(row.latestUpdateTime) : null,
+      });
+    }
+
+    // 顶部汇总:直接用账号维度汇总,确保和“账号详细数据”一致
     let totalViews = 0;
     let totalComments = 0;
     let totalLikes = 0;
     let totalFansIncrease = 0;
+    for (const agg of perAccountMap.values()) {
+      totalViews += agg.views;
+      totalComments += agg.comments;
+      totalLikes += agg.likes;
+      totalFansIncrease += agg.fansIncrease;
+    }
 
-    // 按日期汇总数据
-    const dailyMap = new Map<string, {
-      views: number;
-      comments: number;
-      likes: number;
-      fansIncrease: number;
-    }>();
-
-    // 账号详细列表
     const accountList: Array<{
       id: number;
       nickname: string;
@@ -1002,140 +945,26 @@ export class WorkDayStatisticsService {
       likesCount: number;
       fansIncrease: number;
       updateTime: string;
-    }> = [];
-
-    for (const account of accounts) {
-      const works = await this.workRepository.find({
-        where: { accountId: account.id },
-        select: ['id'],
-      });
-      const workIds = works.map(w => w.id);
-
-      // 获取该账号在日期范围内的每日数据
-      const dateStart = new Date(startDateStr);
-      const dateEnd = new Date(endDateStr);
-      const currentDate = new Date(dateStart);
-
-      while (currentDate <= dateEnd) {
-        const dateStr = this.formatDate(currentDate);
-        
-        // 获取该日期的数据
-        const [daySums, prevDaySums] = await Promise.all([
-          this.getWorkSumsAtDate(workIds, dateStr),
-          this.getWorkSumsAtDate(workIds, this.formatDate(new Date(currentDate.getTime() - 24 * 60 * 60 * 1000))),
-        ]);
-
-        const dayViews = daySums.views - prevDaySums.views;
-        const dayComments = daySums.comments - prevDaySums.comments;
-        const dayLikes = daySums.likes - prevDaySums.likes;
-
-        // 获取粉丝数据
-        const dayUserStat = await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :d', { d: dateStr })
-          .getOne();
-
-        const prevDayUserStat = await this.userDayStatisticsRepository
-          .createQueryBuilder('uds')
-          .where('uds.account_id = :accountId', { accountId: account.id })
-          .andWhere('DATE(uds.record_date) = :d', { d: this.formatDate(new Date(currentDate.getTime() - 24 * 60 * 60 * 1000)) })
-          .getOne();
-
-        const dayFans = (dayUserStat?.fansCount || 0) - (prevDayUserStat?.fansCount || 0);
-
-        if (!dailyMap.has(dateStr)) {
-          dailyMap.set(dateStr, {
-            views: 0,
-            comments: 0,
-            likes: 0,
-            fansIncrease: 0,
-          });
-        }
-
-        const daily = dailyMap.get(dateStr)!;
-        daily.views += Math.max(0, dayViews);
-        daily.comments += Math.max(0, dayComments);
-        daily.likes += Math.max(0, dayLikes);
-        daily.fansIncrease += Math.max(0, dayFans);
-
-        currentDate.setDate(currentDate.getDate() + 1);
-      }
-
-      // 计算账号的总数据(使用 endDate 的数据)
-      const [endSums, startSums] = await Promise.all([
-        this.getWorkSumsAtDate(workIds, endDateStr),
-        this.getWorkSumsAtDate(workIds, startDateStr),
-      ]);
-
-      const accountViews = endSums.views - startSums.views;
-      const accountComments = endSums.comments - startSums.comments;
-      const accountLikes = endSums.likes - startSums.likes;
-
-      // 获取粉丝数据
-      const endUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) <= :d', { d: endDateStr })
-        .orderBy('uds.record_date', 'DESC')
-        .getOne();
-
-      const startUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) <= :d', { d: startDateStr })
-        .orderBy('uds.record_date', 'DESC')
-        .getOne();
-
-      const accountFansIncrease = (endUserStat?.fansCount || 0) - (startUserStat?.fansCount || 0);
-
-      // 获取更新时间
-      const latestUserStat = await this.userDayStatisticsRepository
-        .createQueryBuilder('uds')
-        .where('uds.account_id = :accountId', { accountId: account.id })
-        .andWhere('DATE(uds.record_date) >= :startDate', { startDate: startDateStr })
-        .andWhere('DATE(uds.record_date) <= :endDate', { endDate: endDateStr })
-        .orderBy('uds.updated_at', 'DESC')
-        .getOne();
-
-      const updateTime = latestUserStat?.updatedAt 
-        ? this.formatUpdateTime(latestUserStat.updatedAt)
-        : '';
-
-      accountList.push({
+    }> = accounts.map((account) => {
+      const agg =
+        perAccountMap.get(account.id) ?? { rowCount: 0, views: 0, comments: 0, likes: 0, fansIncrease: 0, latestUpdateTime: null };
+      const updateTime = agg.latestUpdateTime ? this.formatUpdateTime(agg.latestUpdateTime) : '';
+      return {
         id: account.id,
         nickname: account.accountName || '',
         username: account.accountId || '',
         avatarUrl: account.avatarUrl,
         platform: account.platform,
-        income: null, // 收益数据需要从其他表获取
-        recommendationCount: null, // 推荐量(部分平台支持)
-        viewsCount: accountViews > 0 ? accountViews : null,
-        commentsCount: Math.max(0, accountComments),
-        likesCount: Math.max(0, accountLikes),
-        fansIncrease: Math.max(0, accountFansIncrease),
+        income: null,
+        recommendationCount: null,
+        // 没有任何记录时,前端展示“获取失败”,避免把“无数据”误显示成 0
+        viewsCount: agg.rowCount > 0 ? agg.views : null,
+        commentsCount: agg.comments,
+        likesCount: agg.likes,
+        fansIncrease: agg.fansIncrease,
         updateTime,
-      });
-
-      totalAccounts++;
-      totalViews += Math.max(0, accountViews);
-      totalComments += Math.max(0, accountComments);
-      totalLikes += Math.max(0, accountLikes);
-      totalFansIncrease += Math.max(0, accountFansIncrease);
-    }
-
-    // 转换为每日数据数组
-    const dailyData = Array.from(dailyMap.entries())
-      .map(([date, data]) => ({
-        date,
-        income: 0, // 收益数据需要从其他表获取
-        recommendationCount: null, // 推荐量(部分平台支持)
-        viewsCount: data.views,
-        commentsCount: data.comments,
-        likesCount: data.likes,
-        fansIncrease: data.fansIncrease,
-      }))
-      .sort((a, b) => a.date.localeCompare(b.date));
+      };
+    });
 
     return {
       summary: {