# 首页统计数据接口 - 前端对接文档 ## 📋 目录 - [接口概述](#接口概述) - [快速开始](#快速开始) - [接口详情](#接口详情) - [请求参数](#请求参数) - [响应数据](#响应数据) - [请求示例](#请求示例) - [错误处理](#错误处理) - [权限说明](#权限说明) - [注意事项](#注意事项) --- ## 接口概述 ### 基本信息 | 项目 | 内容 | |------|------| | **接口名称** | 获取首页统计数据 | | **接口路径** | `/admin/statistics/home-data` | | **请求方法** | `GET` | | **需要认证** | ✅ 是(需要JWT Token) | | **权限要求** | 所有登录用户可访问 | | **版本** | v3 | ### 功能说明 获取首页展示的统计数据,包括: - ✅ 今日待审核文件数 - ✅ 当前周期已审核文件数 - ✅ 审核文件同比增长 - ✅ 当前周期审核通过率 - ✅ 通过率同比增长 - ✅ 检测到的问题总数 - ✅ 问题数量同比增长 ### 核心特性 1. **三级权限隔离** - 省级管理员:查看全省数据 - 地市管理员:查看本地市数据 - 普通用户:只能查看自己的数据 2. **灵活时间范围** - 支持今天、近3天、近7天、近30天、本月 - 自动对比上一个相同周期 3. **类型筛选** - 支持按文档类型ID筛选 - 支持多个类型ID组合 4. **性能优化** - Redis缓存20分钟 - 响应时间 < 200ms --- ## 快速开始 ### 最简单的调用 ```javascript // 获取本月统计数据(默认) fetch('/admin/statistics/home-data', { headers: { 'Authorization': 'Bearer YOUR_JWT_TOKEN' } }) .then(res => res.json()) .then(data => console.log(data)); ``` ### Vue 3 示例 ```vue ``` ### React 示例 ```jsx import { useState, useEffect } from 'react' import axios from 'axios' function HomeStatistics() { const [stats, setStats] = useState({}) const [loading, setLoading] = useState(false) const fetchHomeStats = async (timeRange = null, typeIds = null) => { setLoading(true) try { const params = {} if (timeRange) params.time_range = timeRange if (typeIds) params.type_ids = typeIds const { data } = await axios.get('/admin/statistics/home-data', { params }) setStats(data) } catch (error) { console.error('获取统计数据失败:', error) } finally { setLoading(false) } } useEffect(() => { fetchHomeStats() }, []) return (
{!loading && ( <>

今日待审核: {stats.today_pending_files}

已审核: {stats.monthly_reviewed_files}

通过率: {stats.monthly_pass_rate}%

)}
) } ``` --- ## 接口详情 ### 完整URL ``` GET {BASE_URL}/admin/statistics/home-data ``` **示例**: ``` https://api.example.com/admin/statistics/home-data http://localhost:8073/admin/statistics/home-data ``` ### 请求头 | Header | 必填 | 类型 | 说明 | 示例 | |--------|------|------|------|------| | `Authorization` | ✅ 是 | String | JWT Token | `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` | | `Content-Type` | ❌ 否 | String | 内容类型 | `application/json` | --- ## 请求参数 ### Query 参数 | 参数名 | 必填 | 类型 | 默认值 | 说明 | |--------|------|------|--------|------| | `type_ids` | ❌ 否 | String | `null` | 文档类型ID,多个用逗号分隔 | | `time_range` | ❌ 否 | String | `null` | 时间范围,见下表 | ### time_range 参数详解 | 值 | 说明 | 当前周期 | 对比周期 | 使用场景 | |----|------|----------|----------|----------| | 不传参数 | 本月数据(默认) | 本月1日 ~ 当前时间 | 上月1日 ~ 上月最后一天 | 默认首页统计 | | `today` | 今天 | 今天00:00 ~ 当前时间 | 昨天00:00 ~ 昨天23:59 | 查看今日数据 | | `3days` | 近3天 | 当前时间向前72小时 | 再往前72小时 | 短期趋势 | | `7days` | 近7天 | 当前时间向前7天 | 再往前7天 | 周统计 | | `30days` | 近30天 | 当前时间向前30天 | 再往前30天 | 月统计 | ### type_ids 参数详解 **格式**: 逗号分隔的整数 **示例**: - 单个类型: `type_ids=1` - 多个类型: `type_ids=1,2,3` - 所有类型: 不传参数或传 `null` **有效值** (根据你的业务配置): ```javascript 1 // 类型1 2 // 类型2 3 // 类型3 // ... 更多类型ID ``` --- ## 响应数据 ### 响应结构 ```typescript interface HomeStatisticsResponse { today_pending_files: number; // 今日待审核文件数 monthly_reviewed_files: number; // 当前周期已审核文件数 monthly_review_growth: GrowthData; // 审核数同比增长 monthly_pass_rate: number; // 当前周期通过率(百分比) pass_rate_growth: GrowthData; // 通过率同比增长 issues_detected: number; // 检测到的问题总数 issues_growth: GrowthData; // 问题数同比增长 } interface GrowthData { value: number; // 增长百分比(绝对值) is_up: boolean; // true=增长, false=下降 } ``` ### 完整响应示例 ```json { "today_pending_files": 15, "monthly_reviewed_files": 120, "monthly_review_growth": { "value": 25.5, "is_up": true }, "monthly_pass_rate": 85.3, "pass_rate_growth": { "value": 3.2, "is_up": true }, "issues_detected": 42, "issues_growth": { "value": 12.8, "is_up": false } } ``` ### 字段说明 #### 1. today_pending_files(今日待审核文件数) - **类型**: `number` - **说明**: 今天新增的待审核文件数(audit_status = 0 或 null) - **范围**: >= 0 - **示例**: `15` #### 2. monthly_reviewed_files(当前周期已审核文件数) - **类型**: `number` - **说明**: 当前时间范围内已审核的文件数 - **包括状态**: - ✅ `audit_status = 1` (通过) - 🔄 `audit_status = 2` (审核中) - ❌ `audit_status = -1` (拒绝) - ⚠️ `audit_status = -2` (警告) - **范围**: >= 0 - **示例**: `120` #### 3. monthly_review_growth(审核数同比增长) - **类型**: `GrowthData` - **说明**: 当前周期 vs 上个周期的审核数量变化 - **计算公式**: `|(当前周期 - 上个周期) / 上个周期| * 100` - **示例**: ```json { "value": 25.5, // 增长了25.5% "is_up": true // 增长趋势 } ``` #### 4. monthly_pass_rate(当前周期通过率) - **类型**: `number` - **说明**: 当前周期审核通过的文件占比 - **计算公式**: `(通过数量 / 已审核总数) * 100` - **范围**: 0.0 ~ 100.0 - **保留**: 小数点后1位 - **示例**: `85.3` (表示85.3%) #### 5. pass_rate_growth(通过率同比增长) - **类型**: `GrowthData` - **说明**: 当前周期 vs 上个周期的通过率变化 - **计算公式**: `|(当前通过率 - 上期通过率) / 上期通过率| * 100` - **示例**: ```json { "value": 3.2, // 通过率提升了3.2% "is_up": true } ``` #### 6. issues_detected(检测到的问题总数) - **类型**: `number` - **说明**: 当前周期内所有评查点发现的问题总数 - **来源**: `evaluation_results` 表中 `result='false'` 的记录 - **范围**: >= 0 - **示例**: `42` #### 7. issues_growth(问题数同比增长) - **类型**: `GrowthData` - **说明**: 当前周期 vs 上个周期的问题数变化 - **示例**: ```json { "value": 12.8, // 问题数减少了12.8% "is_up": false // 下降趋势(好事) } ``` --- ## 请求示例 ### 1. 获取本月统计数据(默认) **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const response = await fetch('/admin/statistics/home-data', { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **响应**: ```json { "today_pending_files": 10, "monthly_reviewed_files": 150, "monthly_review_growth": { "value": 15.5, "is_up": true }, "monthly_pass_rate": 82.0, "pass_rate_growth": { "value": 2.5, "is_up": true }, "issues_detected": 35, "issues_growth": { "value": 8.0, "is_up": false } } ``` --- ### 2. 获取今天的统计数据 **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data?time_range=today" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const response = await fetch('/admin/statistics/home-data?time_range=today', { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **Axios**: ```javascript const { data } = await axios.get('/admin/statistics/home-data', { params: { time_range: 'today' } }) ``` **响应**: ```json { "today_pending_files": 5, "monthly_reviewed_files": 12, "monthly_review_growth": { "value": 20.0, "is_up": true }, "monthly_pass_rate": 75.0, "pass_rate_growth": { "value": 5.0, "is_up": false }, "issues_detected": 8, "issues_growth": { "value": 10.0, "is_up": false } } ``` --- ### 3. 获取近7天统计数据 **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data?time_range=7days" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const response = await fetch('/admin/statistics/home-data?time_range=7days', { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **响应**: ```json { "today_pending_files": 8, "monthly_reviewed_files": 85, "monthly_review_growth": { "value": 30.5, "is_up": true }, "monthly_pass_rate": 88.2, "pass_rate_growth": { "value": 4.0, "is_up": true }, "issues_detected": 25, "issues_growth": { "value": 15.0, "is_up": false } } ``` --- ### 4. 按文档类型筛选(单个类型) **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data?type_ids=1" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const response = await fetch('/admin/statistics/home-data?type_ids=1', { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **Axios**: ```javascript const { data } = await axios.get('/admin/statistics/home-data', { params: { type_ids: '1' } }) ``` --- ### 5. 按文档类型筛选(多个类型) **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data?type_ids=1,2,3" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const typeIds = [1, 2, 3] const response = await fetch(`/admin/statistics/home-data?type_ids=${typeIds.join(',')}`, { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **Axios**: ```javascript const { data } = await axios.get('/admin/statistics/home-data', { params: { type_ids: '1,2,3' } }) ``` --- ### 6. 组合筛选:时间范围 + 文档类型 **请求**: ```bash curl -X GET "http://localhost:8073/admin/statistics/home-data?time_range=7days&type_ids=1,2" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` **JavaScript**: ```javascript const params = new URLSearchParams({ time_range: '7days', type_ids: '1,2' }) const response = await fetch(`/admin/statistics/home-data?${params}`, { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() ``` **Axios**: ```javascript const { data } = await axios.get('/admin/statistics/home-data', { params: { time_range: '7days', type_ids: '1,2' } }) ``` --- ### 7. 完整的Vue组件示例(带下拉选择器) ```vue ``` --- ### 8. 完整的React组件示例(带Ant Design) ```jsx import { useState, useEffect } from 'react' import { Card, Select, Spin, Statistic, Row, Col } from 'antd' import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons' import axios from 'axios' const { Option } = Select function HomeStatistics() { const [stats, setStats] = useState({}) const [loading, setLoading] = useState(false) const [timeRange, setTimeRange] = useState(null) const [typeIds, setTypeIds] = useState(null) // 时间范围选项 const timeRangeOptions = [ { label: '本月', value: null }, { label: '今天', value: 'today' }, { label: '近3天', value: '3days' }, { label: '近7天', value: '7days' }, { label: '近30天', value: '30days' } ] const fetchStats = async () => { setLoading(true) try { const params = {} if (timeRange) params.time_range = timeRange if (typeIds) params.type_ids = typeIds const { data } = await axios.get('/admin/statistics/home-data', { params }) setStats(data) } catch (error) { console.error('获取统计数据失败:', error) } finally { setLoading(false) } } useEffect(() => { fetchStats() }, [timeRange, typeIds]) return (
{/* 筛选器 */}
{/* 统计卡片 */} {stats.monthly_review_growth.is_up ? : } {stats.monthly_review_growth.value}% ) } />
) } export default HomeStatistics ``` --- ## 错误处理 ### 常见错误码 | HTTP状态码 | 错误场景 | 响应示例 | 处理建议 | |-----------|----------|----------|----------| | `200` | ✅ 成功 | 正常数据 | - | | `400` | 参数错误 | `{"detail": "time_range 参数值无效..."}` | 检查参数格式 | | `401` | 未认证 | `{"detail": "未提供认证凭据"}` | 检查Token是否传递 | | `403` | 权限不足 | `{"detail": "此接口仅限管理员访问"}` | 检查用户权限 | | `500` | 服务器错误 | `{"detail": "获取首页统计数据失败..."}` | 联系后端排查 | ### 错误响应格式 ```json { "detail": "错误描述信息" } ``` ### 错误示例 #### 1. time_range 参数无效 **请求**: ```bash curl "http://localhost:8073/admin/statistics/home-data?time_range=invalid" ``` **响应**: HTTP 400 ```json { "detail": "time_range 参数值无效,应为: today, 3days, 7days, 30days,实际为: invalid" } ``` #### 2. type_ids 参数格式错误 **请求**: ```bash curl "http://localhost:8073/admin/statistics/home-data?type_ids=abc" ``` **响应**: HTTP 400 ```json { "detail": "type_ids 参数格式错误,应为逗号分隔的整数,实际为: abc" } ``` #### 3. Token过期或无效 **请求**: ```bash curl "http://localhost:8073/admin/statistics/home-data" \ -H "Authorization: Bearer INVALID_TOKEN" ``` **响应**: HTTP 401 ```json { "detail": "认证凭据无效或已过期" } ``` ### 前端错误处理示例 ```javascript const fetchStats = async () => { try { const { data } = await axios.get('/admin/statistics/home-data', { params: { time_range: 'today' } }) return data } catch (error) { // 处理不同的错误情况 if (error.response) { switch (error.response.status) { case 400: console.error('参数错误:', error.response.data.detail) // 提示用户检查筛选条件 break case 401: console.error('未认证,请重新登录') // 跳转到登录页 window.location.href = '/login' break case 403: console.error('权限不足') // 提示用户权限不足 break case 500: console.error('服务器错误:', error.response.data.detail) // 显示错误提示 break default: console.error('未知错误:', error) } } else if (error.request) { console.error('网络错误,请检查网络连接') } else { console.error('请求配置错误:', error.message) } } } ``` --- ## 权限说明 ### 三级权限体系 | 用户角色 | 权限范围 | 能看到的数据 | 示例 | |---------|---------|------------|------| | **省级管理员** | 全省 | 所有地市的数据汇总 | 可以看到广州+深圳+梅州...所有地市 | | **地市管理员** | 本地市 | 所在地市的所有用户数据 | 梅州管理员只能看梅州的数据 | | **普通用户** | 个人 | 仅自己创建的文档数据 | 张三只能看张三的数据 | ### 权限判断逻辑 系统通过以下步骤判断用户权限: 1. **查询用户角色** ```sql SELECT role_key FROM user_role JOIN roles ON user_role.role_id = roles.id WHERE user_role.user_id = {current_user_id} ``` 2. **权限分配** - `role_key = 'province_admin'` → 省级管理员 - `user_role = 'admin'` 或 `is_leader = true` → 地市管理员 - 其他 → 普通用户 3. **数据过滤** - 省级管理员:不过滤地市(`area = null`) - 地市管理员:过滤地市(`area = '梅州'`) - 普通用户:过滤用户ID(`user_id = 123`) ### 权限验证示例 **省级管理员查询**: ```javascript // Token中包含: { user_role: 'provincial_admin', area: '省级' } const { data } = await axios.get('/admin/statistics/home-data') // 返回全省所有数据 ``` **地市管理员查询**: ```javascript // Token中包含: { user_role: 'admin', area: '梅州' } const { data } = await axios.get('/admin/statistics/home-data') // 只返回梅州的数据 ``` **普通用户查询**: ```javascript // Token中包含: { user_id: 123, user_role: 'common', area: '梅州' } const { data } = await axios.get('/admin/statistics/home-data') // 只返回用户123的数据 ``` --- ## 注意事项 ### ⚠️ 重要提示 1. **Token必须有效** - Token必须包含在Authorization头中 - Token格式: `Bearer {token}` - Token过期需要重新登录 2. **参数大小写敏感** - `time_range=today` ✅ - `time_range=Today` ❌ - `time_range=TODAY` ❌ 3. **type_ids必须是整数** - `type_ids=1,2,3` ✅ - `type_ids=1, 2, 3` ✅ (自动trim空格) - `type_ids=a,b,c` ❌ 4. **缓存机制** - 数据缓存20分钟 - 相同参数20分钟内返回缓存数据 - 不同参数会触发新的查询 5. **时间范围对比** - `today` vs 昨天 - `3days` vs 前3天 - `7days` vs 前7天 - `30days` vs 前30天 - 默认(本月)vs 上月 ### 📊 数据统计说明 1. **"已审核"的定义** - 包括以下状态的文档: - ✅ `audit_status = 1` (通过) - 🔄 `audit_status = 2` (审核中) - ❌ `audit_status = -1` (拒绝) - ⚠️ `audit_status = -2` (警告) - **不包括**: - `audit_status = 0` (待审核) - `audit_status = null` (未知) 2. **"今日待审核"的定义** - 仅统计今天00:00之后创建的文档 - `audit_status = 0` 或 `audit_status = null` 3. **通过率计算** - 公式: `(通过数量 / 已审核总数) * 100` - 只有`audit_status = 1`算通过 - 如果已审核总数为0,通过率为0.0% 4. **增长率计算** - 使用绝对值(不带正负号) - 通过`is_up`判断增长/下降 - 如果上期数据为0,增长率固定为100% ### 🔄 刷新策略建议 ```javascript // 推荐的刷新策略 // 1. 首次加载 onMounted(() => fetchStats()) // 2. 定时刷新(5分钟) setInterval(() => fetchStats(), 5 * 60 * 1000) // 3. 用户切换筛选条件时立即刷新 watch([timeRange, typeIds], () => fetchStats()) // 4. 用户手动刷新 const handleRefresh = () => { // 清除缓存的方式:随机添加时间戳(不推荐,会绕过缓存) // const timestamp = Date.now() // fetchStats({ _t: timestamp }) // 推荐:直接调用,利用缓存 fetchStats() } ``` ### 🎨 UI展示建议 1. **增长指标展示** ```javascript // 使用颜色区分增长/下降 const getGrowthColor = (growth) => { return growth.is_up ? '#52c41a' : '#f5222d' } // 使用图标 const getGrowthIcon = (growth) => { return growth.is_up ? '↑' : '↓' } ``` 2. **数值格式化** ```javascript // 大数值使用千分位 const formatNumber = (num) => { return num.toLocaleString('zh-CN') } // 示例: 1234 → 1,234 ``` 3. **空数据处理** ```javascript // 当数据为0时的友好提示 const displayValue = (value) => { return value > 0 ? value : '暂无数据' } ``` ### 🐛 常见问题 FAQ **Q1: 为什么返回的数据都是0?** A: 可能的原因: - 时间范围内没有文档(如选择"今天"但今天没创建文档) - 文档类型筛选过滤掉了所有数据 - 用户权限限制(普通用户只能看自己的数据) - 数据库中确实没有符合条件的数据 **Q2: 为什么切换时间范围后数据没变化?** A: 可能是Redis缓存,等待20分钟缓存过期或者联系后端清除缓存。 **Q3: 增长率为什么总是100%?** A: 当上个周期数据为0,当前周期有数据时,增长率固定显示100%。 **Q4: 省级管理员和地市管理员看到的数据一样?** A: 如果省级管理员的地市也是"梅州",且数据库中只有梅州的数据,两者看到的会一样。省级管理员应该看到所有地市的汇总。 **Q5: 通过率计算是否包含"审核中"的文档?** A: 通过率 = 通过数量 / 已审核总数。 - 已审核总数包含"审核中"(status=2) - 但通过数量只计算status=1的文档 - 所以"审核中"会降低通过率 --- ## 版本历史 | 版本 | 日期 | 更新内容 | |------|------|---------| | v3.0 | 2025-11-22 | 🎉 新增时间范围筛选功能(today/3days/7days/30days) | | v2.1 | 2025-11-22 | ✨ "已审核"统计包含"审核中"状态(audit_status=2) | | v2.0 | 2025-11-20 | 🔒 完善三级权限隔离机制 | | v1.0 | 2025-11-15 | 🚀 初始版本发布 | --- ## 技术支持 如有疑问,请联系后端开发团队或查看: - 📖 完整API文档: `/docs/api/` - 🐛 问题反馈: [GitHub Issues](https://github.com/your-repo/issues) - 💬 技术讨论: 开发团队微信群 --- **文档生成时间**: 2025-11-22 **文档维护**: 后端开发团队 **最后更新**: Claude Code 🤖