161 lines
4.4 KiB
Markdown
161 lines
4.4 KiB
Markdown
# 分页规范
|
||
|
||
## 概述
|
||
|
||
统一使用 `pageNo`/`pageSize` 作为分页参数,`BaseService.get_page()` 封装分页逻辑,返回标准分页结构。
|
||
|
||
:::tip 分页约定
|
||
- 前端传递 `pageNo`(页码,从 1 开始)和 `pageSize`(每页条数)
|
||
- 后端返回 `{records, total, size, current, pages}` 结构
|
||
- 默认每页 10 条(`PAGE_SIZE = 10`)
|
||
:::
|
||
|
||
## 请求参数
|
||
|
||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||
|------|------|------|--------|------|
|
||
| `pageNo` | int | 否 | 1 | 页码,从 1 开始 |
|
||
| `pageSize` | int | 否 | 10 | 每页条数 |
|
||
|
||
### 请求示例
|
||
|
||
```bash
|
||
# GET 请求
|
||
GET /api/v1/position/page?pageNo=1&pageSize=10
|
||
|
||
# 带筛选条件
|
||
GET /api/v1/position/page?pageNo=1&pageSize=10&name=开发&status=1
|
||
```
|
||
|
||
## 响应格式
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"records": [
|
||
{
|
||
"id": 1,
|
||
"name": "开发工程师",
|
||
"status": 1,
|
||
"statusText": "在用",
|
||
"sort": 1,
|
||
"createTime": "2026-01-01 10:00:00"
|
||
}
|
||
],
|
||
"total": 50,
|
||
"size": 10,
|
||
"current": 1,
|
||
"pages": 5
|
||
},
|
||
"msg": "操作成功",
|
||
"ok": true
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `records` | array | 当前页数据列表 |
|
||
| `total` | int | 总记录数 |
|
||
| `size` | int | 每页条数 |
|
||
| `current` | int | 当前页码 |
|
||
| `pages` | int | 总页数 |
|
||
|
||
## BaseService 实现
|
||
|
||
### get_page 方法
|
||
|
||
```python
|
||
def get_page(self, request) -> R:
|
||
"""分页查询"""
|
||
page, limit = parse_pagination(request)
|
||
query = self._build_page_query(request)
|
||
items, total = self.repo.paginate(page, limit, query)
|
||
|
||
# 超页自动回退第 1 页
|
||
if page > 1 and not items:
|
||
page = 1
|
||
items, total = self.repo.paginate(page, limit, query)
|
||
|
||
result = [self._serialize(item) for item in items]
|
||
return R.page(result, total, page, limit)
|
||
```
|
||
|
||
:::tip 超页回退
|
||
当请求的页码超过总页数时(如删除数据后前端未刷新),自动回退到第 1 页,避免返回空数据。
|
||
:::
|
||
|
||
### 分页查询构建
|
||
|
||
```python
|
||
def _build_page_query(self, request):
|
||
"""组装分页查询:声明字段过滤 + 排序"""
|
||
query = self.repo.filter()
|
||
query = self._apply_page_filters(query, request)
|
||
|
||
for field_name, direction in self.page_order_by:
|
||
column = getattr(self.model, field_name)
|
||
query = query.order_by(column.asc() if direction == 'asc' else column.desc())
|
||
|
||
return query
|
||
```
|
||
|
||
### 字段过滤声明
|
||
|
||
```python
|
||
class PositionService(BaseService(Position]):
|
||
# 模糊查询字段:name LIKE %值%
|
||
page_like_fields = ('name',)
|
||
# 等值查询字段:status = 值
|
||
page_eq_fields = ('status',)
|
||
# 排序规则
|
||
page_order_by = (('sort', 'asc'),)
|
||
|
||
:::tip 过滤字段说明
|
||
- `page_like_fields`:从 query_params 取同名参数,自动应用 `LIKE %值%`,空值跳过
|
||
- `page_eq_fields`:从 query_params 取同名参数,自动应用 `=值`,空值跳过
|
||
- 两种字段均自动跳过 None/空字符串,无需手动判断
|
||
:::
|
||
|
||
## Repository 分页实现
|
||
|
||
```python
|
||
def paginate(self, page_no: int = 1, page_size: int = PAGE_SIZE, query=None):
|
||
"""分页查询,返回 (items, total)"""
|
||
q = query if query is not None else self.db.query(self.model)
|
||
q = q.filter(self._soft_col() == 0) # 自动软删过滤
|
||
total = q.count()
|
||
items = q.limit(page_size).offset((page_no - 1) * page_size).all()
|
||
return items, total
|
||
```
|
||
|
||
## Handler 示例
|
||
|
||
```python
|
||
class PositionPageHandler(BaseHandler):
|
||
@permission_required("sys:position:list")
|
||
async def get(self):
|
||
"""获取岗位分页列表"""
|
||
return await position_service.get_page(self)
|
||
```
|
||
|
||
:::tip 权限标识
|
||
分页接口权限标识格式:`sys:{模块名}:page`
|
||
:::
|
||
|
||
## 自定义分页过滤
|
||
|
||
子类可覆盖 `_apply_page_filters` 添加固定过滤条件:
|
||
|
||
```python
|
||
def _apply_page_filters(self, query, request):
|
||
"""添加固定过滤:只显示启用状态的数据"""
|
||
query = super()._apply_page_filters(query, request)
|
||
query = query.filter(self.model.status == 1)
|
||
return query
|
||
```
|
||
|
||
## 总结
|
||
|
||
分页规范统一使用 `pageNo`/`pageSize` 参数,通过 `BaseService.get_page()` 自动处理分页逻辑。子类只需声明 `page_like_fields`/`page_eq_fields`/`page_order_by` 即可获得完整的分页能力。响应使用 `R.page()` 封装标准分页结构,前端可直接使用 `records`/`total`/`pages` 渲染列表和分页组件。
|