|
|
@@ -0,0 +1,212 @@
|
|
|
+# Design Document: Work Records Backend Pagination
|
|
|
+
|
|
|
+## Overview
|
|
|
+
|
|
|
+本设计实现工作记录列表的后端分页功能,将当前的一次性加载全部数据改为按需加载当前页数据。主要修改涉及后端服务层、API路由层和前端组件。
|
|
|
+
|
|
|
+### 影响范围分析
|
|
|
+
|
|
|
+经代码分析,`WorkRecordService.get_all()` 方法仅在 `/work-records` GET 端点中被调用。其他功能模块(导出、汇总统计等)使用独立的查询方法,不受影响:
|
|
|
+
|
|
|
+| 模块 | 方法 | 影响 |
|
|
|
+|------|------|------|
|
|
|
+| 工作记录列表 | `get_all()` | ✅ 需要修改 |
|
|
|
+| 每日汇总 | `get_daily_summary()` | ❌ 不受影响 |
|
|
|
+| 月度汇总 | `get_monthly_summary()` | ❌ 不受影响 |
|
|
|
+| 年度汇总 | `get_yearly_summary()` | ❌ 不受影响 |
|
|
|
+| 批量结算 | `batch_update_settlement()` | ❌ 不受影响 |
|
|
|
+| 导出功能 | `ExportService` | ❌ 不受影响(独立查询) |
|
|
|
+
|
|
|
+### 向后兼容性
|
|
|
+
|
|
|
+- 分页参数 `page` 和 `page_size` 均为可选参数,有默认值
|
|
|
+- 不传分页参数时,行为与原来一致(返回第1页,默认20条)
|
|
|
+- API 响应结构新增 `pagination` 字段,原有 `data` 字段保持不变
|
|
|
+
|
|
|
+## Architecture
|
|
|
+
|
|
|
+```
|
|
|
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
|
+│ Frontend │ │ Backend API │ │ Database │
|
|
|
+│ WorkRecordList │────▶│ /work-records │────▶│ WorkRecord │
|
|
|
+│ │ │ │ │ │
|
|
|
+│ - page │ │ - get_all() │ │ - LIMIT │
|
|
|
+│ - page_size │ │ - pagination │ │ - OFFSET │
|
|
|
+│ - total │ │ - filters │ │ - COUNT │
|
|
|
+└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+### 分页流程
|
|
|
+
|
|
|
+1. 前端发送请求,携带 `page`、`page_size` 和筛选参数
|
|
|
+2. 后端服务层构建查询,应用筛选条件
|
|
|
+3. 执行 COUNT 查询获取总数
|
|
|
+4. 执行分页查询(LIMIT/OFFSET)获取当前页数据
|
|
|
+5. 返回数据和分页元数据
|
|
|
+
|
|
|
+## Components and Interfaces
|
|
|
+
|
|
|
+### Backend Service Layer
|
|
|
+
|
|
|
+修改 `WorkRecordService.get_all()` 方法:
|
|
|
+
|
|
|
+```python
|
|
|
+@staticmethod
|
|
|
+def get_all(person_id=None, start_date=None, end_date=None,
|
|
|
+ year=None, month=None, is_settled=None,
|
|
|
+ page=1, page_size=20):
|
|
|
+ """Get work records with pagination and filters.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ person_id: Filter by person ID (optional)
|
|
|
+ start_date: Filter by start date (optional)
|
|
|
+ end_date: Filter by end date (optional)
|
|
|
+ year: Filter by year (optional)
|
|
|
+ month: Filter by month 1-12 (optional)
|
|
|
+ is_settled: Filter by settlement status (optional)
|
|
|
+ page: Page number, starting from 1 (default: 1)
|
|
|
+ page_size: Number of records per page (default: 20, max: 100)
|
|
|
+
|
|
|
+ Returns:
|
|
|
+ Dictionary with:
|
|
|
+ - data: List of work record dictionaries
|
|
|
+ - total: Total number of matching records
|
|
|
+ - page: Current page number
|
|
|
+ - page_size: Records per page
|
|
|
+ - total_pages: Total number of pages
|
|
|
+ """
|
|
|
+```
|
|
|
+
|
|
|
+### Backend API Layer
|
|
|
+
|
|
|
+修改 `/work-records` GET 端点:
|
|
|
+
|
|
|
+```python
|
|
|
+@work_record_ns.param('page', '页码,从1开始', type=int, default=1)
|
|
|
+@work_record_ns.param('page_size', '每页数量,默认20,最大100', type=int, default=20)
|
|
|
+```
|
|
|
+
|
|
|
+### API Response Format
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "data": [...],
|
|
|
+ "pagination": {
|
|
|
+ "total": 150,
|
|
|
+ "page": 1,
|
|
|
+ "page_size": 20,
|
|
|
+ "total_pages": 8
|
|
|
+ },
|
|
|
+ "message": "Work records retrieved successfully"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Frontend API Service
|
|
|
+
|
|
|
+修改 `workRecordApi.getAll()` 调用:
|
|
|
+
|
|
|
+```javascript
|
|
|
+getAll: (params) => api.get('/work-records', {
|
|
|
+ params: {
|
|
|
+ ...params,
|
|
|
+ page: params.page || 1,
|
|
|
+ page_size: params.page_size || 20
|
|
|
+ }
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### Frontend Component
|
|
|
+
|
|
|
+修改 `WorkRecordList` 组件:
|
|
|
+
|
|
|
+- 添加 `currentPage` 和 `pageSize` 状态
|
|
|
+- 添加 `total` 状态存储后端返回的总数
|
|
|
+- 修改 Table 的 `pagination` 配置使用后端返回的 `total`
|
|
|
+- 添加 `onChange` 处理器响应分页变化
|
|
|
+
|
|
|
+## Data Models
|
|
|
+
|
|
|
+### Pagination Parameters
|
|
|
+
|
|
|
+| 参数 | 类型 | 默认值 | 范围 | 说明 |
|
|
|
+|------|------|--------|------|------|
|
|
|
+| page | int | 1 | >= 1 | 页码,小于1时使用1 |
|
|
|
+| page_size | int | 20 | 1-100 | 每页数量,超出范围使用20 |
|
|
|
+
|
|
|
+### Pagination Response
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| total | int | 匹配筛选条件的总记录数 |
|
|
|
+| page | int | 当前页码 |
|
|
|
+| page_size | int | 每页数量 |
|
|
|
+| total_pages | int | 总页数,计算公式: ceil(total / page_size) |
|
|
|
+
|
|
|
+## Correctness Properties
|
|
|
+
|
|
|
+*A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
|
|
+
|
|
|
+### Property 1: Pagination Data Count Correctness
|
|
|
+
|
|
|
+*For any* valid page and page_size parameters, the number of records returned should equal `min(page_size, total - (page - 1) * page_size)` when page is within valid range, or 0 when page exceeds total_pages.
|
|
|
+
|
|
|
+**Validates: Requirements 1.4, 4.3**
|
|
|
+
|
|
|
+### Property 2: Pagination Metadata Consistency
|
|
|
+
|
|
|
+*For any* pagination request, the returned metadata should satisfy:
|
|
|
+- `total_pages == ceil(total / page_size)`
|
|
|
+- `page == requested_page` (after normalization)
|
|
|
+- `page_size == requested_page_size` (after normalization)
|
|
|
+- `len(data) <= page_size`
|
|
|
+
|
|
|
+**Validates: Requirements 1.5**
|
|
|
+
|
|
|
+### Property 3: Filter and Pagination Composition
|
|
|
+
|
|
|
+*For any* combination of filter parameters and pagination parameters, the returned `total` should equal the count of all records matching the filter criteria, and the returned data should be a correct subset of filtered records for the requested page.
|
|
|
+
|
|
|
+**Validates: Requirements 2.1, 2.2**
|
|
|
+
|
|
|
+### Property 4: Sort Order Preservation
|
|
|
+
|
|
|
+*For any* pagination request, the returned records should be sorted by `work_date` descending, then by `id` ascending. This order should be consistent across all pages.
|
|
|
+
|
|
|
+**Validates: Requirements 2.3**
|
|
|
+
|
|
|
+### Property 5: Boundary Parameter Normalization
|
|
|
+
|
|
|
+*For any* page < 1, the service should return page 1 data.
|
|
|
+*For any* page_size < 1 or page_size > 100, the service should use page_size = 20.
|
|
|
+
|
|
|
+**Validates: Requirements 4.1, 4.2**
|
|
|
+
|
|
|
+## Error Handling
|
|
|
+
|
|
|
+| 场景 | 处理方式 |
|
|
|
+|------|----------|
|
|
|
+| page < 1 | 使用 page = 1 |
|
|
|
+| page_size < 1 或 > 100 | 使用 page_size = 20 |
|
|
|
+| page > total_pages | 返回空数据列表,分页元数据正确 |
|
|
|
+| 无匹配数据 | 返回空列表,total = 0,total_pages = 0 |
|
|
|
+
|
|
|
+## Testing Strategy
|
|
|
+
|
|
|
+### Unit Tests
|
|
|
+
|
|
|
+- 测试默认分页参数(不传 page/page_size)
|
|
|
+- 测试边界值处理(page=0, page=-1, page_size=0, page_size=101)
|
|
|
+- 测试空结果集的分页元数据
|
|
|
+
|
|
|
+### Property-Based Tests
|
|
|
+
|
|
|
+使用 `hypothesis` 库进行属性测试:
|
|
|
+
|
|
|
+- **Property 1**: 生成随机 page/page_size,验证返回数据量正确
|
|
|
+- **Property 2**: 生成随机请求,验证分页元数据一致性
|
|
|
+- **Property 3**: 生成随机筛选条件和分页参数,验证筛选与分页组合正确
|
|
|
+- **Property 4**: 生成随机分页请求,验证排序顺序
|
|
|
+- **Property 5**: 生成边界参数,验证参数规范化
|
|
|
+
|
|
|
+每个属性测试运行至少 100 次迭代。
|