bricks/aidocs/paging.md
2025-10-05 06:39:58 +08:00

272 lines
7.6 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.

# `bricks.BufferedDataLoader` 技术文档
> 一个用于分页加载数据并支持缓冲机制的前端数据加载器。
---
## 概述
`bricks.BufferedDataLoader` 是一个 JavaScript 类,旨在为需要高效处理大量分页数据的 UI 组件(如表格、列表等)提供**带缓冲的数据加载能力**。它通过限制内存中缓存的页面数量来优化性能,并支持向前/向后翻页操作。
该类适用于与支持分页接口的后端服务配合使用,典型场景包括大数据量表格滚动加载、虚拟滚动列表等。
---
## 命名空间
```js
bricks.BufferedDataLoader
```
依赖于全局对象 `window.bricks`,若不存在则自动创建。
---
## 构造函数
### `new bricks.BufferedDataLoader(widget, options)`
#### 参数
| 参数 | 类型 | 必填 | 描述 |
|------|------|------|------|
| `widget` | Object | ✅ | 数据展示组件实例,必须实现 `.clear_data()`, `.add_rows(rows, direction)`, `.del_old_rows(count, direction)` 方法。 |
| `opts` | Object | ✅ | 配置选项对象,详见下表。 |
#### `opts` 配置项
| 属性 | 类型 | 可选 | 默认值 | 说明 |
|------|------|------|--------|------|
| `url` | String | ❌ | - | 请求数据的 API 地址。 |
| `method` | String | ✅ | `'GET'` | HTTP 请求方法,如 `'GET'``'POST'`。 |
| `params` | Object | ✅ | `{}` | 固定请求参数(例如过滤条件、排序字段等)。 |
| `buffer_pages` | Number | ✅ | `5` | 最大缓存页数,超出时将移除最旧或最新的一页以释放内存。 |
| `pagerows` | Number | ✅ | `60` | 每页返回的数据行数。 |
---
## 实例属性
| 属性 | 类型 | 描述 |
|------|------|------|
| `cur_page` | Number | 当前正在加载/显示的页码(从 1 开始)。 |
| `total_records` | Number | 总记录数(由服务器返回填充)。 |
| `total_page` | Number | 总页数(根据 `total_records / pagerows` 计算得出)。 |
| `buffer` | Object | (预留扩展)当前用于存储已加载页面数据的缓存对象(本版本未实际使用)。 |
| `buffered_pages` | Number | 当前缓冲中的页面数量(影响是否触发旧数据清理)。 |
| `loading` | Boolean | 标志位,表示是否正在进行网络请求。 |
| `direction` | String | 上下方向标识:`'up'` 表示上一页,`'down'` 表示下一页,用于通知 widget 渲染策略。 |
| `cur_params` | Object | 合并后的当前请求参数(包含固定参数和动态传参)。 |
---
## 方法
### `initial()`
重置加载器状态,通常在初始化或重新加载前调用。
#### 功能:
- 重置当前页为 `-1`
- 清空缓冲计数
- 重置总记录数
- 清空当前参数副本
---
### `async loadData(params)`
初始化并加载第一页数据,清空现有数据。
#### 参数:
- `params` *(Object, 可选)*: 动态附加的请求参数(会与 `opts.params` 合并)
#### 返回值:
- `Promise<Object>`: 解析为服务器返回的数据结构,包含:
```js
{
total: 1000,
page: 1,
total_page: 17,
rows: [...]
}
```
#### 流程:
1. 调用 `initial()` 重置状态
2. 清空 widget 显示数据
3. 合并默认参数与传入参数
4. 设置每页行数 (`rows`) 和初始页码 (`page=1`)
5. 调用 `loadPage()` 加载第一页
#### 示例:
```js
const loader = new bricks.BufferedDataLoader(myWidget, {
url: '/api/data',
params: { category: 'A' },
pagerows: 50
});
await loader.loadData({ search: 'keyword' }); // 发送 {category:'A', search:'keyword', rows:50, page:1}
```
---
### `async loadPage(page)`
加载指定页码的数据(内部方法,也可手动调用)。
#### 参数:
- `page` *(Number, 可选)*: 指定要加载的页码,默认为当前 `this.cur_page + 1`(如果未设置)
> ⚠️ 注意:此方法内部管理 `cur_page`,外部应优先使用 `nextPage()` / `previousPage()`。
#### 行为逻辑:
1. 若正在加载,则直接返回(防重复提交)
2. 若缓冲页数达到上限(`buffer_pages`),则调用 `widget.del_old_rows()` 删除旧行
3. 构造完整请求参数(含 `page`, `rows` 等)
4. 使用 `bricks.HttpJson` 发起异步请求
5. 更新总记录数和总页数
6. 调用 `widget.add_rows()` 添加新数据
7. 增加缓冲页计数,释放加载锁
#### 返回值:
- `Promise<Object>`: 服务器响应数据
---
### `async nextPage()`
加载下一页数据。
#### 条件判断:
- 如果已在最后一页或正在加载,则不执行任何操作。
#### 行为:
- 设置 `direction = 'down'`
- `cur_page += 1`
- 调用 `loadPage()`
#### 示例:
```js
await loader.nextPage(); // 加载第 2 页
```
---
### `async previousPage()`
加载上一页数据。
#### 条件判断:
- 如果已是第一页或正在加载,则不执行。
#### 行为:
- 设置 `direction = 'up'`
- `cur_page -= 1`
- 调用 `loadPage()`
#### 示例:
```js
await loader.previousPage(); // 回退到前一页
```
---
## widget 接口要求
`BufferedDataLoader` 依赖传入的 `widget` 实现以下三个方法:
| 方法 | 签名 | 用途 |
|------|------|------|
| `.clear_data()` | `function(): void` | 清空当前所有数据显示 |
| `.add_rows(rows, direction)` | `function(Array, String): void` | 添加一批数据行,`direction` 为 `'up'` 或 `'down'`,可用于决定插入位置 |
| `.del_old_rows(count, direction)` | `function(Number, String): void` | 删除旧数据行(例如顶部或底部若干行),用于维持缓冲大小 |
> 💡 提示这些方法可结合虚拟滚动、DOM 复用等技术实现高性能渲染。
---
## 使用示例
```js
// 定义 widget 对象(模拟组件)
const myWidget = {
data: [],
clear_data() {
this.data = [];
console.log("数据已清空");
},
add_rows(rows, direction) {
if (direction === 'up') {
this.data.unshift(...rows);
} else {
this.data.push(...rows);
}
console.log(`添加了 ${rows.length} 行数据,方向: ${direction}`);
},
del_old_rows(count, direction) {
if (direction === 'up') {
this.data.splice(-count); // 删除末尾
} else {
this.data.splice(0, count); // 删除开头
}
console.log(`删除了 ${count} 行旧数据`);
}
};
// 创建 BufferedDataLoader 实例
const loader = new bricks.BufferedDataLoader(myWidget, {
url: '/api/list',
method: 'POST',
params: { type: 'report' },
buffer_pages: 3,
pagerows: 20
});
// 初始加载第一页
await loader.loadData({ keyword: 'test' });
// 下一页
await loader.nextPage();
// 上一页
await loader.previousPage();
```
---
## 注意事项
1. **线程安全**:通过 `this.loading` 实现简单的并发控制,防止重复请求。
2. **内存控制**:通过 `buffer_pages` 控制最大缓存页数,避免内存溢出。
3. **方向感知**`direction` 字段帮助 widget 区分上下滑动行为,便于优化 UI 渲染。
4. **总页数计算修正**
```js
if (d.total_page * this.pagerows < this.total_record) {
d.total_page += 1;
}
```
此处逻辑存在潜在错误(应使用向上取整),建议改为:
```js
d.total_page = Math.ceil(d.total / this.pagerows);
```
---
## 改进建议
| 问题 | 建议修复 |
|------|---------|
| `total_page` 计算错误 | 改为 `Math.ceil(total / pagerows)` |
| `buffer` 成员未实际使用 | 应实现真正的页面数据缓存以支持快速回退 |
| 缺少错误处理 | 在 `httpcall` 外包裹 `try-catch` 并抛出异常 |
| 不支持取消请求 | 可引入 `AbortController` 实现请求中断 |
---
## 版本信息
- 所属库:`bricks.js`
- 类名:`BufferedDataLoader`
- 创建时间:未知
- 作者:未知
---
📖 **文档生成于2025年4月5日**