Jelajahi Sumber

docs: Web 端统计分析功能设计——基于现有 Admin SPA 扩展(收入趋势/用户增长/转化漏斗/分销 Top10),Phase1 核心统计 2.5 天+Phase2 高级分析 1.5 天

liaoxg 3 bulan lalu
induk
melakukan
f7333a2f93
1 mengubah file dengan 647 tambahan dan 0 penghapusan
  1. 647 0
      docs/web-analytics-design.md

+ 647 - 0
docs/web-analytics-design.md

@@ -0,0 +1,647 @@
+# Web 端统计分析功能设计
+
+> 基于现有 Admin SPA(Vue 3 + Element Plus + ECharts)扩展统计分析功能  
+> 状态:v1.0 Draft
+
+---
+
+## 一、现状评估
+
+### 已有基础设施
+
+**后端(num-server)**
+| 组件 | 位置 | 功能 | 状态 |
+|------|------|------|------|
+| AdminController | `/api/admin/stats` | Dashboard 基础统计 | ✅ 已有 |
+| CommissionService | `getStats(userId)` | 个人佣金统计 | ✅ 已有 |
+| OrderRepository | `findByStatusAndPaidAtBetween` | 时间范围订单查询 | ✅ 已有 |
+| UserRepository | `countByVipEndTimeAfter` | VIP 用户统计 | ✅ 已有 |
+
+**前端(admin/)**
+| 组件 | 位置 | 功能 | 状态 |
+|------|------|------|------|
+| Dashboard 页面 | `admin/src/views/dashboard/index.vue` | 4 个统计卡片 + 柱状图 + 饼图 | ✅ 已有 |
+| ECharts | `^5.4.0` | 图表库 | ✅ 已安装 |
+| Element Plus | `^2.4.0` | UI 组件库 | ✅ 已安装 |
+| Axios | `admin/src/utils/request.ts` | HTTP 客户端 | ✅ 已有 |
+
+### 缺失功能
+
+| 类别 | 缺失内容 | 优先级 |
+|------|---------|--------|
+| **后端** | 时间序列统计接口(按日/周/月分组) | ⭐⭐⭐ |
+| **后端** | 用户增长趋势(按注册时间分组) | ⭐⭐ |
+| **后端** | 收入趋势(按支付时间分组) | ⭐⭐⭐ |
+| **后端** | 转化漏斗(普通用户→C 端→能量师) | ⭐⭐ |
+| **后端** | 分销网络统计(团队层级/佣金分布) | ⭐⭐ |
+| **前端** | 日期范围选择器(快捷 + 自定义) | ⭐⭐⭐ |
+| **前端** | 收入趋势折线图 | ⭐⭐⭐ |
+| **前端** | 用户增长柱状图 | ⭐⭐ |
+| **前端** | 订单状态时间序列(堆叠面积图) | ⭐ |
+| **前端** | 数据导出功能(CSV/Excel) | ⭐ |
+
+---
+
+## 二、设计目标
+
+**Phase 1(核心统计)**
+1. 日期范围筛选(今日/近 7 天/近 30 天/本月/自定义)
+2. 收入趋势折线图(按日/周/月聚合)
+3. 用户增长柱状图(新增用户/VIP 转化)
+4. 订单统计(支付转化率/产品类型分布)
+
+**Phase 2(高级分析)**
+1. 转化漏斗(注册→付费→升级→能量师)
+2. 分销网络分析(团队层级/佣金分布/Top10 推广者)
+3. 提现统计(提现金额/通道分布/失败率)
+4. 数据导出(CSV/Excel)
+
+---
+
+## 三、后端设计
+
+### 3.1 新增统计 Service
+
+**文件:** `num-server/src/main/java/com/etotem/num/service/AnalyticsService.java`
+
+```java
+package com.etotem.num.service;
+
+import org.springframework.stereotype.Service;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Web 端统计分析服务。
+ * 提供时间序列统计、趋势分析、转化漏斗等功能。
+ */
+@Service
+public class AnalyticsService {
+
+    // Dependencies: OrderRepository, UserRepository, CommissionRepository, WithdrawRepository
+
+    /**
+     * 收入时间序列统计。
+     * @param startDate 开始时间
+     * @param endDate 结束时间
+     * @param groupBy 分组维度:"day" | "week" | "month"
+     * @return List<Map> 每个时间点的数据:{date: "2026-06-01", revenue: 13100, orderCount: 5}
+     */
+    public List<Map<String, Object>> getRevenueTrend(LocalDateTime startDate, 
+                                                      LocalDateTime endDate, 
+                                                      String groupBy) {
+        // 1. 查询时间范围内所有已支付订单
+        // 2. 按 groupBy 分组(day/week/month)
+        // 3. 每组计算:总金额(分)、订单数、平均客单价
+        // 4. 返回按时间排序的列表
+    }
+
+    /**
+     * 用户增长时间序列。
+     * @return List<Map> {date: "2026-06-01", newUsers: 10, newVip: 3, newPractitioner: 1}
+     */
+    public List<Map<String, Object>> getUserGrowthTrend(LocalDateTime startDate,
+                                                         LocalDateTime endDate,
+                                                         String groupBy) {
+        // 1. 查询时间范围内所有新用户(按 created_at)
+        // 2. 按 vipType 分类统计
+        // 3. 返回按时间排序的列表
+    }
+
+    /**
+     * 转化漏斗统计。
+     * @return Map{registered: 1000, paidC: 200, paidSuper: 50, practitioner: 30, conversionRate: 3%}
+     */
+    public Map<String, Object> getConversionFunnel() {
+        // 1. 统计总用户数(registered)
+        // 2. 统计 C 端付费用户数(paidC)
+        // 3. 统计超级会员数(paidSuper)
+        // 4. 统计能量师数(practitioner)
+        // 5. 计算转化率:practitioner / registered
+    }
+
+    /**
+     * 分销网络统计。
+     * @return Map{totalPromoters: 50, topPromoters: List[top10], totalCommission: 500000, avgCommission: 10000}
+     */
+    public Map<String, Object> getDistributionStats() {
+        // 1. 统计有推广记录的用户数(invitedBy IS NOT NULL)
+        // 2. 查询 Top10 推广者(按 directCount + indirectCount 排序)
+        // 3. 统计总佣金(settled + available)
+        // 4. 计算人均佣金
+    }
+
+    /**
+     * 提现统计。
+     * @return Map{totalWithdraw: 300000, successRate: 95%, byChannel: {REFUND: 100000, WECHAT: 200000}}
+     */
+    public Map<String, Object> getWithdrawStats(LocalDateTime startDate,
+                                                 LocalDateTime endDate) {
+        // 1. 统计提现总额
+        // 2. 按通道分组统计
+        // 3. 计算成功率(success / total)
+    }
+
+    /**
+     * 核心指标汇总(支持日期范围)。
+     * @return Map{totalRevenue: 1000000, totalOrders: 100, totalUsers: 500, newUsers: 50, ...}
+     */
+    public Map<String, Object> getSummaryStats(LocalDateTime startDate,
+                                                LocalDateTime endDate) {
+        // 综合统计,用于 Dashboard 顶部卡片
+    }
+}
+```
+
+### 3.2 AdminController 扩展
+
+**文件:** `num-server/src/main/java/com/etotem/num/controller/AdminController.java`
+
+**新增端点:**
+
+```java
+@PostMapping("/analytics/revenue-trend")
+public Result<List<Map<String, Object>>> revenueTrend(@RequestBody Map<String, String> body) {
+    String startDate = body.get("startDate");  // ISO 8601: "2026-06-01T00:00:00"
+    String endDate = body.get("endDate");      // ISO 8601: "2026-06-30T23:59:59"
+    String groupBy = body.get("groupBy");      // "day" | "week" | "month"
+    
+    LocalDateTime start = LocalDateTime.parse(startDate);
+    LocalDateTime end = LocalDateTime.parse(endDate);
+    
+    return Result.success(analyticsService.getRevenueTrend(start, end, groupBy));
+}
+
+@PostMapping("/analytics/user-growth")
+public Result<List<Map<String, Object>>> userGrowth(@RequestBody Map<String, String> body) {
+    // 同上
+}
+
+@PostMapping("/analytics/conversion-funnel")
+public Result<Map<String, Object>> conversionFunnel() {
+    return Result.success(analyticsService.getConversionFunnel());
+}
+
+@PostMapping("/analytics/distribution")
+public Result<Map<String, Object>> distributionStats() {
+    return Result.success(analyticsService.getDistributionStats());
+}
+
+@PostMapping("/analytics/summary")
+public Result<Map<String, Object>> summaryStats(@RequestBody Map<String, String> body) {
+    // 支持日期范围
+}
+```
+
+### 3.3 Repository 扩展(如需要)
+
+**OrderRepository 新增方法:**
+
+```java
+@Query("SELECT DATE(o.paidAt) as date, SUM(o.totalFee) as revenue, COUNT(o) as count " +
+       "FROM Order o WHERE o.status = 'paid' AND o.paidAt BETWEEN :start AND :end " +
+       "GROUP BY DATE(o.paidAt) ORDER BY date")
+List<Object[]> findRevenueByDate(@Param("start") LocalDateTime start, 
+                                  @Param("end") LocalDateTime end);
+```
+
+**UserRepository 新增方法:**
+
+```java
+@Query("SELECT DATE(u.createdAt) as date, COUNT(u) as count " +
+       "FROM User u WHERE u.createdAt BETWEEN :start AND :end " +
+       "GROUP BY DATE(u.createdAt) ORDER BY date")
+List<Object[]> findUserGrowthByDate(@Param("start") LocalDateTime start, 
+                                     @Param("end") LocalDateTime end);
+```
+
+---
+
+## 四、前端设计
+
+### 4.1 新增页面路由
+
+**文件:** `admin/src/router/index.ts`
+
+```typescript
+{
+  path: '/analytics',
+  name: 'Analytics',
+  component: () => import('@/views/analytics/index.vue'),
+  meta: { title: '统计分析', icon: 'trend-charts' }
+}
+```
+
+**文件:** `admin/src/layout/index.vue`
+
+```vue
+<el-menu-item index="/analytics">
+  <el-icon><TrendCharts /></el-icon>
+  <span>统计分析</span>
+</el-menu-item>
+```
+
+### 4.2 统计分析页面
+
+**文件:** `admin/src/views/analytics/index.vue`
+
+**页面结构:**
+
+```vue
+<template>
+  <div class="analytics-container">
+    <!-- 1. 日期范围选择器 -->
+    <el-card class="filter-card">
+      <el-row :gutter="16" align="middle">
+        <el-col :span="8">
+          <el-date-picker
+            v-model="dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :shortcuts="dateShortcuts"
+            @change="fetchData"
+          />
+        </el-col>
+        <el-col :span="4">
+          <el-select v-model="groupBy" placeholder="分组维度" @change="fetchData">
+            <el-option label="按日" value="day" />
+            <el-option label="按周" value="week" />
+            <el-option label="按月" value="month" />
+          </el-select>
+        </el-col>
+        <el-col :span="4">
+          <el-button type="primary" @click="fetchData">查询</el-button>
+          <el-button @click="exportData">导出</el-button>
+        </el-col>
+      </el-row>
+    </el-card>
+
+    <!-- 2. 核心指标卡片(4 列) -->
+    <el-row :gutter="16" class="metrics-row">
+      <el-col :span="6">
+        <el-card shadow="hover">
+          <div class="metric-item">
+            <div class="label">总收入</div>
+            <div class="value">¥{{ summary.totalRevenue / 100 }}</div>
+            <div class="trend positive">↑ 12%</div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <!-- 订单总数 -->
+      </el-col>
+      <el-col :span="6">
+        <!-- 新增用户 -->
+      </el-col>
+      <el-col :span="6">
+        <!-- VIP 转化率 -->
+      </el-col>
+    </el-row>
+
+    <!-- 3. 收入趋势图(折线图) -->
+    <el-card class="chart-card">
+      <template #header>
+        <div class="card-header">
+          <span>收入趋势</span>
+          <el-radio-group v-model="groupBy" size="small" @change="fetchRevenueTrend">
+            <el-radio-button label="day">按日</el-radio-button>
+            <el-radio-button label="week">按周</el-radio-button>
+            <el-radio-button label="month">按月</el-radio-button>
+          </el-radio-group>
+        </div>
+      </template>
+      <div ref="revenueChartRef" class="chart-container"></div>
+    </el-card>
+
+    <!-- 4. 用户增长图(柱状图 + 折线图组合) -->
+    <el-card class="chart-card">
+      <template #header>用户增长</template>
+      <div ref="userGrowthChartRef" class="chart-container"></div>
+    </el-card>
+
+    <!-- 5. 转化漏斗(漏斗图) -->
+    <el-card class="chart-card">
+      <template #header>转化漏斗</template>
+      <div ref="funnelChartRef" class="chart-container"></div>
+    </el-card>
+
+    <!-- 6. 分销 Top10(表格) -->
+    <el-card class="table-card">
+      <template #header>
+        <div class="card-header">
+          <span>推广者 Top10</span>
+          <el-button type="primary" size="small">查看全部</el-button>
+        </div>
+      </template>
+      <el-table :data="topPromoters" stripe>
+        <el-table-column prop="nickname" label="用户" />
+        <el-table-column prop="directCount" label="直接推广" />
+        <el-table-column prop="indirectCount" label="间接推广" />
+        <el-table-column prop="totalCommission" label="总佣金(¥)" />
+      </el-table>
+    </el-card>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import * as echarts from 'echarts'
+import { analyticsApi } from '@/api/analytics'
+
+// 日期范围
+const dateRange = ref<[Date, Date]>()
+const groupBy = ref('day')
+
+// 日期快捷选项
+const dateShortcuts = [
+  { text: '今日', value: () => [new Date(), new Date()] },
+  { text: '近 7 天', value: () => {
+    const end = new Date()
+    const start = new Date()
+    start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+    return [start, end]
+  }},
+  { text: '近 30 天', value: () => {
+    const end = new Date()
+    const start = new Date()
+    start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+    return [start, end]
+  }},
+  { text: '本月', value: () => {
+    const now = new Date()
+    const start = new Date(now.getFullYear(), now.getMonth(), 1)
+    return [start, now]
+  }},
+]
+
+// 核心指标
+const summary = ref({
+  totalRevenue: 0,
+  totalOrders: 0,
+  newUsers: 0,
+  vipConversionRate: 0,
+})
+
+// Top 推广者
+const topPromoters = ref([])
+
+// 图表实例
+const revenueChartRef = ref<HTMLElement>()
+const userGrowthChartRef = ref<HTMLElement>()
+const funnelChartRef = ref<HTMLElement>()
+let revenueChart: echarts.ECharts
+let userGrowthChart: echarts.ECharts
+let funnelChart: echarts.ECharts
+
+// 获取数据
+const fetchData = async () => {
+  await fetchSummary()
+  await fetchRevenueTrend()
+  await fetchUserGrowth()
+  await fetchConversionFunnel()
+  await fetchTopPromoters()
+}
+
+const fetchSummary = async () => {
+  const res = await analyticsApi.getSummary({
+    startDate: dateRange.value?.[0].toISOString(),
+    endDate: dateRange.value?.[1].toISOString(),
+  })
+  summary.value = res.data
+}
+
+const fetchRevenueTrend = async () => {
+  const res = await analyticsApi.getRevenueTrend({
+    startDate: dateRange.value?.[0].toISOString(),
+    endDate: dateRange.value?.[1].toISOString(),
+    groupBy: groupBy.value,
+  })
+  
+  // 渲染折线图
+  const option = {
+    tooltip: { trigger: 'axis' },
+    xAxis: {
+      type: 'category',
+      data: res.data.map((item: any) => item.date),
+    },
+    yAxis: {
+      type: 'value',
+      axisLabel: { formatter: '¥{value}' },
+    },
+    series: [{
+      type: 'line',
+      data: res.data.map((item: any) => item.revenue / 100),
+      smooth: true,
+      areaStyle: { opacity: 0.3 },
+    }],
+  }
+  revenueChart.setOption(option)
+}
+
+const fetchUserGrowth = async () => {
+  // 类似实现柱状图 + 折线图组合
+}
+
+const fetchConversionFunnel = async () => {
+  // 漏斗图实现
+}
+
+const fetchTopPromoters = async () => {
+  // 表格数据
+}
+
+const exportData = () => {
+  // CSV/Excel 导出逻辑
+}
+
+onMounted(() => {
+  revenueChart = echarts.init(revenueChartRef.value!)
+  userGrowthChart = echarts.init(userGrowthChartRef.value!)
+  funnelChart = echarts.init(funnelChartRef.value!)
+  fetchData()
+})
+</script>
+
+<style lang="scss" scoped>
+.analytics-container {
+  padding: 20px;
+}
+
+.filter-card {
+  margin-bottom: 20px;
+}
+
+.metrics-row {
+  margin-bottom: 20px;
+}
+
+.metric-item {
+  .label {
+    font-size: 14px;
+    color: #909399;
+  }
+  .value {
+    font-size: 24px;
+    font-weight: bold;
+    color: #303133;
+    margin: 8px 0;
+  }
+  .trend {
+    font-size: 12px;
+    &.positive { color: #67c23a; }
+    &.negative { color: #f56c6c; }
+  }
+}
+
+.chart-card {
+  margin-bottom: 20px;
+  
+  .chart-container {
+    height: 400px;
+  }
+}
+</style>
+```
+
+### 4.3 API 封装
+
+**文件:** `admin/src/api/analytics.ts`
+
+```typescript
+import request from '@/utils/request'
+
+export const analyticsApi = {
+  /** 核心指标汇总 */
+  getSummary(params: { startDate?: string; endDate?: string }) {
+    return request({
+      url: '/api/admin/analytics/summary',
+      method: 'post',
+      data: params,
+    })
+  },
+
+  /** 收入趋势 */
+  getRevenueTrend(params: { startDate: string; endDate: string; groupBy: string }) {
+    return request({
+      url: '/api/admin/analytics/revenue-trend',
+      method: 'post',
+      data: params,
+    })
+  },
+
+  /** 用户增长 */
+  getUserGrowth(params: { startDate: string; endDate: string; groupBy: string }) {
+    return request({
+      url: '/api/admin/analytics/user-growth',
+      method: 'post',
+      data: params,
+    })
+  },
+
+  /** 转化漏斗 */
+  getConversionFunnel() {
+    return request({
+      url: '/api/admin/analytics/conversion-funnel',
+      method: 'post',
+    })
+  },
+
+  /** 分销统计 */
+  getDistributionStats() {
+    return request({
+      url: '/api/admin/analytics/distribution',
+      method: 'post',
+    })
+  },
+
+  /** Top 推广者 */
+  getTopPromoters(limit = 10) {
+    return request({
+      url: '/api/admin/analytics/top-promoters',
+      method: 'post',
+      data: { limit },
+    })
+  },
+}
+```
+
+---
+
+## 五、实施计划
+
+### Phase 1:核心统计(2-3 天)
+
+| 任务 | 文件 | 预估工时 |
+|------|------|---------|
+| **后端** | | |
+| 创建 AnalyticsService | `service/AnalyticsService.java` | 4h |
+| 实现 getRevenueTrend | 同上 | 2h |
+| 实现 getUserGrowthTrend | 同上 | 2h |
+| 实现 getSummaryStats | 同上 | 1h |
+| AdminController 新增端点 | `controller/AdminController.java` | 1h |
+| **前端** | | |
+| 创建 analytics 路由 | `router/index.ts` | 0.5h |
+| 新增 Analytics 页面 | `views/analytics/index.vue` | 4h |
+| API 封装 | `api/analytics.ts` | 1h |
+| 日期范围选择器 | 同上 | 1h |
+| 收入趋势折线图 | 同上 | 2h |
+| 用户增长柱状图 | 同上 | 2h |
+| **合计** | | **20.5h ≈ 2.5 天** |
+
+### Phase 2:高级分析(2-3 天)
+
+| 任务 | 文件 | 预估工时 |
+|------|------|---------|
+| **后端** | | |
+| 实现 getConversionFunnel | `service/AnalyticsService.java` | 2h |
+| 实现 getDistributionStats | 同上 | 2h |
+| 实现 getWithdrawStats | 同上 | 1h |
+| Repository 扩展方法 | `OrderRepository.java`, `UserRepository.java` | 2h |
+| **前端** | | |
+| 转化漏斗图 | `views/analytics/index.vue` | 2h |
+| 分销 Top10 表格 | 同上 | 1h |
+| 数据导出功能(CSV) | 同上 | 2h |
+| **合计** | | **14h ≈ 1.5 天** |
+
+---
+
+## 六、验收标准
+
+### Phase 1
+
+- [ ] 日期范围选择器正常工作(快捷选项 + 自定义)
+- [ ] 收入趋势折线图正确显示(单位:元,保留 2 位小数)
+- [ ] 用户增长柱状图正确显示(新增用户/VIP 转化分层)
+- [ ] 核心指标卡片数据准确(总收入/订单数/新增用户/转化率)
+- [ ] 分组维度切换正常(按日/周/月)
+
+### Phase 2
+
+- [ ] 转化漏斗图正确显示(注册→付费→升级→能量师)
+- [ ] 分销 Top10 表格数据准确
+- [ ] 数据导出功能正常(CSV 格式)
+- [ ] 提现统计图表正确(通道分布/成功率)
+
+---
+
+## 七、技术注意事项
+
+1. **金额单位转换**:后端返回分(Integer),前端需除以 100 显示为元
+2. **时区处理**:所有时间使用 ISO 8601 格式,后端解析为 LocalDateTime
+3. **性能优化**:大数据量时使用数据库聚合(GROUP BY),避免内存计算
+4. **缓存策略**:统计数据可缓存 5-10 分钟,避免频繁查询
+5. **图表响应式**:ECharts 实例需监听窗口 resize 事件
+6. **空数据处理**:无数据时显示友好提示,不渲染空图表
+
+---
+
+## 八、扩展方向(未来)
+
+1. **实时数据**:WebSocket 推送实时订单/用户数据
+2. **自定义报表**:用户自选指标 + 图表类型
+3. **预警系统**:收入/用户异常波动告警
+4. **A/B 测试分析**:不同定价策略转化对比
+5. **RFM 模型**:用户价值分层分析