|
|
@@ -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 模型**:用户价值分层分析
|