zx-infinite-scroll 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +298 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# zx-infinite-scroll
|
|
2
2
|
|
|
3
3
|
Vue 3 无限滚动组件,支持下拉刷新、上拉加载。
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ Vue 3 无限滚动组件,支持下拉刷新、上拉加载。
|
|
|
15
15
|
## 安装
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install
|
|
18
|
+
npm install zx-infinite-scroll
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## 使用
|
|
@@ -24,7 +24,8 @@ npm install @zhixin/infinite-scroll
|
|
|
24
24
|
|
|
25
25
|
```vue
|
|
26
26
|
<script setup lang="ts">
|
|
27
|
-
import { InfiniteScrollV2 } from '
|
|
27
|
+
import { InfiniteScrollV2 } from 'zx-infinite-scroll';
|
|
28
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
28
29
|
|
|
29
30
|
interface Item {
|
|
30
31
|
id: number;
|
|
@@ -60,7 +61,8 @@ const fetchData = async ({ page, pageSize }) => {
|
|
|
60
61
|
|
|
61
62
|
```ts
|
|
62
63
|
import { createApp } from 'vue';
|
|
63
|
-
import InfiniteScrollPlugin from '
|
|
64
|
+
import InfiniteScrollPlugin from 'zx-infinite-scroll';
|
|
65
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
64
66
|
import App from './App.vue';
|
|
65
67
|
|
|
66
68
|
const app = createApp(App);
|
|
@@ -82,34 +84,83 @@ app.mount('#app');
|
|
|
82
84
|
|
|
83
85
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
84
86
|
|------|------|--------|------|
|
|
85
|
-
| fetchFn | `(params: FetchParams) => Promise<FetchResult<T>>` | - |
|
|
86
|
-
| params | `Record<string, any>` | `{}` |
|
|
87
|
+
| fetchFn | `(params: FetchParams) => Promise<FetchResult<T>>` | - | 数据请求函数(必填),需返回 `{ records: T[], total: number }` |
|
|
88
|
+
| params | `Record<string, any>` | `{}` | 查询参数(如筛选条件),变化时自动重新加载 |
|
|
87
89
|
| pageSize | `number` | `10` | 每页条数 |
|
|
88
90
|
| autoReload | `boolean` | `true` | params 变化时是否自动重新加载 |
|
|
89
91
|
| immediate | `boolean` | `true` | 是否在挂载时自动加载首页 |
|
|
90
92
|
| pullRefresh | `boolean` | `false` | 是否启用下拉刷新 |
|
|
91
93
|
| pullThreshold | `number` | `70` | 下拉刷新触发距离(px) |
|
|
92
|
-
| loadThreshold | `number` | `50` |
|
|
94
|
+
| loadThreshold | `number` | `50` | 上拉加载触发距离(距离底部 px) |
|
|
95
|
+
| emptyText | `string` | `'暂无数据'` | 空状态文案 |
|
|
96
|
+
| pullingText | `string` | `'下拉刷新'` | 下拉中的文案 |
|
|
97
|
+
| loosingText | `string` | `'释放即可刷新'` | 释放刷新的文案 |
|
|
98
|
+
| refreshingText | `string` | `'加载中...'` | 刷新中的文案 |
|
|
99
|
+
| loadingText | `string` | `'加载中...'` | 加载中的文案 |
|
|
100
|
+
| finishedText | `string` | `'没有更多了~'` | 加载完成的文案 |
|
|
101
|
+
| errorText | `string` | `'加载失败,点击重试'` | 加载失败的文案 |
|
|
102
|
+
|
|
103
|
+
#### FetchParams 类型
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface FetchParams {
|
|
107
|
+
page: number; // 当前页码,从 1 开始
|
|
108
|
+
pageSize: number; // 每页条数
|
|
109
|
+
[key: string]: any; // 其他自定义参数(从 params prop 传入)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### FetchResult 类型
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface FetchResult<T> {
|
|
117
|
+
records: T[]; // 当前页数据列表
|
|
118
|
+
total: number; // 总条数
|
|
119
|
+
}
|
|
120
|
+
```
|
|
93
121
|
|
|
94
122
|
### InfiniteScrollV2 Events
|
|
95
123
|
|
|
96
124
|
| 事件 | 参数 | 说明 |
|
|
97
125
|
|------|------|------|
|
|
98
|
-
| success | `{ records: T[], total: number, reset: boolean }` |
|
|
99
|
-
| error | `err: unknown` |
|
|
126
|
+
| success | `{ records: T[], total: number, reset: boolean }` | 加载成功时触发,每次成功(含分页)都会触发 |
|
|
127
|
+
| error | `err: unknown` | 加载失败时触发 |
|
|
128
|
+
| update:list | `list: T[]` | 列表内容变化时触发(支持 v-model:list) |
|
|
100
129
|
|
|
101
130
|
### InfiniteScrollV2 Slots
|
|
102
131
|
|
|
103
132
|
| 插槽 | 参数 | 说明 |
|
|
104
133
|
|------|------|------|
|
|
105
|
-
| default | `{ list, loading, finished, isEmpty, error, total, reload }` |
|
|
134
|
+
| default | `{ list, loading, finished, isEmpty, error, total, reload }` | 列表内容插槽 |
|
|
135
|
+
|
|
136
|
+
#### Slot 参数说明
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
{
|
|
140
|
+
list: T[]; // 当前列表数据
|
|
141
|
+
loading: boolean; // 是否正在加载
|
|
142
|
+
finished: boolean; // 是否已加载完所有数据
|
|
143
|
+
isEmpty: boolean; // 是否为空数据
|
|
144
|
+
error: boolean; // 是否加载失败
|
|
145
|
+
total: number; // 总条数
|
|
146
|
+
reload: () => void; // 重新加载第一页的方法
|
|
147
|
+
}
|
|
148
|
+
```
|
|
106
149
|
|
|
107
150
|
### InfiniteScrollV2 Expose
|
|
108
151
|
|
|
109
|
-
| 方法 | 说明 |
|
|
110
|
-
|
|
111
|
-
| reload() | 重新加载第一页 |
|
|
112
|
-
| refresh() | 同 reload |
|
|
152
|
+
| 方法 | 类型 | 说明 |
|
|
153
|
+
|------|------|------|
|
|
154
|
+
| reload() | `() => Promise<void>` | 重新加载第一页 |
|
|
155
|
+
| refresh() | `() => Promise<void>` | 同 reload(别名) |
|
|
156
|
+
| list | `Ref<T[]>` | 当前列表数据(响应式) |
|
|
157
|
+
| loading | `Ref<boolean>` | 加载中状态(响应式) |
|
|
158
|
+
| finished | `Ref<boolean>` | 是否已加载完(响应式) |
|
|
159
|
+
| isEmpty | `Ref<boolean>` | 是否为空数据(响应式) |
|
|
160
|
+
| total | `Ref<number>` | 总条数(响应式) |
|
|
161
|
+
| finishLoad() | `() => void` | 完成上拉加载(透传 v1 方法) |
|
|
162
|
+
| finishRefresh() | `() => void` | 完成下拉刷新(透传 v1 方法) |
|
|
163
|
+
| checkContentHeight() | `() => void` | 检查内容高度(透传 v1 方法) |
|
|
113
164
|
|
|
114
165
|
## InfiniteScroll(基础组件)
|
|
115
166
|
|
|
@@ -118,7 +169,8 @@ app.mount('#app');
|
|
|
118
169
|
```vue
|
|
119
170
|
<script setup lang="ts">
|
|
120
171
|
import { ref } from 'vue';
|
|
121
|
-
import { InfiniteScroll } from '
|
|
172
|
+
import { InfiniteScroll } from 'zx-infinite-scroll';
|
|
173
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
122
174
|
|
|
123
175
|
const list = ref([]);
|
|
124
176
|
const loading = ref(false);
|
|
@@ -148,6 +200,237 @@ const onRefresh = async () => {
|
|
|
148
200
|
</template>
|
|
149
201
|
```
|
|
150
202
|
|
|
203
|
+
### InfiniteScroll Props
|
|
204
|
+
|
|
205
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
206
|
+
|------|------|--------|------|
|
|
207
|
+
| loading | `boolean` | `false` | 是否处于加载中状态 |
|
|
208
|
+
| finished | `boolean` | `false` | 是否已加载完所有数据 |
|
|
209
|
+
| isEmpty | `boolean` | `false` | 是否显示空状态 |
|
|
210
|
+
| emptyText | `string` | `'暂无数据'` | 空状态文案 |
|
|
211
|
+
| pullRefresh | `boolean` | `false` | 是否启用下拉刷新 |
|
|
212
|
+
| pullThreshold | `number` | `70` | 下拉刷新触发距离(px) |
|
|
213
|
+
| loadThreshold | `number` | `50` | 上拉加载触发距离(距离底部 px) |
|
|
214
|
+
| pullingText | `string` | `'下拉刷新'` | 下拉中的文案 |
|
|
215
|
+
| loosingText | `string` | `'释放即可刷新'` | 释放刷新的文案 |
|
|
216
|
+
| refreshingText | `string` | `'加载中...'` | 刷新中的文案 |
|
|
217
|
+
| loadingText | `string` | `'加载中...'` | 加载中的文案 |
|
|
218
|
+
| finishedText | `string` | `'没有更多了~'` | 加载完成的文案 |
|
|
219
|
+
| errorText | `string` | `'加载失败,点击重试'` | 加载失败的文案 |
|
|
220
|
+
| error | `boolean` | `false` | 是否显示错误状态 |
|
|
221
|
+
| contentLoading | `boolean` | `false` | 内容区域是否显示加载状态(用于下拉刷新时的遮罩) |
|
|
222
|
+
|
|
223
|
+
### InfiniteScroll Events
|
|
224
|
+
|
|
225
|
+
| 事件 | 参数 | 说明 |
|
|
226
|
+
|------|------|------|
|
|
227
|
+
| load | - | 触发上拉加载时触发 |
|
|
228
|
+
| refresh | - | 触发下拉刷新时触发 |
|
|
229
|
+
| retry | - | 点击错误重试时触发 |
|
|
230
|
+
|
|
231
|
+
### InfiniteScroll Expose
|
|
232
|
+
|
|
233
|
+
| 方法 | 类型 | 说明 |
|
|
234
|
+
|------|------|------|
|
|
235
|
+
| finishRefresh() | `() => void` | 完成下拉刷新,调用后收起刷新指示器 |
|
|
236
|
+
| finishPullDown() | `() => void` | 同 finishRefresh(别名) |
|
|
237
|
+
| finishLoad() | `() => void` | 完成上拉加载 |
|
|
238
|
+
| finishPullUp() | `() => void` | 同 finishLoad(别名) |
|
|
239
|
+
| checkContentHeight() | `() => void` | 检查内容高度,不足一屏时自动触发加载 |
|
|
240
|
+
|
|
241
|
+
### InfiniteScroll Slots
|
|
242
|
+
|
|
243
|
+
| 插槽 | 说明 |
|
|
244
|
+
|------|------|
|
|
245
|
+
| default | 列表内容 |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 完整使用示例
|
|
250
|
+
|
|
251
|
+
### 示例 1:基础列表
|
|
252
|
+
|
|
253
|
+
```vue
|
|
254
|
+
<script setup lang="ts">
|
|
255
|
+
import { InfiniteScrollV2 } from 'zx-infinite-scroll';
|
|
256
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
257
|
+
|
|
258
|
+
interface Item {
|
|
259
|
+
id: number;
|
|
260
|
+
name: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const fetchData = async ({ page, pageSize }) => {
|
|
264
|
+
const res = await fetch(`/api/list?page=${page}&pageSize=${pageSize}`);
|
|
265
|
+
const data = await res.json();
|
|
266
|
+
return {
|
|
267
|
+
records: data.list,
|
|
268
|
+
total: data.total
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
</script>
|
|
272
|
+
|
|
273
|
+
<template>
|
|
274
|
+
<InfiniteScrollV2 :fetch-fn="fetchData">
|
|
275
|
+
<template #default="{ list }">
|
|
276
|
+
<div v-for="item in list" :key="item.id" class="item">
|
|
277
|
+
{{ item.name }}
|
|
278
|
+
</div>
|
|
279
|
+
</template>
|
|
280
|
+
</InfiniteScrollV2>
|
|
281
|
+
</template>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 示例 2:带下拉刷新和筛选参数
|
|
285
|
+
|
|
286
|
+
```vue
|
|
287
|
+
<script setup lang="ts">
|
|
288
|
+
import { ref } from 'vue';
|
|
289
|
+
import { InfiniteScrollV2 } from 'zx-infinite-scroll';
|
|
290
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
291
|
+
|
|
292
|
+
const keyword = ref('');
|
|
293
|
+
const category = ref('');
|
|
294
|
+
|
|
295
|
+
const fetchData = async ({ page, pageSize, keyword, category }) => {
|
|
296
|
+
const res = await fetch(
|
|
297
|
+
`/api/list?page=${page}&pageSize=${pageSize}&keyword=${keyword}&category=${category}`
|
|
298
|
+
);
|
|
299
|
+
return await res.json();
|
|
300
|
+
};
|
|
301
|
+
</script>
|
|
302
|
+
|
|
303
|
+
<template>
|
|
304
|
+
<input v-model="keyword" placeholder="搜索关键词" />
|
|
305
|
+
<select v-model="category">
|
|
306
|
+
<option value="">全部分类</option>
|
|
307
|
+
<option value="1">分类1</option>
|
|
308
|
+
<option value="2">分类2</option>
|
|
309
|
+
</select>
|
|
310
|
+
|
|
311
|
+
<InfiniteScrollV2
|
|
312
|
+
:fetch-fn="fetchData"
|
|
313
|
+
:params="{ keyword, category }"
|
|
314
|
+
:pull-refresh="true"
|
|
315
|
+
:page-size="20"
|
|
316
|
+
>
|
|
317
|
+
<template #default="{ list, loading, reload }">
|
|
318
|
+
<div v-for="item in list" :key="item.id" class="item">
|
|
319
|
+
{{ item.name }}
|
|
320
|
+
</div>
|
|
321
|
+
<button v-if="!loading" @click="reload">手动刷新</button>
|
|
322
|
+
</template>
|
|
323
|
+
</InfiniteScrollV2>
|
|
324
|
+
</template>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 示例 3:监听事件
|
|
328
|
+
|
|
329
|
+
```vue
|
|
330
|
+
<script setup lang="ts">
|
|
331
|
+
import { InfiniteScrollV2 } from 'zx-infinite-scroll';
|
|
332
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
333
|
+
|
|
334
|
+
const handleSuccess = ({ records, total, reset }) => {
|
|
335
|
+
console.log('加载成功', { records, total, reset });
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const handleError = (err) => {
|
|
339
|
+
console.error('加载失败', err);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const fetchData = async ({ page, pageSize }) => {
|
|
343
|
+
// 模拟可能失败的请求
|
|
344
|
+
const res = await fetch(`/api/list?page=${page}&pageSize=${pageSize}`);
|
|
345
|
+
if (!res.ok) throw new Error('请求失败');
|
|
346
|
+
return await res.json();
|
|
347
|
+
};
|
|
348
|
+
</script>
|
|
349
|
+
|
|
350
|
+
<template>
|
|
351
|
+
<InfiniteScrollV2
|
|
352
|
+
:fetch-fn="fetchData"
|
|
353
|
+
@success="handleSuccess"
|
|
354
|
+
@error="handleError"
|
|
355
|
+
>
|
|
356
|
+
<template #default="{ list }">
|
|
357
|
+
<div v-for="item in list" :key="item.id">
|
|
358
|
+
{{ item.name }}
|
|
359
|
+
</div>
|
|
360
|
+
</template>
|
|
361
|
+
</InfiniteScrollV2>
|
|
362
|
+
</template>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 示例 4:使用基础组件(手动控制)
|
|
366
|
+
|
|
367
|
+
```vue
|
|
368
|
+
<script setup lang="ts">
|
|
369
|
+
import { ref } from 'vue';
|
|
370
|
+
import { InfiniteScroll } from 'zx-infinite-scroll';
|
|
371
|
+
import 'zx-infinite-scroll/dist/style.css';
|
|
372
|
+
|
|
373
|
+
const scrollRef = ref();
|
|
374
|
+
const list = ref([]);
|
|
375
|
+
const loading = ref(false);
|
|
376
|
+
const finished = ref(false);
|
|
377
|
+
const error = ref(false);
|
|
378
|
+
const page = ref(1);
|
|
379
|
+
const total = ref(0);
|
|
380
|
+
|
|
381
|
+
const loadData = async () => {
|
|
382
|
+
loading.value = true;
|
|
383
|
+
error.value = false;
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch(`/api/list?page=${page.value}&pageSize=10`);
|
|
386
|
+
const data = await res.json();
|
|
387
|
+
list.value.push(...data.records);
|
|
388
|
+
total.value = data.total;
|
|
389
|
+
finished.value = list.value.length >= total.value;
|
|
390
|
+
page.value++;
|
|
391
|
+
} catch (err) {
|
|
392
|
+
error.value = true;
|
|
393
|
+
} finally {
|
|
394
|
+
loading.value = false;
|
|
395
|
+
scrollRef.value?.finishLoad();
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const refreshData = async () => {
|
|
400
|
+
page.value = 1;
|
|
401
|
+
list.value = [];
|
|
402
|
+
finished.value = false;
|
|
403
|
+
await loadData();
|
|
404
|
+
scrollRef.value?.finishRefresh();
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const retryLoad = () => {
|
|
408
|
+
if (list.value.length === 0) {
|
|
409
|
+
refreshData();
|
|
410
|
+
} else {
|
|
411
|
+
loadData();
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
</script>
|
|
415
|
+
|
|
416
|
+
<template>
|
|
417
|
+
<InfiniteScroll
|
|
418
|
+
ref="scrollRef"
|
|
419
|
+
:loading="loading"
|
|
420
|
+
:finished="finished"
|
|
421
|
+
:error="error"
|
|
422
|
+
:pull-refresh="true"
|
|
423
|
+
@load="loadData"
|
|
424
|
+
@refresh="refreshData"
|
|
425
|
+
@retry="retryLoad"
|
|
426
|
+
>
|
|
427
|
+
<div v-for="item in list" :key="item.id" class="item">
|
|
428
|
+
{{ item.name }}
|
|
429
|
+
</div>
|
|
430
|
+
</InfiniteScroll>
|
|
431
|
+
</template>
|
|
432
|
+
```
|
|
433
|
+
|
|
151
434
|
## License
|
|
152
435
|
|
|
153
436
|
MIT
|