# 首页统计数据接口 - 前端对接文档
## 📋 目录
- [接口概述](#接口概述)
- [快速开始](#快速开始)
- [接口详情](#接口详情)
- [请求参数](#请求参数)
- [响应数据](#响应数据)
- [请求示例](#请求示例)
- [错误处理](#错误处理)
- [权限说明](#权限说明)
- [注意事项](#注意事项)
---
## 接口概述
### 基本信息
| 项目 | 内容 |
|------|------|
| **接口名称** | 获取首页统计数据 |
| **接口路径** | `/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
今日待审核: {{ stats.today_pending_files }}
已审核: {{ stats.monthly_reviewed_files }}
通过率: {{ stats.monthly_pass_rate }}%
```
### 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
今日待审核
{{ stats.today_pending_files }}
已审核文件
{{ stats.monthly_reviewed_files }}
{{ stats.monthly_review_growth?.is_up ? '↑' : '↓' }}
{{ stats.monthly_review_growth?.value }}%
审核通过率
{{ stats.monthly_pass_rate }}%
{{ stats.pass_rate_growth?.is_up ? '↑' : '↓' }}
{{ stats.pass_rate_growth?.value }}%
检测到的问题
{{ stats.issues_detected }}
{{ stats.issues_growth?.is_up ? '↑' : '↓' }}
{{ stats.issues_growth?.value }}%
加载中...
```
---
### 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 🤖