Files
leaudit-platform-frontend/auth_doc/home_statistics_api_详细对接文档.md
2025-12-05 00:09:32 +08:00

1191 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 首页统计数据接口 - 前端对接文档
## 📋 目录
- [接口概述](#接口概述)
- [快速开始](#快速开始)
- [接口详情](#接口详情)
- [请求参数](#请求参数)
- [响应数据](#响应数据)
- [请求示例](#请求示例)
- [错误处理](#错误处理)
- [权限说明](#权限说明)
- [注意事项](#注意事项)
---
## 接口概述
### 基本信息
| 项目 | 内容 |
|------|------|
| **接口名称** | 获取首页统计数据 |
| **接口路径** | `/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
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const stats = ref({})
const loading = ref(false)
const fetchHomeStats = async (timeRange = null, typeIds = null) => {
loading.value = 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 })
stats.value = data
} catch (error) {
console.error('获取统计数据失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchHomeStats()
})
</script>
<template>
<div v-if="!loading">
<h2>今日待审核: {{ stats.today_pending_files }}</h2>
<h2>已审核: {{ stats.monthly_reviewed_files }}</h2>
<h2>通过率: {{ stats.monthly_pass_rate }}%</h2>
</div>
</template>
```
### 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 (
<div>
{!loading && (
<>
<h2>今日待审核: {stats.today_pending_files}</h2>
<h2>已审核: {stats.monthly_reviewed_files}</h2>
<h2>通过率: {stats.monthly_pass_rate}%</h2>
</>
)}
</div>
)
}
```
---
## 接口详情
### 完整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
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const stats = ref({})
const loading = ref(false)
const timeRange = ref(null)
const typeIds = ref(null)
// 时间范围选项
const timeRangeOptions = [
{ label: '本月', value: null },
{ label: '今天', value: 'today' },
{ label: '近3天', value: '3days' },
{ label: '近7天', value: '7days' },
{ label: '近30天', value: '30days' }
]
// 文档类型选项(根据实际业务调整)
const typeOptions = [
{ label: '全部类型', value: null },
{ label: '类型1', value: '1' },
{ label: '类型2', value: '2' },
{ label: '类型1+2', value: '1,2' }
]
const fetchStats = async () => {
loading.value = true
try {
const params = {}
if (timeRange.value) params.time_range = timeRange.value
if (typeIds.value) params.type_ids = typeIds.value
const { data } = await axios.get('/admin/statistics/home-data', { params })
stats.value = data
} catch (error) {
console.error('获取统计数据失败:', error)
} finally {
loading.value = false
}
}
onMounted(() => {
fetchStats()
})
</script>
<template>
<div class="home-statistics">
<!-- 筛选器 -->
<div class="filters">
<select v-model="timeRange" @change="fetchStats">
<option v-for="opt in timeRangeOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
<select v-model="typeIds" @change="fetchStats">
<option v-for="opt in typeOptions" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
</div>
<!-- 统计数据展示 -->
<div v-if="!loading" class="stats-cards">
<div class="stat-card">
<h3>今日待审核</h3>
<p class="number">{{ stats.today_pending_files }}</p>
</div>
<div class="stat-card">
<h3>已审核文件</h3>
<p class="number">{{ stats.monthly_reviewed_files }}</p>
<p class="growth" :class="{ up: stats.monthly_review_growth?.is_up }">
{{ stats.monthly_review_growth?.is_up ? '↑' : '↓' }}
{{ stats.monthly_review_growth?.value }}%
</p>
</div>
<div class="stat-card">
<h3>审核通过率</h3>
<p class="number">{{ stats.monthly_pass_rate }}%</p>
<p class="growth" :class="{ up: stats.pass_rate_growth?.is_up }">
{{ stats.pass_rate_growth?.is_up ? '↑' : '↓' }}
{{ stats.pass_rate_growth?.value }}%
</p>
</div>
<div class="stat-card">
<h3>检测到的问题</h3>
<p class="number">{{ stats.issues_detected }}</p>
<p class="growth" :class="{ down: !stats.issues_growth?.is_up }">
{{ stats.issues_growth?.is_up ? '↑' : '↓' }}
{{ stats.issues_growth?.value }}%
</p>
</div>
</div>
<div v-else class="loading">加载中...</div>
</div>
</template>
<style scoped>
.filters {
display: flex;
gap: 16px;
margin-bottom: 24px;
}
.stats-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
.stat-card {
padding: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.number {
font-size: 32px;
font-weight: bold;
margin: 8px 0;
}
.growth {
font-size: 14px;
color: #666;
}
.growth.up {
color: #52c41a;
}
.growth.down {
color: #f5222d;
}
</style>
```
---
### 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 (
<div style={{ padding: '24px' }}>
{/* 筛选器 */}
<div style={{ marginBottom: '24px', display: 'flex', gap: '16px' }}>
<Select
placeholder="选择时间范围"
value={timeRange}
onChange={setTimeRange}
style={{ width: 200 }}
>
{timeRangeOptions.map(opt => (
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
))}
</Select>
<Select
placeholder="选择文档类型"
value={typeIds}
onChange={setTypeIds}
style={{ width: 200 }}
>
<Option value={null}>全部类型</Option>
<Option value="1">类型1</Option>
<Option value="2">类型2</Option>
<Option value="1,2">类型1+2</Option>
</Select>
</div>
{/* 统计卡片 */}
<Spin spinning={loading}>
<Row gutter={16}>
<Col span={6}>
<Card>
<Statistic
title="今日待审核"
value={stats.today_pending_files}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="已审核文件"
value={stats.monthly_reviewed_files}
suffix={
stats.monthly_review_growth && (
<span style={{
color: stats.monthly_review_growth.is_up ? '#52c41a' : '#f5222d',
fontSize: '14px'
}}>
{stats.monthly_review_growth.is_up ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
{stats.monthly_review_growth.value}%
</span>
)
}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="审核通过率"
value={stats.monthly_pass_rate}
precision={1}
suffix="%"
valueStyle={{ color: '#3f8600' }}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="检测到的问题"
value={stats.issues_detected}
valueStyle={{ color: '#cf1322' }}
/>
</Card>
</Col>
</Row>
</Spin>
</div>
)
}
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 🤖