zx-infinite-scroll 1.0.0

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 ADDED
@@ -0,0 +1,153 @@
1
+ # @zhixin/infinite-scroll
2
+
3
+ Vue 3 无限滚动组件,支持下拉刷新、上拉加载。
4
+
5
+ ## 特性
6
+
7
+ - ✅ 支持下拉刷新(可选)
8
+ - ✅ 支持上拉加载更多
9
+ - ✅ 自动检测内容高度不足时加载
10
+ - ✅ 防抖/节流优化
11
+ - ✅ 支持错误重试
12
+ - ✅ 完整的 TypeScript 类型支持
13
+ - ✅ 两个版本:基础组件 + 封装组件
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install @zhixin/infinite-scroll
19
+ ```
20
+
21
+ ## 使用
22
+
23
+ ### 方式一:按需引入
24
+
25
+ ```vue
26
+ <script setup lang="ts">
27
+ import { InfiniteScrollV2 } from '@zhixin/infinite-scroll';
28
+
29
+ interface Item {
30
+ id: number;
31
+ name: string;
32
+ }
33
+
34
+ const fetchData = async ({ page, pageSize }) => {
35
+ const res = await fetch(`/api/list?page=${page}&pageSize=${pageSize}`);
36
+ const data = await res.json();
37
+ return {
38
+ records: data.list,
39
+ total: data.total
40
+ };
41
+ };
42
+ </script>
43
+
44
+ <template>
45
+ <InfiniteScrollV2
46
+ :fetch-fn="fetchData"
47
+ :pull-refresh="true"
48
+ :page-size="20"
49
+ >
50
+ <template #default="{ list }">
51
+ <div v-for="item in list" :key="item.id">
52
+ {{ item.name }}
53
+ </div>
54
+ </template>
55
+ </InfiniteScrollV2>
56
+ </template>
57
+ ```
58
+
59
+ ### 方式二:全局注册
60
+
61
+ ```ts
62
+ import { createApp } from 'vue';
63
+ import InfiniteScrollPlugin from '@zhixin/infinite-scroll';
64
+ import App from './App.vue';
65
+
66
+ const app = createApp(App);
67
+ app.use(InfiniteScrollPlugin);
68
+ app.mount('#app');
69
+ ```
70
+
71
+ ```vue
72
+ <template>
73
+ <InfiniteScrollV2 :fetch-fn="fetchData">
74
+ <!-- ... -->
75
+ </InfiniteScrollV2>
76
+ </template>
77
+ ```
78
+
79
+ ## API
80
+
81
+ ### InfiniteScrollV2 Props
82
+
83
+ | 属性 | 类型 | 默认值 | 说明 |
84
+ |------|------|--------|------|
85
+ | fetchFn | `(params: FetchParams) => Promise<FetchResult<T>>` | - | 数据请求函数(必填) |
86
+ | params | `Record<string, any>` | `{}` | 查询参数,变化时自动重新加载 |
87
+ | pageSize | `number` | `10` | 每页条数 |
88
+ | autoReload | `boolean` | `true` | params 变化时是否自动重新加载 |
89
+ | immediate | `boolean` | `true` | 是否在挂载时自动加载首页 |
90
+ | pullRefresh | `boolean` | `false` | 是否启用下拉刷新 |
91
+ | pullThreshold | `number` | `70` | 下拉刷新触发距离(px) |
92
+ | loadThreshold | `number` | `50` | 上拉加载触发距离(px) |
93
+
94
+ ### InfiniteScrollV2 Events
95
+
96
+ | 事件 | 参数 | 说明 |
97
+ |------|------|------|
98
+ | success | `{ records: T[], total: number, reset: boolean }` | 加载成功 |
99
+ | error | `err: unknown` | 加载失败 |
100
+
101
+ ### InfiniteScrollV2 Slots
102
+
103
+ | 插槽 | 参数 | 说明 |
104
+ |------|------|------|
105
+ | default | `{ list, loading, finished, isEmpty, error, total, reload }` | 列表内容 |
106
+
107
+ ### InfiniteScrollV2 Expose
108
+
109
+ | 方法 | 说明 |
110
+ |------|------|
111
+ | reload() | 重新加载第一页 |
112
+ | refresh() | 同 reload |
113
+
114
+ ## InfiniteScroll(基础组件)
115
+
116
+ 如果需要更多控制权,可以使用基础组件:
117
+
118
+ ```vue
119
+ <script setup lang="ts">
120
+ import { ref } from 'vue';
121
+ import { InfiniteScroll } from '@zhixin/infinite-scroll';
122
+
123
+ const list = ref([]);
124
+ const loading = ref(false);
125
+ const finished = ref(false);
126
+
127
+ const onLoad = async () => {
128
+ // 自行实现加载逻辑
129
+ };
130
+
131
+ const onRefresh = async () => {
132
+ // 自行实现刷新逻辑
133
+ };
134
+ </script>
135
+
136
+ <template>
137
+ <InfiniteScroll
138
+ :loading="loading"
139
+ :finished="finished"
140
+ :pull-refresh="true"
141
+ @load="onLoad"
142
+ @refresh="onRefresh"
143
+ >
144
+ <div v-for="item in list" :key="item.id">
145
+ {{ item.name }}
146
+ </div>
147
+ </InfiniteScroll>
148
+ </template>
149
+ ```
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,253 @@
1
+ import { App } from 'vue';
2
+ import { ComponentOptionsMixin } from 'vue';
3
+ import { ComponentProvideOptions } from 'vue';
4
+ import { DefineComponent } from 'vue';
5
+ import { ExtractPropTypes } from 'vue';
6
+ import { default as InfiniteScrollV2 } from './components/infinite-scroll-v2.vue';
7
+ import { PropType } from 'vue';
8
+ import { PublicProps } from 'vue';
9
+
10
+ declare const __VLS_component: DefineComponent<ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<Props>, {
11
+ loading: boolean;
12
+ finished: boolean;
13
+ isEmpty: boolean;
14
+ emptyText: string;
15
+ pullRefresh: boolean;
16
+ pullThreshold: number;
17
+ loadThreshold: number;
18
+ pullingText: string;
19
+ loosingText: string;
20
+ refreshingText: string;
21
+ loadingText: string;
22
+ finishedText: string;
23
+ errorText: string;
24
+ error: boolean;
25
+ contentLoading: boolean;
26
+ }>>, {
27
+ refresh: typeof finishRefresh;
28
+ finishRefresh: typeof finishRefresh;
29
+ finishPullDown: typeof finishRefresh;
30
+ finishLoad: typeof finishLoad;
31
+ finishPullUp: typeof finishLoad;
32
+ checkContentHeight: typeof checkContentHeight;
33
+ }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
34
+ refresh: () => void;
35
+ load: () => void;
36
+ retry: () => void;
37
+ }, string, PublicProps, Readonly<ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<Props>, {
38
+ loading: boolean;
39
+ finished: boolean;
40
+ isEmpty: boolean;
41
+ emptyText: string;
42
+ pullRefresh: boolean;
43
+ pullThreshold: number;
44
+ loadThreshold: number;
45
+ pullingText: string;
46
+ loosingText: string;
47
+ refreshingText: string;
48
+ loadingText: string;
49
+ finishedText: string;
50
+ errorText: string;
51
+ error: boolean;
52
+ contentLoading: boolean;
53
+ }>>> & Readonly<{
54
+ onRefresh?: (() => any) | undefined;
55
+ onLoad?: (() => any) | undefined;
56
+ onRetry?: (() => any) | undefined;
57
+ }>, {
58
+ loading: boolean;
59
+ finished: boolean;
60
+ isEmpty: boolean;
61
+ emptyText: string;
62
+ pullRefresh: boolean;
63
+ pullThreshold: number;
64
+ loadThreshold: number;
65
+ pullingText: string;
66
+ loosingText: string;
67
+ refreshingText: string;
68
+ loadingText: string;
69
+ finishedText: string;
70
+ errorText: string;
71
+ error: boolean;
72
+ contentLoading: boolean;
73
+ }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
74
+
75
+ declare const __VLS_component_2: DefineComponent<ExtractPropTypes<__VLS_WithDefaults_3<__VLS_TypePropsToRuntimeProps_3<Props_2>, {
76
+ text: string;
77
+ showText: boolean;
78
+ paddingTop: number;
79
+ emptyImage: string;
80
+ }>>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes<__VLS_WithDefaults_3<__VLS_TypePropsToRuntimeProps_3<Props_2>, {
81
+ text: string;
82
+ showText: boolean;
83
+ paddingTop: number;
84
+ emptyImage: string;
85
+ }>>> & Readonly<{}>, {
86
+ text: string;
87
+ showText: boolean;
88
+ paddingTop: number | string;
89
+ emptyImage: string;
90
+ }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
91
+
92
+ declare type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
93
+
94
+ declare type __VLS_NonUndefinedable_2<T> = T extends undefined ? never : T;
95
+
96
+ declare type __VLS_NonUndefinedable_3<T> = T extends undefined ? never : T;
97
+
98
+ declare type __VLS_Prettify<T> = {
99
+ [K in keyof T]: T[K];
100
+ } & {};
101
+
102
+ declare type __VLS_Prettify_2<T> = {
103
+ [K in keyof T]: T[K];
104
+ } & {};
105
+
106
+ declare type __VLS_Prettify_3<T> = {
107
+ [K in keyof T]: T[K];
108
+ } & {};
109
+
110
+ declare function __VLS_template(): {
111
+ default?(_: {}): any;
112
+ };
113
+
114
+ declare function __VLS_template_2(): {
115
+ default?(_: {}): any;
116
+ };
117
+
118
+ declare type __VLS_TypePropsToRuntimeProps<T> = {
119
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
120
+ type: PropType<__VLS_NonUndefinedable<T[K]>>;
121
+ } : {
122
+ type: PropType<T[K]>;
123
+ required: true;
124
+ };
125
+ };
126
+
127
+ declare type __VLS_TypePropsToRuntimeProps_2<T> = {
128
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
129
+ type: PropType<__VLS_NonUndefinedable_2<T[K]>>;
130
+ } : {
131
+ type: PropType<T[K]>;
132
+ required: true;
133
+ };
134
+ };
135
+
136
+ declare type __VLS_TypePropsToRuntimeProps_3<T> = {
137
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
138
+ type: PropType<__VLS_NonUndefinedable_3<T[K]>>;
139
+ } : {
140
+ type: PropType<T[K]>;
141
+ required: true;
142
+ };
143
+ };
144
+
145
+ declare type __VLS_WithDefaults<P, D> = {
146
+ [K in keyof Pick<P, keyof P>]: K extends keyof D ? __VLS_Prettify<P[K] & {
147
+ default: D[K];
148
+ }> : P[K];
149
+ };
150
+
151
+ declare type __VLS_WithDefaults_2<P, D> = {
152
+ [K in keyof Pick<P, keyof P>]: K extends keyof D ? __VLS_Prettify_2<P[K] & {
153
+ default: D[K];
154
+ }> : P[K];
155
+ };
156
+
157
+ declare type __VLS_WithDefaults_3<P, D> = {
158
+ [K in keyof Pick<P, keyof P>]: K extends keyof D ? __VLS_Prettify_3<P[K] & {
159
+ default: D[K];
160
+ }> : P[K];
161
+ };
162
+
163
+ declare type __VLS_WithTemplateSlots<T, S> = T & {
164
+ new (): {
165
+ $slots: S;
166
+ };
167
+ };
168
+
169
+ declare type __VLS_WithTemplateSlots_2<T, S> = T & {
170
+ new (): {
171
+ $slots: S;
172
+ };
173
+ };
174
+
175
+ declare function checkContentHeight(): void;
176
+
177
+ declare const _default: {
178
+ install: typeof install;
179
+ };
180
+ export default _default;
181
+
182
+ export declare const EmptyComponent: __VLS_WithTemplateSlots_2<typeof __VLS_component_2, ReturnType<typeof __VLS_template_2>>;
183
+
184
+ declare function finishLoad(): void;
185
+
186
+ declare function finishRefresh(): void;
187
+
188
+ export declare const InfiniteScroll: __VLS_WithTemplateSlots<typeof __VLS_component, ReturnType<typeof __VLS_template>>;
189
+
190
+ export { InfiniteScrollV2 }
191
+
192
+ export declare function install(app: App): void;
193
+
194
+ declare interface Props {
195
+ /** 是否处于加载中状态 */
196
+ loading?: boolean;
197
+ /** 是否已加载完所有数据 */
198
+ finished?: boolean;
199
+ /** 是否显示空状态 */
200
+ isEmpty?: boolean;
201
+ /** 空状态文案 */
202
+ emptyText?: string;
203
+ /** 是否启用下拉刷新 */
204
+ pullRefresh?: boolean;
205
+ /** 下拉刷新触发距离(px) */
206
+ pullThreshold?: number;
207
+ /** 上拉加载触发距离(距离底部 px) */
208
+ loadThreshold?: number;
209
+ /** 下拉中的文案 */
210
+ pullingText?: string;
211
+ /** 释放刷新的文案 */
212
+ loosingText?: string;
213
+ /** 刷新中的文案 */
214
+ refreshingText?: string;
215
+ /** 加载中的文案 */
216
+ loadingText?: string;
217
+ /** 加载完成的文案 */
218
+ finishedText?: string;
219
+ /** 加载失败的文案 */
220
+ errorText?: string;
221
+ /** 是否显示错误状态 */
222
+ error?: boolean;
223
+ /** 内容区域是否显示加载状态 */
224
+ contentLoading?: boolean;
225
+ }
226
+
227
+ declare interface Props_2 {
228
+ text?: string;
229
+ showText?: boolean;
230
+ /** 顶部间距,可传数字(px)或带单位字符串,默认 168px */
231
+ paddingTop?: number | string;
232
+ /** 自定义图片 URL */
233
+ emptyImage?: string;
234
+ }
235
+
236
+ export declare const RequestLoading: DefineComponent<ExtractPropTypes<__VLS_WithDefaults_2<__VLS_TypePropsToRuntimeProps_2<{
237
+ text?: string;
238
+ fullscreen?: boolean;
239
+ }>, {
240
+ text: string;
241
+ fullscreen: boolean;
242
+ }>>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes<__VLS_WithDefaults_2<__VLS_TypePropsToRuntimeProps_2<{
243
+ text?: string;
244
+ fullscreen?: boolean;
245
+ }>, {
246
+ text: string;
247
+ fullscreen: boolean;
248
+ }>>> & Readonly<{}>, {
249
+ text: string;
250
+ fullscreen: boolean;
251
+ }, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
252
+
253
+ export { }
@@ -0,0 +1,428 @@
1
+ import { defineComponent as N, openBlock as f, createElementBlock as m, normalizeClass as P, createElementVNode as z, toDisplayString as $, computed as re, normalizeStyle as U, renderSlot as j, createTextVNode as ge, createCommentVNode as V, ref as d, watch as Y, onMounted as ce, nextTick as I, onBeforeUnmount as pe, withDirectives as xe, vShow as ye, createVNode as Te, createBlock as Z, withCtx as we } from "vue";
2
+ const Ee = { class: "loading-content" }, Re = { class: "loading-text" }, Ce = /* @__PURE__ */ N({
3
+ name: "RequestLoading",
4
+ __name: "request-loading",
5
+ props: {
6
+ text: { default: "加载中..." },
7
+ fullscreen: { type: Boolean, default: !1 }
8
+ },
9
+ setup(t) {
10
+ return (g, p) => (f(), m("div", {
11
+ class: P(["loading-container", { fullscreen: t.fullscreen }])
12
+ }, [
13
+ z("div", Ee, [
14
+ p[0] || (p[0] = z("span", { class: "loading-spinner" }, null, -1)),
15
+ z("span", Re, $(t.text), 1)
16
+ ])
17
+ ], 2));
18
+ }
19
+ }), G = (t, g) => {
20
+ const p = t.__vccOpts || t;
21
+ for (const [n, x] of g)
22
+ p[n] = x;
23
+ return p;
24
+ }, K = /* @__PURE__ */ G(Ce, [["__scopeId", "data-v-d89150ba"]]), Le = ["src"], ke = {
25
+ key: 0,
26
+ class: "empty-text"
27
+ }, He = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='80' viewBox='0 0 96 80' fill='none'%3E%3Cpath d='M8 16C8 12.6863 10.6863 10 14 10H38L44 16H82C85.3137 16 88 18.6863 88 22V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V16Z' fill='%23E8E8E8'/%3E%3Cpath d='M8 20C8 18.3431 9.34315 17 11 17H85C86.6569 17 88 18.3431 88 20V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V20Z' fill='%23F5F5F5'/%3E%3Cpath d='M38 42C38 40.8954 38.8954 40 40 40H56C57.1046 40 58 40.8954 58 42V44C58 45.1046 57.1046 46 56 46H40C38.8954 46 38 45.1046 38 44V42Z' fill='%23D9D9D9'/%3E%3C/svg%3E", Se = /* @__PURE__ */ N({
28
+ name: "EmptyComponent",
29
+ __name: "empty-component",
30
+ props: {
31
+ text: { default: "暂无数据" },
32
+ showText: { type: Boolean, default: !0 },
33
+ paddingTop: { default: 168 },
34
+ emptyImage: { default: "" }
35
+ },
36
+ setup(t) {
37
+ const g = t, p = re(() => ({
38
+ paddingTop: typeof g.paddingTop == "number" ? `${g.paddingTop}px` : g.paddingTop
39
+ })), n = re(() => g.emptyImage || He);
40
+ return (x, a) => (f(), m("div", {
41
+ class: "empty-wrap",
42
+ style: U(p.value)
43
+ }, [
44
+ z("img", {
45
+ class: "empty-image",
46
+ src: n.value,
47
+ alt: ""
48
+ }, null, 8, Le),
49
+ t.showText ? (f(), m("p", ke, [
50
+ j(x.$slots, "default", {}, () => [
51
+ ge($(t.text), 1)
52
+ ], !0)
53
+ ])) : V("", !0)
54
+ ], 4));
55
+ }
56
+ }), fe = /* @__PURE__ */ G(Se, [["__scopeId", "data-v-3c452901"]]), Be = { class: "pull-refresh-content" }, be = { key: 0 }, Me = {
57
+ key: 1,
58
+ class: "pull-text"
59
+ }, De = {
60
+ key: 2,
61
+ class: "pull-text"
62
+ }, Ve = {
63
+ key: 0,
64
+ class: "content-loading-mask"
65
+ }, Ie = {
66
+ key: 1,
67
+ class: "finished-text"
68
+ }, ue = 8, $e = /* @__PURE__ */ N({
69
+ name: "InfiniteScroll",
70
+ __name: "infinite-scroll",
71
+ props: {
72
+ loading: { type: Boolean, default: !1 },
73
+ finished: { type: Boolean, default: !1 },
74
+ isEmpty: { type: Boolean, default: !1 },
75
+ emptyText: { default: "暂无数据" },
76
+ pullRefresh: { type: Boolean, default: !1 },
77
+ pullThreshold: { default: 70 },
78
+ loadThreshold: { default: 50 },
79
+ pullingText: { default: "下拉刷新" },
80
+ loosingText: { default: "释放即可刷新" },
81
+ refreshingText: { default: "加载中..." },
82
+ loadingText: { default: "加载中..." },
83
+ finishedText: { default: "没有更多了~" },
84
+ errorText: { default: "加载失败,点击重试" },
85
+ error: { type: Boolean, default: !1 },
86
+ contentLoading: { type: Boolean, default: !1 }
87
+ },
88
+ emits: ["refresh", "load", "retry"],
89
+ setup(t, { expose: g, emit: p }) {
90
+ const n = t, x = p, a = d(null), o = d(null), C = d(null), i = d("idle"), h = d(0), y = d(!1);
91
+ let E = 0, L = 0, T = 0, w = "none", v = !1, S = !1, B = !1, k = null, r = null, l = null, u = null, c = -1;
92
+ function M(e, s) {
93
+ !n.pullRefresh || v || (E = e, L = s, T = 0, w = "none", i.value = "idle");
94
+ }
95
+ function J(e, s, R) {
96
+ if (!n.pullRefresh || v || L === 0) return !1;
97
+ const D = e - E, H = s - L;
98
+ if (w === "none") {
99
+ const b = Math.abs(D), X = Math.abs(H);
100
+ if (b > ue || X > ue)
101
+ w = X > b * 1.5 ? "vertical" : "horizontal";
102
+ else
103
+ return !1;
104
+ }
105
+ if (w === "horizontal") return !1;
106
+ const q = ve() <= 1;
107
+ if (!q && H < 0) {
108
+ const b = a.value;
109
+ return !!(b && b.scrollHeight - b.scrollTop - b.clientHeight <= 1 && R);
110
+ }
111
+ return q ? (T = Math.max(0, Math.min(H, 150)), y.value = !0, r === null && (r = requestAnimationFrame(() => {
112
+ r = null, h.value = T * 0.5, T === 0 ? i.value = "idle" : i.value = T > n.pullThreshold ? "loosing" : "pulling";
113
+ })), H > 0) : !1;
114
+ }
115
+ function Q() {
116
+ !n.pullRefresh || v || (r !== null && (cancelAnimationFrame(r), r = null), y.value = !1, T > n.pullThreshold ? (v = !0, i.value = "refreshing", h.value = 50, x("refresh")) : (h.value = 0, i.value = "idle"), oe());
117
+ }
118
+ function he() {
119
+ !n.pullRefresh || v || (r !== null && (cancelAnimationFrame(r), r = null), y.value = !1, h.value = 0, i.value = "idle", oe());
120
+ }
121
+ function W(e) {
122
+ M(e.touches[0].clientX, e.touches[0].clientY);
123
+ }
124
+ function _(e) {
125
+ const s = e.touches[0];
126
+ J(s.clientX, s.clientY, e.cancelable) && e.cancelable && e.preventDefault();
127
+ }
128
+ function ee() {
129
+ Q();
130
+ }
131
+ function te() {
132
+ he();
133
+ }
134
+ function le(e) {
135
+ !n.pullRefresh || v || e.button !== 0 || (B = !0, M(e.clientX, e.clientY));
136
+ }
137
+ function ne(e) {
138
+ B && J(e.clientX, e.clientY, !0) && e.preventDefault();
139
+ }
140
+ function ae() {
141
+ B && (B = !1, Q());
142
+ }
143
+ function oe() {
144
+ E = 0, L = 0, T = 0, w = "none";
145
+ }
146
+ function O() {
147
+ n.loading || n.finished || n.error || S || k || (k = window.setTimeout(() => {
148
+ var q;
149
+ k = null;
150
+ const e = a.value;
151
+ if (!e) return;
152
+ const { scrollTop: s, clientHeight: R, scrollHeight: D } = e, H = ((q = o.value) == null ? void 0 : q.clientHeight) || 0;
153
+ if (H <= R) {
154
+ if (c === H) return;
155
+ c = H, ie(!0);
156
+ return;
157
+ }
158
+ D - s - R <= n.loadThreshold && ie(!1);
159
+ }, 100));
160
+ }
161
+ function ie(e = !1) {
162
+ S = !0, x("load"), I(() => {
163
+ var R;
164
+ const s = a.value;
165
+ s && (e ? s.scrollTop = s.scrollHeight : (R = C.value) == null || R.scrollIntoView({ block: "end", behavior: "smooth" }));
166
+ });
167
+ }
168
+ function ve() {
169
+ var e;
170
+ return ((e = a.value) == null ? void 0 : e.scrollTop) ?? 0;
171
+ }
172
+ function A() {
173
+ v = !1, h.value = 0, l !== null && clearTimeout(l), l = window.setTimeout(() => {
174
+ i.value = "idle", l = null;
175
+ }, 250), c = -1, I(() => {
176
+ F();
177
+ });
178
+ }
179
+ function se() {
180
+ S = !1, I(() => {
181
+ var s;
182
+ const e = ((s = o.value) == null ? void 0 : s.clientHeight) || 0;
183
+ c !== -1 && e === c || (c = -1, F());
184
+ });
185
+ }
186
+ function F() {
187
+ n.finished || n.loading || n.error || I(() => {
188
+ var R, D;
189
+ const e = ((R = o.value) == null ? void 0 : R.clientHeight) || 0, s = ((D = a.value) == null ? void 0 : D.clientHeight) || 0;
190
+ e <= s && !S && O();
191
+ });
192
+ }
193
+ function me() {
194
+ x("retry");
195
+ }
196
+ return Y(
197
+ () => n.loading,
198
+ (e) => {
199
+ e || (S = !1);
200
+ }
201
+ ), Y(
202
+ () => n.finished,
203
+ (e) => {
204
+ e && (c = -1);
205
+ }
206
+ ), ce(() => {
207
+ const e = a.value;
208
+ e && (n.pullRefresh && (e.addEventListener("touchstart", W, { passive: !0 }), e.addEventListener("touchmove", _, { passive: !1 }), e.addEventListener("touchend", ee, { passive: !0 }), e.addEventListener("touchcancel", te, { passive: !0 }), e.addEventListener("mousedown", le), window.addEventListener("mousemove", ne), window.addEventListener("mouseup", ae)), e.addEventListener("scroll", O, { passive: !0 }), typeof ResizeObserver < "u" && (u = new ResizeObserver(() => {
209
+ F();
210
+ }), u.observe(e), o.value && u.observe(o.value)), I(() => {
211
+ F();
212
+ }));
213
+ }), pe(() => {
214
+ const e = a.value;
215
+ e && (e.removeEventListener("touchstart", W), e.removeEventListener("touchmove", _), e.removeEventListener("touchend", ee), e.removeEventListener("touchcancel", te), e.removeEventListener("mousedown", le), e.removeEventListener("scroll", O)), window.removeEventListener("mousemove", ne), window.removeEventListener("mouseup", ae), u && (u.disconnect(), u = null), r !== null && (cancelAnimationFrame(r), r = null), k !== null && (clearTimeout(k), k = null), l !== null && (clearTimeout(l), l = null);
216
+ }), g({
217
+ refresh: A,
218
+ finishRefresh: A,
219
+ finishPullDown: A,
220
+ finishLoad: se,
221
+ finishPullUp: se,
222
+ checkContentHeight: F
223
+ }), (e, s) => (f(), m("div", {
224
+ ref_key: "containerRef",
225
+ ref: a,
226
+ class: "infinite-scroll"
227
+ }, [
228
+ t.pullRefresh ? (f(), m("div", {
229
+ key: 0,
230
+ class: P(["pull-refresh-indicator", { "no-transition": y.value }]),
231
+ style: U({ transform: `translateY(${h.value}px)` })
232
+ }, [
233
+ xe(z("div", Be, [
234
+ i.value === "refreshing" ? (f(), m("span", be)) : i.value === "loosing" ? (f(), m("span", Me, $(t.loosingText), 1)) : (f(), m("span", De, $(t.pullingText), 1))
235
+ ], 512), [
236
+ [ye, i.value !== "idle"]
237
+ ])
238
+ ], 6)) : V("", !0),
239
+ z("div", {
240
+ ref_key: "contentRef",
241
+ ref: o,
242
+ class: P(["infinite-scroll-content", { "no-transition": y.value }]),
243
+ style: U(t.pullRefresh ? { transform: `translateY(${h.value}px)` } : {})
244
+ }, [
245
+ t.contentLoading ? (f(), m("div", Ve, [
246
+ Te(K, { text: t.refreshingText }, null, 8, ["text"])
247
+ ])) : V("", !0),
248
+ t.isEmpty && !t.loading ? (f(), Z(fe, {
249
+ key: 1,
250
+ text: t.emptyText
251
+ }, null, 8, ["text"])) : V("", !0),
252
+ j(e.$slots, "default", {}, void 0, !0),
253
+ t.isEmpty ? V("", !0) : (f(), m("div", {
254
+ key: 2,
255
+ ref_key: "indicatorRef",
256
+ ref: C,
257
+ class: "load-more-indicator"
258
+ }, [
259
+ t.loading && i.value !== "refreshing" ? (f(), Z(K, {
260
+ key: 0,
261
+ text: t.loadingText
262
+ }, null, 8, ["text"])) : t.finished ? (f(), m("span", Ie, $(t.finishedText), 1)) : t.error ? (f(), m("span", {
263
+ key: 2,
264
+ class: "error-text",
265
+ onClick: me
266
+ }, $(t.errorText), 1)) : V("", !0)
267
+ ], 512))
268
+ ], 6)
269
+ ], 512));
270
+ }
271
+ }), de = /* @__PURE__ */ G($e, [["__scopeId", "data-v-bf5ca1d9"]]), ze = /* @__PURE__ */ N({
272
+ name: "InfiniteScrollV2",
273
+ __name: "infinite-scroll-v2",
274
+ props: {
275
+ fetchFn: {},
276
+ params: { default: () => ({}) },
277
+ pageSize: { default: 10 },
278
+ autoReload: { type: Boolean, default: !0 },
279
+ immediate: { type: Boolean, default: !0 },
280
+ pullRefresh: { type: Boolean, default: !1 },
281
+ pullThreshold: { default: 70 },
282
+ loadThreshold: { default: 50 },
283
+ emptyText: { default: "暂无数据" },
284
+ pullingText: { default: "下拉刷新" },
285
+ loosingText: { default: "释放即可刷新" },
286
+ refreshingText: { default: "加载中..." },
287
+ loadingText: { default: "加载中..." },
288
+ finishedText: { default: "没有更多了~" },
289
+ errorText: { default: "加载失败,点击重试" }
290
+ },
291
+ emits: ["success", "error", "update:list"],
292
+ setup(t, { expose: g, emit: p }) {
293
+ const n = t, x = p, a = d(null), o = d([]), C = d(!1), i = d(!1), h = d(!1), y = d(!1), E = d(0), L = d(1), T = d(!1);
294
+ let w = 0;
295
+ async function v(l = !1) {
296
+ l && (L.value = 1, i.value = !1, h.value = !1), C.value = !0, y.value = !1, w += 1;
297
+ const u = w;
298
+ try {
299
+ const c = await n.fetchFn({
300
+ page: L.value,
301
+ pageSize: n.pageSize,
302
+ ...n.params
303
+ });
304
+ if (u !== w) return;
305
+ const M = (c == null ? void 0 : c.records) || [];
306
+ l ? (o.value = M, E.value = 0) : o.value = [...o.value, ...M], E.value = Number((c == null ? void 0 : c.total) || 0), i.value = o.value.length >= E.value, h.value = o.value.length === 0, x("success", { records: M, total: E.value, reset: l }), x("update:list", o.value);
307
+ } catch (c) {
308
+ if (u !== w) return;
309
+ y.value = !0, x("error", c);
310
+ } finally {
311
+ u === w && (C.value = !1);
312
+ }
313
+ }
314
+ async function S() {
315
+ var l;
316
+ T.value = !0, (l = a.value) == null || l.finishRefresh(), await new Promise((u) => {
317
+ setTimeout(() => u(), 250);
318
+ }), await v(!0), T.value = !1;
319
+ }
320
+ async function B() {
321
+ var l, u;
322
+ if (i.value) {
323
+ (l = a.value) == null || l.finishLoad();
324
+ return;
325
+ }
326
+ o.value.length > 0 && (L.value += 1), await v(!1), (u = a.value) == null || u.finishLoad();
327
+ }
328
+ function k() {
329
+ y.value = !1, o.value.length === 0 ? v(!0) : B();
330
+ }
331
+ async function r() {
332
+ await v(!0);
333
+ }
334
+ return Y(
335
+ () => n.params,
336
+ () => {
337
+ n.autoReload && r();
338
+ },
339
+ { deep: !0 }
340
+ ), Y(
341
+ o,
342
+ async () => {
343
+ var l;
344
+ await I(), (l = a.value) == null || l.checkContentHeight();
345
+ },
346
+ { deep: !0 }
347
+ ), ce(() => {
348
+ n.immediate && v(!0);
349
+ }), g({
350
+ /** 重新加载第一页 */
351
+ reload: r,
352
+ /** 兼容别名 */
353
+ refresh: r,
354
+ /** 当前列表数据 */
355
+ list: o,
356
+ /** 加载中状态 */
357
+ loading: C,
358
+ /** 是否已加载完 */
359
+ finished: i,
360
+ /** 是否空数据 */
361
+ isEmpty: h,
362
+ /** 总条数 */
363
+ total: E,
364
+ /** 透传 v1 方法 */
365
+ finishLoad: () => {
366
+ var l;
367
+ return (l = a.value) == null ? void 0 : l.finishLoad();
368
+ },
369
+ finishRefresh: () => {
370
+ var l;
371
+ return (l = a.value) == null ? void 0 : l.finishRefresh();
372
+ },
373
+ checkContentHeight: () => {
374
+ var l;
375
+ return (l = a.value) == null ? void 0 : l.checkContentHeight();
376
+ }
377
+ }), (l, u) => (f(), Z(de, {
378
+ ref_key: "scrollRef",
379
+ ref: a,
380
+ loading: C.value,
381
+ finished: i.value,
382
+ "is-empty": h.value,
383
+ error: y.value,
384
+ "pull-refresh": t.pullRefresh,
385
+ "pull-threshold": t.pullThreshold,
386
+ "load-threshold": t.loadThreshold,
387
+ "empty-text": t.emptyText,
388
+ "pulling-text": t.pullingText,
389
+ "loosing-text": t.loosingText,
390
+ "refreshing-text": t.refreshingText,
391
+ "loading-text": t.loadingText,
392
+ "finished-text": t.finishedText,
393
+ "error-text": t.errorText,
394
+ "content-loading": T.value,
395
+ onLoad: B,
396
+ onRefresh: S,
397
+ onRetry: k
398
+ }, {
399
+ default: we(() => [
400
+ j(l.$slots, "default", {
401
+ list: o.value,
402
+ loading: C.value,
403
+ finished: i.value,
404
+ isEmpty: h.value,
405
+ error: y.value,
406
+ total: E.value,
407
+ reload: r
408
+ })
409
+ ]),
410
+ _: 3
411
+ }, 8, ["loading", "finished", "is-empty", "error", "pull-refresh", "pull-threshold", "load-threshold", "empty-text", "pulling-text", "loosing-text", "refreshing-text", "loading-text", "finished-text", "error-text", "content-loading"]));
412
+ }
413
+ });
414
+ function Fe(t) {
415
+ t.component("InfiniteScroll", de), t.component("InfiniteScrollV2", ze), t.component("RequestLoading", K), t.component("EmptyComponent", fe);
416
+ }
417
+ const Ne = {
418
+ install: Fe
419
+ };
420
+ export {
421
+ fe as EmptyComponent,
422
+ de as InfiniteScroll,
423
+ ze as InfiniteScrollV2,
424
+ K as RequestLoading,
425
+ Ne as default,
426
+ Fe as install
427
+ };
428
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/components/request-loading.vue","../src/components/empty-component.vue","../src/components/infinite-scroll.vue","../src/components/infinite-scroll-v2.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\ndefineOptions({ name: 'RequestLoading' });\n\nwithDefaults(defineProps<{ text?: string; fullscreen?: boolean }>(), {\n text: '加载中...',\n fullscreen: false\n});\n</script>\n\n<template>\n <div class=\"loading-container\" :class=\"{ fullscreen: fullscreen }\">\n <div class=\"loading-content\">\n <span class=\"loading-spinner\"></span>\n <span class=\"loading-text\">{{ text }}</span>\n </div>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.loading-content {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 6px 12px;\n border-radius: 16px;\n color: var(--zx-primary-color, #1e5efd);\n font-size: 14px;\n}\n\n.loading-spinner {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 2.5px solid var(--zx-primary-color, #1e5efd);\n border-top-color: transparent;\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.loading-container.fullscreen {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(255, 255, 255, 0.5);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\ndefineOptions({ name: 'EmptyComponent' });\n\ninterface Props {\n text?: string;\n showText?: boolean;\n /** 顶部间距,可传数字(px)或带单位字符串,默认 168px */\n paddingTop?: number | string;\n /** 自定义图片 URL */\n emptyImage?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n text: '暂无数据',\n showText: true,\n paddingTop: 168,\n emptyImage: ''\n});\n\nconst wrapStyle = computed(() => ({\n paddingTop: typeof props.paddingTop === 'number' ? `${props.paddingTop}px` : props.paddingTop\n}));\n\n// 默认空状态 SVG(简洁的空文件夹图标)\nconst defaultEmptySvg = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='80' viewBox='0 0 96 80' fill='none'%3E%3Cpath d='M8 16C8 12.6863 10.6863 10 14 10H38L44 16H82C85.3137 16 88 18.6863 88 22V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V16Z' fill='%23E8E8E8'/%3E%3Cpath d='M8 20C8 18.3431 9.34315 17 11 17H85C86.6569 17 88 18.3431 88 20V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V20Z' fill='%23F5F5F5'/%3E%3Cpath d='M38 42C38 40.8954 38.8954 40 40 40H56C57.1046 40 58 40.8954 58 42V44C58 45.1046 57.1046 46 56 46H40C38.8954 46 38 45.1046 38 44V42Z' fill='%23D9D9D9'/%3E%3C/svg%3E`;\n\nconst imageSrc = computed(() => props.emptyImage || defaultEmptySvg);\n</script>\n\n<template>\n <div class=\"empty-wrap\" :style=\"wrapStyle\">\n <img class=\"empty-image\" :src=\"imageSrc\" alt=\"\" />\n <p v-if=\"showText\" class=\"empty-text\">\n <slot>{{ text }}</slot>\n </p>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.empty-wrap {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.empty-image {\n width: 96px;\n height: 80px;\n}\n\n.empty-text {\n margin: 12px 0 0;\n color: #8c8f97;\n font-size: 12px;\n line-height: 22px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport RequestLoading from './request-loading.vue';\nimport EmptyComponent from './empty-component.vue';\n\ndefineOptions({\n name: 'InfiniteScroll'\n});\n\ninterface Props {\n /** 是否处于加载中状态 */\n loading?: boolean;\n /** 是否已加载完所有数据 */\n finished?: boolean;\n /** 是否显示空状态 */\n isEmpty?: boolean;\n /** 空状态文案 */\n emptyText?: string;\n /** 是否启用下拉刷新 */\n pullRefresh?: boolean;\n /** 下拉刷新触发距离(px) */\n pullThreshold?: number;\n /** 上拉加载触发距离(距离底部 px) */\n loadThreshold?: number;\n /** 下拉中的文案 */\n pullingText?: string;\n /** 释放刷新的文案 */\n loosingText?: string;\n /** 刷新中的文案 */\n refreshingText?: string;\n /** 加载中的文案 */\n loadingText?: string;\n /** 加载完成的文案 */\n finishedText?: string;\n /** 加载失败的文案 */\n errorText?: string;\n /** 是否显示错误状态 */\n error?: boolean;\n /** 内容区域是否显示加载状态 */\n contentLoading?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n loading: false,\n finished: false,\n isEmpty: false,\n emptyText: '暂无数据',\n pullRefresh: false,\n pullThreshold: 70,\n loadThreshold: 50,\n pullingText: '下拉刷新',\n loosingText: '释放即可刷新',\n refreshingText: '加载中...',\n loadingText: '加载中...',\n finishedText: '没有更多了~',\n errorText: '加载失败,点击重试',\n error: false,\n contentLoading: false\n});\n\nconst emit = defineEmits<{\n /** 触发下拉刷新 */\n refresh: [];\n /** 触发上拉加载 */\n load: [];\n /** 点击错误重试 */\n retry: [];\n}>();\n\nconst containerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst indicatorRef = ref<HTMLElement | null>(null);\n\n// 下拉刷新状态\nconst pullStatus = ref<'idle' | 'pulling' | 'loosing' | 'refreshing'>('idle');\nconst pullDistance = ref(0);\n// 拖动期间禁用 transition,避免 transform 变化时被 0.25s 补间拖慢\nconst isDragging = ref(false);\n\n// 触摸相关\nlet startX = 0;\nlet startY = 0;\nlet lastDistance = 0;\n// 手势方向锁:避免横向滑动被误判为下拉\nlet gestureLock: 'none' | 'vertical' | 'horizontal' = 'none';\nconst SLOPE_LOCK_DISTANCE = 8;\n\nlet isRefreshing = false;\nlet isLoading = false;\n// 鼠标拖拽下拉(PC 端)\nlet isMouseDown = false;\n\n// 节流相关\nlet scrollTimer: number | null = null;\nlet touchMoveRaf: number | null = null;\nlet idleTimer: number | null = null;\n// 监听容器尺寸变化\nlet resizeObserver: ResizeObserver | null = null;\n// 防止空页死循环:snapshot 触发 load 时的 contentHeight,下次完成后没增长就不再 auto-load\nlet lastTriggerContentHeight = -1;\n\n// 下拉刷新核心:按下(与指针类型无关,touch/mouse 共用)\nfunction pullStart(clientX: number, clientY: number) {\n if (!props.pullRefresh || isRefreshing) return;\n startX = clientX;\n startY = clientY;\n lastDistance = 0;\n gestureLock = 'none';\n pullStatus.value = 'idle';\n}\n\n// 下拉刷新核心:移动(rAF 节流,始终使用最新位置)\n// 返回 true 表示消费了本次移动(下拉进行中),调用方据此决定是否 preventDefault\nfunction pullMove(clientX: number, clientY: number, cancelable: boolean): boolean {\n if (!props.pullRefresh || isRefreshing || startY === 0) return false;\n\n const dx = clientX - startX;\n const dy = clientY - startY;\n\n // 方向判定:先到达 8px 锁定的方向才认为有意图,垂直要明显大于水平(1.5x)\n if (gestureLock === 'none') {\n const ax = Math.abs(dx);\n const ay = Math.abs(dy);\n if (ax > SLOPE_LOCK_DISTANCE || ay > SLOPE_LOCK_DISTANCE) {\n gestureLock = ay > ax * 1.5 ? 'vertical' : 'horizontal';\n } else {\n return false;\n }\n }\n if (gestureLock === 'horizontal') return false;\n\n // 子像素容差:≤1 视为已到顶,避免 X5 内核 scrollTop 偶发非整数判断失败\n const atTop = getScrollTop() <= 1;\n\n // iOS 15- 兜底:在 atBottom 且向上拖时阻止整页 rubber-band\n // (iOS 16+ overscroll-behavior 已处理,此处兜底兼容)\n if (!atTop && dy < 0) {\n const c = containerRef.value;\n if (c) {\n const atBottom = c.scrollHeight - c.scrollTop - c.clientHeight <= 1;\n if (atBottom && cancelable) {\n return true;\n }\n }\n return false;\n }\n if (!atTop) return false;\n\n // 反向拖动也要更新(让指示器跟着手指回退),夹紧到 [0, 150]\n const clamped = Math.max(0, Math.min(dy, 150));\n\n lastDistance = clamped;\n isDragging.value = true;\n\n if (touchMoveRaf === null) {\n touchMoveRaf = requestAnimationFrame(() => {\n touchMoveRaf = null;\n pullDistance.value = lastDistance * 0.5;\n if (lastDistance === 0) {\n pullStatus.value = 'idle';\n } else {\n pullStatus.value = lastDistance > props.pullThreshold ? 'loosing' : 'pulling';\n }\n });\n }\n\n // 仅在向下拖时消费事件(避免误吞用户向上滚动的意图)\n return dy > 0;\n}\n\n// 下拉刷新核心:松开\nfunction pullEnd() {\n if (!props.pullRefresh || isRefreshing) return;\n\n // 取消待执行的 rAF,避免松手后还有一帧异步更新把 UI 拉回 loosing 卡住\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n\n isDragging.value = false;\n\n // 用最新的 lastDistance 同步判定,不依赖可能尚未生效的 pullStatus\n if (lastDistance > props.pullThreshold) {\n isRefreshing = true;\n pullStatus.value = 'refreshing';\n pullDistance.value = 50;\n emit('refresh');\n } else {\n pullDistance.value = 0;\n pullStatus.value = 'idle';\n }\n\n resetTouch();\n}\n\n// 下拉刷新核心:取消(回到 idle)\nfunction pullCancel() {\n if (!props.pullRefresh || isRefreshing) return;\n\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n\n isDragging.value = false;\n pullDistance.value = 0;\n pullStatus.value = 'idle';\n resetTouch();\n}\n\n// 下拉刷新 - touchstart\nfunction handleTouchStart(e: TouchEvent) {\n pullStart(e.touches[0].clientX, e.touches[0].clientY);\n}\n\n// 下拉刷新 - touchmove\nfunction handleTouchMove(e: TouchEvent) {\n const touch = e.touches[0];\n if (pullMove(touch.clientX, touch.clientY, e.cancelable) && e.cancelable) {\n e.preventDefault();\n }\n}\n\n// 下拉刷新 - touchend\nfunction handleTouchEnd() {\n pullEnd();\n}\n\n// 下拉刷新 - touchcancel(微信 X5 内核会在系统手势介入时派发 cancel 而非 end)\nfunction handleTouchCancel() {\n pullCancel();\n}\n\n// 下拉刷新 - mousedown(PC 端鼠标拖拽)\nfunction handleMouseDown(e: MouseEvent) {\n // 仅左键触发\n if (!props.pullRefresh || isRefreshing || e.button !== 0) return;\n isMouseDown = true;\n pullStart(e.clientX, e.clientY);\n}\n\n// 下拉刷新 - mousemove(挂在 window 上,允许拖出容器)\nfunction handleMouseMove(e: MouseEvent) {\n if (!isMouseDown) return;\n if (pullMove(e.clientX, e.clientY, true)) {\n // 阻止拖拽时选中文本\n e.preventDefault();\n }\n}\n\n// 下拉刷新 - mouseup\nfunction handleMouseUp() {\n if (!isMouseDown) return;\n isMouseDown = false;\n pullEnd();\n}\n\nfunction resetTouch() {\n startX = 0;\n startY = 0;\n lastDistance = 0;\n gestureLock = 'none';\n}\n\n// 上拉加载 - scroll(节流处理)\nfunction handleScroll() {\n if (props.loading || props.finished || props.error || isLoading) return;\n if (scrollTimer) return;\n\n scrollTimer = window.setTimeout(() => {\n scrollTimer = null;\n const container = containerRef.value;\n if (!container) return;\n\n const { scrollTop, clientHeight, scrollHeight } = container;\n const contentHeight = contentRef.value?.clientHeight || 0;\n\n // 情况1:内容不足一屏,自动触发加载\n if (contentHeight <= clientHeight) {\n // 死循环防护:上一次 auto-load 后内容没增长,不再继续触发\n if (lastTriggerContentHeight === contentHeight) return;\n lastTriggerContentHeight = contentHeight;\n // 不足一屏时主动滚到底,让 loading 指示器进入视口\n triggerLoad(true);\n return;\n }\n\n // 情况2:滚动到距离底部阈值范围内\n if (scrollHeight - scrollTop - clientHeight <= props.loadThreshold) {\n // 用户已经手动滚到底,不再强制 scrollTop(避免打断惯性滚动)\n triggerLoad(false);\n }\n }, 100);\n}\n\nfunction triggerLoad(scrollToBottom = false) {\n isLoading = true;\n emit('load');\n nextTick(() => {\n const c = containerRef.value;\n if (!c) return;\n if (scrollToBottom) {\n // 内容不足一屏:直接滚到底(没有\"惯性\"可以打断)\n c.scrollTop = c.scrollHeight;\n } else {\n // 阈值触发:用 scrollIntoView 平滑进入视口,避免突兀打断;\n // 旧 WebKit 不识别 options 会降级为即时滚动,效果等价\n indicatorRef.value?.scrollIntoView({ block: 'end', behavior: 'smooth' });\n }\n });\n}\n\n// 获取滚动距离\nfunction getScrollTop(): number {\n return containerRef.value?.scrollTop ?? 0;\n}\n\n// 完成下拉刷新\nfunction finishRefresh() {\n isRefreshing = false;\n pullDistance.value = 0;\n // 让 transform snap-back 完成后再切 idle,避免 spinner 在动画中途突然消失\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = window.setTimeout(() => {\n pullStatus.value = 'idle';\n idleTimer = null;\n }, 250);\n // 重置死循环计数\n lastTriggerContentHeight = -1;\n nextTick(() => {\n checkContentHeight();\n });\n}\n\n// 完成上拉加载\nfunction finishLoad() {\n isLoading = false;\n nextTick(() => {\n const contentHeight = contentRef.value?.clientHeight || 0;\n // 内容没增长就不再触发 auto-load,避免空页死循环\n if (lastTriggerContentHeight !== -1 && contentHeight === lastTriggerContentHeight) return;\n lastTriggerContentHeight = -1;\n checkContentHeight();\n });\n}\n\n// 检查内容高度,不足一屏时自动触发加载\nfunction checkContentHeight() {\n if (props.finished || props.loading || props.error) return;\n\n nextTick(() => {\n const contentHeight = contentRef.value?.clientHeight || 0;\n const clientHeight = containerRef.value?.clientHeight || 0;\n\n if (contentHeight <= clientHeight && !isLoading) {\n handleScroll();\n }\n });\n}\n\n// 点击错误重试 — 由父组件决定具体行为,避免 retry + load 双触发\nfunction handleRetry() {\n emit('retry');\n}\n\n// loading 完成时同步重置 isLoading\nwatch(\n () => props.loading,\n newVal => {\n if (!newVal) {\n isLoading = false;\n }\n }\n);\n\n// finished 翻为 true 时重置死循环计数\nwatch(\n () => props.finished,\n newVal => {\n if (newVal) {\n lastTriggerContentHeight = -1;\n }\n }\n);\n\nonMounted(() => {\n const container = containerRef.value;\n if (!container) return;\n\n if (props.pullRefresh) {\n container.addEventListener('touchstart', handleTouchStart, { passive: true });\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\n container.addEventListener('touchend', handleTouchEnd, { passive: true });\n container.addEventListener('touchcancel', handleTouchCancel, { passive: true });\n // 鼠标拖拽(PC):move/up 挂在 window 上,允许拖出容器边界\n container.addEventListener('mousedown', handleMouseDown);\n window.addEventListener('mousemove', handleMouseMove);\n window.addEventListener('mouseup', handleMouseUp);\n }\n\n container.addEventListener('scroll', handleScroll, { passive: true });\n\n // 监听容器/内容尺寸变化(旋转、WebView resize、软键盘弹出收起等)\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(() => {\n checkContentHeight();\n });\n resizeObserver.observe(container);\n if (contentRef.value) resizeObserver.observe(contentRef.value);\n }\n\n // 初始检查内容高度\n nextTick(() => {\n checkContentHeight();\n });\n});\n\nonBeforeUnmount(() => {\n const container = containerRef.value;\n if (container) {\n container.removeEventListener('touchstart', handleTouchStart);\n container.removeEventListener('touchmove', handleTouchMove);\n container.removeEventListener('touchend', handleTouchEnd);\n container.removeEventListener('touchcancel', handleTouchCancel);\n container.removeEventListener('mousedown', handleMouseDown);\n container.removeEventListener('scroll', handleScroll);\n }\n window.removeEventListener('mousemove', handleMouseMove);\n window.removeEventListener('mouseup', handleMouseUp);\n if (resizeObserver) {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n if (scrollTimer !== null) {\n clearTimeout(scrollTimer);\n scrollTimer = null;\n }\n if (idleTimer !== null) {\n clearTimeout(idleTimer);\n idleTimer = null;\n }\n});\n\ndefineExpose({\n refresh: finishRefresh,\n finishRefresh,\n finishPullDown: finishRefresh,\n finishLoad,\n finishPullUp: finishLoad,\n checkContentHeight\n});\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"infinite-scroll\">\n <!-- 下拉刷新指示器 -->\n <div\n v-if=\"pullRefresh\"\n class=\"pull-refresh-indicator\"\n :class=\"{ 'no-transition': isDragging }\"\n :style=\"{ transform: `translateY(${pullDistance}px)` }\"\n >\n <div v-show=\"pullStatus !== 'idle'\" class=\"pull-refresh-content\">\n <!-- <RequestLoading v-if=\"pullStatus === 'refreshing'\" text=\"\" /> -->\n <span v-if=\"pullStatus === 'refreshing'\"></span>\n <span v-else-if=\"pullStatus === 'loosing'\" class=\"pull-text\">{{ loosingText }}</span>\n <span v-else class=\"pull-text\">{{ pullingText }}</span>\n </div>\n </div>\n\n <!-- 内容区域 -->\n <div\n ref=\"contentRef\"\n class=\"infinite-scroll-content\"\n :class=\"{ 'no-transition': isDragging }\"\n :style=\"pullRefresh ? { transform: `translateY(${pullDistance}px)` } : {}\"\n >\n <!-- 内容区域加载遮罩 -->\n <div v-if=\"contentLoading\" class=\"content-loading-mask\">\n <RequestLoading :text=\"refreshingText\" />\n </div>\n\n <!-- 空状态 -->\n <EmptyComponent v-if=\"isEmpty && !loading\" :text=\"emptyText\" />\n\n <!-- 默认插槽 -->\n <slot></slot>\n\n <!-- 上拉加载指示器 -->\n <div v-if=\"!isEmpty\" ref=\"indicatorRef\" class=\"load-more-indicator\">\n <RequestLoading v-if=\"loading && pullStatus !== 'refreshing'\" :text=\"loadingText\" />\n <span v-else-if=\"finished\" class=\"finished-text\">{{ finishedText }}</span>\n <span v-else-if=\"error\" class=\"error-text\" @click=\"handleRetry\">{{ errorText }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.infinite-scroll {\n height: 100%;\n position: relative;\n min-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n // 阻止滚动到边界时把滚动链传递给祖先(修复 iOS 16+ / 微信 X5 下拉变成整页拖拽)\n overscroll-behavior: contain;\n // 显式声明只接受纵向手势,避免被外层 touch-action 影响\n touch-action: pan-y;\n // iOS 下让原生回弹有惯性\n -webkit-overflow-scrolling: touch;\n}\n\n.pull-refresh-indicator {\n position: absolute;\n top: -50px;\n left: 0;\n right: 0;\n height: 50px;\n transition: transform 0.25s;\n\n // 拖动期间禁用过渡,让 transform 1:1 跟随手指\n &.no-transition {\n transition: none;\n }\n}\n\n.pull-refresh-content {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 14px;\n color: #969799;\n}\n\n.pull-text {\n color: var(--zx-primary-color, #1989fa);\n font-size: 14px;\n}\n\n.infinite-scroll-content {\n position: relative;\n transition: transform 0.25s;\n min-height: 100%;\n\n &.no-transition {\n transition: none;\n }\n}\n\n.content-loading-mask {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(255, 255, 255, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n backdrop-filter: blur(2px);\n}\n\n.load-more-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n // 预留高度避免 loading 渲染后被推到视口外造成遮挡\n min-height: 50px;\n padding: 16px 0;\n font-size: 14px;\n color: #8c8f97;\n}\n\n.finished-text {\n color: #8c8f97;\n font-size: 14px;\n}\n\n.error-text {\n color: #ee0a24;\n font-size: 14px;\n cursor: pointer;\n\n &:active {\n opacity: 0.7;\n }\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T\">\nimport { type Ref, nextTick, onMounted, ref, watch } from 'vue';\nimport InfiniteScroll from './infinite-scroll.vue';\n\ndefineOptions({\n name: 'InfiniteScrollV2'\n});\n\ninterface FetchParams {\n page: number;\n pageSize: number;\n [key: string]: any;\n}\n\ninterface FetchResult<R> {\n records: R[];\n total: number;\n}\n\ninterface Props {\n /** 数据请求函数,需返回 { records, total },失败时抛错 */\n fetchFn: (params: FetchParams) => Promise<FetchResult<T>>;\n /** 业务查询参数(如筛选条件),变化时自动 reset 重新加载 */\n params?: Record<string, any>;\n /** 每页条数 */\n pageSize?: number;\n /** params 变化时是否自动重新加载 */\n autoReload?: boolean;\n /** 是否在挂载时自动加载首页 */\n immediate?: boolean;\n /** 是否启用下拉刷新 */\n pullRefresh?: boolean;\n /** 下拉刷新触发距离(px) */\n pullThreshold?: number;\n /** 上拉加载触发距离(距离底部 px) */\n loadThreshold?: number;\n /** 空状态文案 */\n emptyText?: string;\n /** 下拉中的文案 */\n pullingText?: string;\n /** 释放刷新的文案 */\n loosingText?: string;\n /** 刷新中的文案 */\n refreshingText?: string;\n /** 加载中的文案 */\n loadingText?: string;\n /** 加载完成的文案 */\n finishedText?: string;\n /** 加载失败的文案 */\n errorText?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n params: () => ({}),\n pageSize: 10,\n autoReload: true,\n immediate: true,\n pullRefresh: false,\n pullThreshold: 70,\n loadThreshold: 50,\n emptyText: '暂无数据',\n pullingText: '下拉刷新',\n loosingText: '释放即可刷新',\n refreshingText: '加载中...',\n loadingText: '加载中...',\n finishedText: '没有更多了~',\n errorText: '加载失败,点击重试'\n});\n\nconst emit = defineEmits<{\n /** 加载成功,每次成功(含分页)都会触发 */\n success: [payload: { records: T[]; total: number; reset: boolean }];\n /** 加载失败 */\n error: [err: unknown];\n /** 列表内容变化(v-model:list 支持) */\n 'update:list': [list: T[]];\n}>();\n\nconst scrollRef = ref<InstanceType<typeof InfiniteScroll> | null>(null);\n\nconst list = ref<T[]>([]) as Ref<T[]>;\nconst loading = ref(false);\nconst finished = ref(false);\nconst isEmpty = ref(false);\nconst loadError = ref(false);\nconst total = ref(0);\nconst page = ref(1);\nconst isRefreshing = ref(false);\n\nlet requestSeq = 0;\n\nasync function load(reset = false) {\n if (reset) {\n page.value = 1;\n finished.value = false;\n isEmpty.value = false;\n // 不立即清空 list,保留旧数据直到新数据到达\n }\n loading.value = true;\n loadError.value = false;\n\n requestSeq += 1;\n const seq = requestSeq;\n\n try {\n const result = await props.fetchFn({\n page: page.value,\n pageSize: props.pageSize,\n ...props.params\n });\n\n if (seq !== requestSeq) return;\n\n const records = result?.records || [];\n if (reset) {\n // reset 时替换整个列表\n list.value = records;\n total.value = 0;\n } else {\n list.value = [...list.value, ...records];\n }\n total.value = Number(result?.total || 0);\n finished.value = list.value.length >= total.value;\n isEmpty.value = list.value.length === 0;\n\n emit('success', { records, total: total.value, reset });\n emit('update:list', list.value);\n } catch (err) {\n if (seq !== requestSeq) return;\n loadError.value = true;\n emit('error', err);\n } finally {\n if (seq === requestSeq) {\n loading.value = false;\n }\n }\n}\n\nasync function handleRefresh() {\n isRefreshing.value = true;\n // 先让下拉刷新指示器收回\n scrollRef.value?.finishRefresh();\n // 等待 250ms 让指示器完全收回(对应 infinite-scroll.vue 的 transition 时长)\n await new Promise<void>(resolve => {\n setTimeout(() => resolve(), 250);\n });\n // 指示器收回后再开始请求新数据\n await load(true);\n isRefreshing.value = false;\n}\n\nasync function handleLoad() {\n if (finished.value) {\n scrollRef.value?.finishLoad();\n return;\n }\n // 列表为空时是首次加载(page 已经是 1),其它情况 page++\n if (list.value.length > 0) {\n page.value += 1;\n }\n await load(false);\n scrollRef.value?.finishLoad();\n}\n\nfunction handleRetry() {\n loadError.value = false;\n if (list.value.length === 0) {\n load(true);\n } else {\n handleLoad();\n }\n}\n\nasync function reload() {\n await load(true);\n}\n\nwatch(\n () => props.params,\n () => {\n if (props.autoReload) {\n reload();\n }\n },\n { deep: true }\n);\n\nwatch(\n list,\n async () => {\n await nextTick();\n scrollRef.value?.checkContentHeight();\n },\n { deep: true }\n);\n\nonMounted(() => {\n if (props.immediate) {\n load(true);\n }\n});\n\ndefineExpose({\n /** 重新加载第一页 */\n reload,\n /** 兼容别名 */\n refresh: reload,\n /** 当前列表数据 */\n list,\n /** 加载中状态 */\n loading,\n /** 是否已加载完 */\n finished,\n /** 是否空数据 */\n isEmpty,\n /** 总条数 */\n total,\n /** 透传 v1 方法 */\n finishLoad: () => scrollRef.value?.finishLoad(),\n finishRefresh: () => scrollRef.value?.finishRefresh(),\n checkContentHeight: () => scrollRef.value?.checkContentHeight()\n});\n</script>\n\n<template>\n <InfiniteScroll\n ref=\"scrollRef\"\n :loading=\"loading\"\n :finished=\"finished\"\n :is-empty=\"isEmpty\"\n :error=\"loadError\"\n :pull-refresh=\"pullRefresh\"\n :pull-threshold=\"pullThreshold\"\n :load-threshold=\"loadThreshold\"\n :empty-text=\"emptyText\"\n :pulling-text=\"pullingText\"\n :loosing-text=\"loosingText\"\n :refreshing-text=\"refreshingText\"\n :loading-text=\"loadingText\"\n :finished-text=\"finishedText\"\n :error-text=\"errorText\"\n :content-loading=\"isRefreshing\"\n @load=\"handleLoad\"\n @refresh=\"handleRefresh\"\n @retry=\"handleRetry\"\n >\n <slot\n :list=\"list\"\n :loading=\"loading\"\n :finished=\"finished\"\n :is-empty=\"isEmpty\"\n :error=\"loadError\"\n :total=\"total\"\n :reload=\"reload\"\n />\n </InfiniteScroll>\n</template>\n","import { App } from 'vue';\nimport InfiniteScroll from './components/infinite-scroll.vue';\nimport InfiniteScrollV2 from './components/infinite-scroll-v2.vue';\nimport RequestLoading from './components/request-loading.vue';\nimport EmptyComponent from './components/empty-component.vue';\n\n// 单独导出组件\nexport { InfiniteScroll, InfiniteScrollV2, RequestLoading, EmptyComponent };\n\n// Vue plugin install 函数\nexport function install(app: App) {\n app.component('InfiniteScroll', InfiniteScroll);\n app.component('InfiniteScrollV2', InfiniteScrollV2);\n app.component('RequestLoading', RequestLoading);\n app.component('EmptyComponent', EmptyComponent);\n}\n\n// 默认导出(支持 app.use() 安装)\nexport default {\n install\n};\n"],"names":["_createElementBlock","_normalizeClass","__props","_createElementVNode","_hoisted_1","_hoisted_2","_toDisplayString","defaultEmptySvg","props","wrapStyle","computed","imageSrc","_openBlock","_renderSlot","_ctx","SLOPE_LOCK_DISTANCE","emit","__emit","containerRef","ref","contentRef","indicatorRef","pullStatus","pullDistance","isDragging","startX","startY","lastDistance","gestureLock","isRefreshing","isLoading","isMouseDown","scrollTimer","touchMoveRaf","idleTimer","resizeObserver","lastTriggerContentHeight","pullStart","clientX","clientY","pullMove","cancelable","dx","dy","ax","ay","atTop","getScrollTop","c","pullEnd","resetTouch","pullCancel","handleTouchStart","handleTouchMove","touch","handleTouchEnd","handleTouchCancel","handleMouseDown","handleMouseMove","handleMouseUp","handleScroll","container","scrollTop","clientHeight","scrollHeight","contentHeight","_a","triggerLoad","scrollToBottom","nextTick","finishRefresh","checkContentHeight","finishLoad","_b","handleRetry","watch","newVal","onMounted","onBeforeUnmount","__expose","_withDirectives","_hoisted_3","_hoisted_4","_normalizeStyle","_hoisted_5","_createVNode","RequestLoading","_createBlock","EmptyComponent","_hoisted_6","scrollRef","list","loading","finished","isEmpty","loadError","total","page","requestSeq","load","reset","seq","result","records","err","handleRefresh","resolve","handleLoad","reload","InfiniteScroll","install","app","InfiniteScrollV2","index"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(m,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(m=typeof globalThis<"u"?globalThis:m||self,e(m.ZhixinInfiniteScroll={},m.Vue))})(this,function(m,e){"use strict";const ne={class:"loading-content"},le={class:"loading-text"},oe=e.defineComponent({name:"RequestLoading",__name:"request-loading",props:{text:{default:"加载中..."},fullscreen:{type:Boolean,default:!1}},setup(n){return(p,g)=>(e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["loading-container",{fullscreen:n.fullscreen}])},[e.createElementVNode("div",ne,[g[0]||(g[0]=e.createElementVNode("span",{class:"loading-spinner"},null,-1)),e.createElementVNode("span",le,e.toDisplayString(n.text),1)])],2))}}),I=(n,p)=>{const g=n.__vccOpts||n;for(const[o,y]of p)g[o]=y;return g},N=I(oe,[["__scopeId","data-v-d89150ba"]]),ae=["src"],ie={key:0,class:"empty-text"},re="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='80' viewBox='0 0 96 80' fill='none'%3E%3Cpath d='M8 16C8 12.6863 10.6863 10 14 10H38L44 16H82C85.3137 16 88 18.6863 88 22V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V16Z' fill='%23E8E8E8'/%3E%3Cpath d='M8 20C8 18.3431 9.34315 17 11 17H85C86.6569 17 88 18.3431 88 20V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V20Z' fill='%23F5F5F5'/%3E%3Cpath d='M38 42C38 40.8954 38.8954 40 40 40H56C57.1046 40 58 40.8954 58 42V44C58 45.1046 57.1046 46 56 46H40C38.8954 46 38 45.1046 38 44V42Z' fill='%23D9D9D9'/%3E%3C/svg%3E",$=I(e.defineComponent({name:"EmptyComponent",__name:"empty-component",props:{text:{default:"暂无数据"},showText:{type:Boolean,default:!0},paddingTop:{default:168},emptyImage:{default:""}},setup(n){const p=n,g=e.computed(()=>({paddingTop:typeof p.paddingTop=="number"?`${p.paddingTop}px`:p.paddingTop})),o=e.computed(()=>p.emptyImage||re);return(y,a)=>(e.openBlock(),e.createElementBlock("div",{class:"empty-wrap",style:e.normalizeStyle(g.value)},[e.createElementVNode("img",{class:"empty-image",src:o.value,alt:""},null,8,ae),n.showText?(e.openBlock(),e.createElementBlock("p",ie,[e.renderSlot(y.$slots,"default",{},()=>[e.createTextVNode(e.toDisplayString(n.text),1)],!0)])):e.createCommentVNode("",!0)],4))}}),[["__scopeId","data-v-3c452901"]]),se={class:"pull-refresh-content"},ce={key:0},fe={key:1,class:"pull-text"},de={key:2,class:"pull-text"},ue={key:0,class:"content-loading-mask"},he={key:1,class:"finished-text"},Y=8,z=I(e.defineComponent({name:"InfiniteScroll",__name:"infinite-scroll",props:{loading:{type:Boolean,default:!1},finished:{type:Boolean,default:!1},isEmpty:{type:Boolean,default:!1},emptyText:{default:"暂无数据"},pullRefresh:{type:Boolean,default:!1},pullThreshold:{default:70},loadThreshold:{default:50},pullingText:{default:"下拉刷新"},loosingText:{default:"释放即可刷新"},refreshingText:{default:"加载中..."},loadingText:{default:"加载中..."},finishedText:{default:"没有更多了~"},errorText:{default:"加载失败,点击重试"},error:{type:Boolean,default:!1},contentLoading:{type:Boolean,default:!1}},emits:["refresh","load","retry"],setup(n,{expose:p,emit:g}){const o=n,y=g,a=e.ref(null),i=e.ref(null),B=e.ref(null),r=e.ref("idle"),u=e.ref(0),x=e.ref(!1);let k=0,C=0,T=0,v="none",h=!1,S=!1,L=!1,w=null,c=null,l=null,f=null,d=-1;function V(t,s){!o.pullRefresh||h||(k=t,C=s,T=0,v="none",r.value="idle")}function X(t,s,E){if(!o.pullRefresh||h||C===0)return!1;const D=t-k,R=s-C;if(v==="none"){const H=Math.abs(D),O=Math.abs(R);if(H>Y||O>Y)v=O>H*1.5?"vertical":"horizontal";else return!1}if(v==="horizontal")return!1;const b=ge()<=1;if(!b&&R<0){const H=a.value;return!!(H&&H.scrollHeight-H.scrollTop-H.clientHeight<=1&&E)}return b?(T=Math.max(0,Math.min(R,150)),x.value=!0,c===null&&(c=requestAnimationFrame(()=>{c=null,u.value=T*.5,T===0?r.value="idle":r.value=T>o.pullThreshold?"loosing":"pulling"})),R>0):!1}function Z(){!o.pullRefresh||h||(c!==null&&(cancelAnimationFrame(c),c=null),x.value=!1,T>o.pullThreshold?(h=!0,r.value="refreshing",u.value=50,y("refresh")):(u.value=0,r.value="idle"),W())}function pe(){!o.pullRefresh||h||(c!==null&&(cancelAnimationFrame(c),c=null),x.value=!1,u.value=0,r.value="idle",W())}function _(t){V(t.touches[0].clientX,t.touches[0].clientY)}function U(t){const s=t.touches[0];X(s.clientX,s.clientY,t.cancelable)&&t.cancelable&&t.preventDefault()}function j(){Z()}function K(){pe()}function G(t){!o.pullRefresh||h||t.button!==0||(L=!0,V(t.clientX,t.clientY))}function J(t){L&&X(t.clientX,t.clientY,!0)&&t.preventDefault()}function Q(){L&&(L=!1,Z())}function W(){k=0,C=0,T=0,v="none"}function q(){o.loading||o.finished||o.error||S||w||(w=window.setTimeout(()=>{var b;w=null;const t=a.value;if(!t)return;const{scrollTop:s,clientHeight:E,scrollHeight:D}=t,R=((b=i.value)==null?void 0:b.clientHeight)||0;if(R<=E){if(d===R)return;d=R,ee(!0);return}D-s-E<=o.loadThreshold&&ee(!1)},100))}function ee(t=!1){S=!0,y("load"),e.nextTick(()=>{var E;const s=a.value;s&&(t?s.scrollTop=s.scrollHeight:(E=B.value)==null||E.scrollIntoView({block:"end",behavior:"smooth"}))})}function ge(){var t;return((t=a.value)==null?void 0:t.scrollTop)??0}function F(){h=!1,u.value=0,l!==null&&clearTimeout(l),l=window.setTimeout(()=>{r.value="idle",l=null},250),d=-1,e.nextTick(()=>{M()})}function te(){S=!1,e.nextTick(()=>{var s;const t=((s=i.value)==null?void 0:s.clientHeight)||0;d!==-1&&t===d||(d=-1,M())})}function M(){o.finished||o.loading||o.error||e.nextTick(()=>{var E,D;const t=((E=i.value)==null?void 0:E.clientHeight)||0,s=((D=a.value)==null?void 0:D.clientHeight)||0;t<=s&&!S&&q()})}function ye(){y("retry")}return e.watch(()=>o.loading,t=>{t||(S=!1)}),e.watch(()=>o.finished,t=>{t&&(d=-1)}),e.onMounted(()=>{const t=a.value;t&&(o.pullRefresh&&(t.addEventListener("touchstart",_,{passive:!0}),t.addEventListener("touchmove",U,{passive:!1}),t.addEventListener("touchend",j,{passive:!0}),t.addEventListener("touchcancel",K,{passive:!0}),t.addEventListener("mousedown",G),window.addEventListener("mousemove",J),window.addEventListener("mouseup",Q)),t.addEventListener("scroll",q,{passive:!0}),typeof ResizeObserver<"u"&&(f=new ResizeObserver(()=>{M()}),f.observe(t),i.value&&f.observe(i.value)),e.nextTick(()=>{M()}))}),e.onBeforeUnmount(()=>{const t=a.value;t&&(t.removeEventListener("touchstart",_),t.removeEventListener("touchmove",U),t.removeEventListener("touchend",j),t.removeEventListener("touchcancel",K),t.removeEventListener("mousedown",G),t.removeEventListener("scroll",q)),window.removeEventListener("mousemove",J),window.removeEventListener("mouseup",Q),f&&(f.disconnect(),f=null),c!==null&&(cancelAnimationFrame(c),c=null),w!==null&&(clearTimeout(w),w=null),l!==null&&(clearTimeout(l),l=null)}),p({refresh:F,finishRefresh:F,finishPullDown:F,finishLoad:te,finishPullUp:te,checkContentHeight:M}),(t,s)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"containerRef",ref:a,class:"infinite-scroll"},[n.pullRefresh?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["pull-refresh-indicator",{"no-transition":x.value}]),style:e.normalizeStyle({transform:`translateY(${u.value}px)`})},[e.withDirectives(e.createElementVNode("div",se,[r.value==="refreshing"?(e.openBlock(),e.createElementBlock("span",ce)):r.value==="loosing"?(e.openBlock(),e.createElementBlock("span",fe,e.toDisplayString(n.loosingText),1)):(e.openBlock(),e.createElementBlock("span",de,e.toDisplayString(n.pullingText),1))],512),[[e.vShow,r.value!=="idle"]])],6)):e.createCommentVNode("",!0),e.createElementVNode("div",{ref_key:"contentRef",ref:i,class:e.normalizeClass(["infinite-scroll-content",{"no-transition":x.value}]),style:e.normalizeStyle(n.pullRefresh?{transform:`translateY(${u.value}px)`}:{})},[n.contentLoading?(e.openBlock(),e.createElementBlock("div",ue,[e.createVNode(N,{text:n.refreshingText},null,8,["text"])])):e.createCommentVNode("",!0),n.isEmpty&&!n.loading?(e.openBlock(),e.createBlock($,{key:1,text:n.emptyText},null,8,["text"])):e.createCommentVNode("",!0),e.renderSlot(t.$slots,"default",{},void 0,!0),n.isEmpty?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("div",{key:2,ref_key:"indicatorRef",ref:B,class:"load-more-indicator"},[n.loading&&r.value!=="refreshing"?(e.openBlock(),e.createBlock(N,{key:0,text:n.loadingText},null,8,["text"])):n.finished?(e.openBlock(),e.createElementBlock("span",he,e.toDisplayString(n.finishedText),1)):n.error?(e.openBlock(),e.createElementBlock("span",{key:2,class:"error-text",onClick:ye},e.toDisplayString(n.errorText),1)):e.createCommentVNode("",!0)],512))],6)],512))}}),[["__scopeId","data-v-bf5ca1d9"]]),A=e.defineComponent({name:"InfiniteScrollV2",__name:"infinite-scroll-v2",props:{fetchFn:{},params:{default:()=>({})},pageSize:{default:10},autoReload:{type:Boolean,default:!0},immediate:{type:Boolean,default:!0},pullRefresh:{type:Boolean,default:!1},pullThreshold:{default:70},loadThreshold:{default:50},emptyText:{default:"暂无数据"},pullingText:{default:"下拉刷新"},loosingText:{default:"释放即可刷新"},refreshingText:{default:"加载中..."},loadingText:{default:"加载中..."},finishedText:{default:"没有更多了~"},errorText:{default:"加载失败,点击重试"}},emits:["success","error","update:list"],setup(n,{expose:p,emit:g}){const o=n,y=g,a=e.ref(null),i=e.ref([]),B=e.ref(!1),r=e.ref(!1),u=e.ref(!1),x=e.ref(!1),k=e.ref(0),C=e.ref(1),T=e.ref(!1);let v=0;async function h(l=!1){l&&(C.value=1,r.value=!1,u.value=!1),B.value=!0,x.value=!1,v+=1;const f=v;try{const d=await o.fetchFn({page:C.value,pageSize:o.pageSize,...o.params});if(f!==v)return;const V=(d==null?void 0:d.records)||[];l?(i.value=V,k.value=0):i.value=[...i.value,...V],k.value=Number((d==null?void 0:d.total)||0),r.value=i.value.length>=k.value,u.value=i.value.length===0,y("success",{records:V,total:k.value,reset:l}),y("update:list",i.value)}catch(d){if(f!==v)return;x.value=!0,y("error",d)}finally{f===v&&(B.value=!1)}}async function S(){var l;T.value=!0,(l=a.value)==null||l.finishRefresh(),await new Promise(f=>{setTimeout(()=>f(),250)}),await h(!0),T.value=!1}async function L(){var l,f;if(r.value){(l=a.value)==null||l.finishLoad();return}i.value.length>0&&(C.value+=1),await h(!1),(f=a.value)==null||f.finishLoad()}function w(){x.value=!1,i.value.length===0?h(!0):L()}async function c(){await h(!0)}return e.watch(()=>o.params,()=>{o.autoReload&&c()},{deep:!0}),e.watch(i,async()=>{var l;await e.nextTick(),(l=a.value)==null||l.checkContentHeight()},{deep:!0}),e.onMounted(()=>{o.immediate&&h(!0)}),p({reload:c,refresh:c,list:i,loading:B,finished:r,isEmpty:u,total:k,finishLoad:()=>{var l;return(l=a.value)==null?void 0:l.finishLoad()},finishRefresh:()=>{var l;return(l=a.value)==null?void 0:l.finishRefresh()},checkContentHeight:()=>{var l;return(l=a.value)==null?void 0:l.checkContentHeight()}}),(l,f)=>(e.openBlock(),e.createBlock(z,{ref_key:"scrollRef",ref:a,loading:B.value,finished:r.value,"is-empty":u.value,error:x.value,"pull-refresh":n.pullRefresh,"pull-threshold":n.pullThreshold,"load-threshold":n.loadThreshold,"empty-text":n.emptyText,"pulling-text":n.pullingText,"loosing-text":n.loosingText,"refreshing-text":n.refreshingText,"loading-text":n.loadingText,"finished-text":n.finishedText,"error-text":n.errorText,"content-loading":T.value,onLoad:L,onRefresh:S,onRetry:w},{default:e.withCtx(()=>[e.renderSlot(l.$slots,"default",{list:i.value,loading:B.value,finished:r.value,isEmpty:u.value,error:x.value,total:k.value,reload:c})]),_:3},8,["loading","finished","is-empty","error","pull-refresh","pull-threshold","load-threshold","empty-text","pulling-text","loosing-text","refreshing-text","loading-text","finished-text","error-text","content-loading"]))}});function P(n){n.component("InfiniteScroll",z),n.component("InfiniteScrollV2",A),n.component("RequestLoading",N),n.component("EmptyComponent",$)}const me={install:P};m.EmptyComponent=$,m.InfiniteScroll=z,m.InfiniteScrollV2=A,m.RequestLoading=N,m.default=me,m.install=P,Object.defineProperties(m,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/components/request-loading.vue","../src/components/empty-component.vue","../src/components/infinite-scroll.vue","../src/components/infinite-scroll-v2.vue","../src/index.ts"],"sourcesContent":["<script setup lang=\"ts\">\ndefineOptions({ name: 'RequestLoading' });\n\nwithDefaults(defineProps<{ text?: string; fullscreen?: boolean }>(), {\n text: '加载中...',\n fullscreen: false\n});\n</script>\n\n<template>\n <div class=\"loading-container\" :class=\"{ fullscreen: fullscreen }\">\n <div class=\"loading-content\">\n <span class=\"loading-spinner\"></span>\n <span class=\"loading-text\">{{ text }}</span>\n </div>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.loading-content {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 6px 12px;\n border-radius: 16px;\n color: var(--zx-primary-color, #1e5efd);\n font-size: 14px;\n}\n\n.loading-spinner {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 2.5px solid var(--zx-primary-color, #1e5efd);\n border-top-color: transparent;\n animation: spin 0.8s linear infinite;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.loading-container.fullscreen {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: rgba(255, 255, 255, 0.5);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue';\n\ndefineOptions({ name: 'EmptyComponent' });\n\ninterface Props {\n text?: string;\n showText?: boolean;\n /** 顶部间距,可传数字(px)或带单位字符串,默认 168px */\n paddingTop?: number | string;\n /** 自定义图片 URL */\n emptyImage?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n text: '暂无数据',\n showText: true,\n paddingTop: 168,\n emptyImage: ''\n});\n\nconst wrapStyle = computed(() => ({\n paddingTop: typeof props.paddingTop === 'number' ? `${props.paddingTop}px` : props.paddingTop\n}));\n\n// 默认空状态 SVG(简洁的空文件夹图标)\nconst defaultEmptySvg = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='80' viewBox='0 0 96 80' fill='none'%3E%3Cpath d='M8 16C8 12.6863 10.6863 10 14 10H38L44 16H82C85.3137 16 88 18.6863 88 22V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V16Z' fill='%23E8E8E8'/%3E%3Cpath d='M8 20C8 18.3431 9.34315 17 11 17H85C86.6569 17 88 18.3431 88 20V66C88 69.3137 85.3137 72 82 72H14C10.6863 72 8 69.3137 8 66V20Z' fill='%23F5F5F5'/%3E%3Cpath d='M38 42C38 40.8954 38.8954 40 40 40H56C57.1046 40 58 40.8954 58 42V44C58 45.1046 57.1046 46 56 46H40C38.8954 46 38 45.1046 38 44V42Z' fill='%23D9D9D9'/%3E%3C/svg%3E`;\n\nconst imageSrc = computed(() => props.emptyImage || defaultEmptySvg);\n</script>\n\n<template>\n <div class=\"empty-wrap\" :style=\"wrapStyle\">\n <img class=\"empty-image\" :src=\"imageSrc\" alt=\"\" />\n <p v-if=\"showText\" class=\"empty-text\">\n <slot>{{ text }}</slot>\n </p>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.empty-wrap {\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.empty-image {\n width: 96px;\n height: 80px;\n}\n\n.empty-text {\n margin: 12px 0 0;\n color: #8c8f97;\n font-size: 12px;\n line-height: 22px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport RequestLoading from './request-loading.vue';\nimport EmptyComponent from './empty-component.vue';\n\ndefineOptions({\n name: 'InfiniteScroll'\n});\n\ninterface Props {\n /** 是否处于加载中状态 */\n loading?: boolean;\n /** 是否已加载完所有数据 */\n finished?: boolean;\n /** 是否显示空状态 */\n isEmpty?: boolean;\n /** 空状态文案 */\n emptyText?: string;\n /** 是否启用下拉刷新 */\n pullRefresh?: boolean;\n /** 下拉刷新触发距离(px) */\n pullThreshold?: number;\n /** 上拉加载触发距离(距离底部 px) */\n loadThreshold?: number;\n /** 下拉中的文案 */\n pullingText?: string;\n /** 释放刷新的文案 */\n loosingText?: string;\n /** 刷新中的文案 */\n refreshingText?: string;\n /** 加载中的文案 */\n loadingText?: string;\n /** 加载完成的文案 */\n finishedText?: string;\n /** 加载失败的文案 */\n errorText?: string;\n /** 是否显示错误状态 */\n error?: boolean;\n /** 内容区域是否显示加载状态 */\n contentLoading?: boolean;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n loading: false,\n finished: false,\n isEmpty: false,\n emptyText: '暂无数据',\n pullRefresh: false,\n pullThreshold: 70,\n loadThreshold: 50,\n pullingText: '下拉刷新',\n loosingText: '释放即可刷新',\n refreshingText: '加载中...',\n loadingText: '加载中...',\n finishedText: '没有更多了~',\n errorText: '加载失败,点击重试',\n error: false,\n contentLoading: false\n});\n\nconst emit = defineEmits<{\n /** 触发下拉刷新 */\n refresh: [];\n /** 触发上拉加载 */\n load: [];\n /** 点击错误重试 */\n retry: [];\n}>();\n\nconst containerRef = ref<HTMLElement | null>(null);\nconst contentRef = ref<HTMLElement | null>(null);\nconst indicatorRef = ref<HTMLElement | null>(null);\n\n// 下拉刷新状态\nconst pullStatus = ref<'idle' | 'pulling' | 'loosing' | 'refreshing'>('idle');\nconst pullDistance = ref(0);\n// 拖动期间禁用 transition,避免 transform 变化时被 0.25s 补间拖慢\nconst isDragging = ref(false);\n\n// 触摸相关\nlet startX = 0;\nlet startY = 0;\nlet lastDistance = 0;\n// 手势方向锁:避免横向滑动被误判为下拉\nlet gestureLock: 'none' | 'vertical' | 'horizontal' = 'none';\nconst SLOPE_LOCK_DISTANCE = 8;\n\nlet isRefreshing = false;\nlet isLoading = false;\n// 鼠标拖拽下拉(PC 端)\nlet isMouseDown = false;\n\n// 节流相关\nlet scrollTimer: number | null = null;\nlet touchMoveRaf: number | null = null;\nlet idleTimer: number | null = null;\n// 监听容器尺寸变化\nlet resizeObserver: ResizeObserver | null = null;\n// 防止空页死循环:snapshot 触发 load 时的 contentHeight,下次完成后没增长就不再 auto-load\nlet lastTriggerContentHeight = -1;\n\n// 下拉刷新核心:按下(与指针类型无关,touch/mouse 共用)\nfunction pullStart(clientX: number, clientY: number) {\n if (!props.pullRefresh || isRefreshing) return;\n startX = clientX;\n startY = clientY;\n lastDistance = 0;\n gestureLock = 'none';\n pullStatus.value = 'idle';\n}\n\n// 下拉刷新核心:移动(rAF 节流,始终使用最新位置)\n// 返回 true 表示消费了本次移动(下拉进行中),调用方据此决定是否 preventDefault\nfunction pullMove(clientX: number, clientY: number, cancelable: boolean): boolean {\n if (!props.pullRefresh || isRefreshing || startY === 0) return false;\n\n const dx = clientX - startX;\n const dy = clientY - startY;\n\n // 方向判定:先到达 8px 锁定的方向才认为有意图,垂直要明显大于水平(1.5x)\n if (gestureLock === 'none') {\n const ax = Math.abs(dx);\n const ay = Math.abs(dy);\n if (ax > SLOPE_LOCK_DISTANCE || ay > SLOPE_LOCK_DISTANCE) {\n gestureLock = ay > ax * 1.5 ? 'vertical' : 'horizontal';\n } else {\n return false;\n }\n }\n if (gestureLock === 'horizontal') return false;\n\n // 子像素容差:≤1 视为已到顶,避免 X5 内核 scrollTop 偶发非整数判断失败\n const atTop = getScrollTop() <= 1;\n\n // iOS 15- 兜底:在 atBottom 且向上拖时阻止整页 rubber-band\n // (iOS 16+ overscroll-behavior 已处理,此处兜底兼容)\n if (!atTop && dy < 0) {\n const c = containerRef.value;\n if (c) {\n const atBottom = c.scrollHeight - c.scrollTop - c.clientHeight <= 1;\n if (atBottom && cancelable) {\n return true;\n }\n }\n return false;\n }\n if (!atTop) return false;\n\n // 反向拖动也要更新(让指示器跟着手指回退),夹紧到 [0, 150]\n const clamped = Math.max(0, Math.min(dy, 150));\n\n lastDistance = clamped;\n isDragging.value = true;\n\n if (touchMoveRaf === null) {\n touchMoveRaf = requestAnimationFrame(() => {\n touchMoveRaf = null;\n pullDistance.value = lastDistance * 0.5;\n if (lastDistance === 0) {\n pullStatus.value = 'idle';\n } else {\n pullStatus.value = lastDistance > props.pullThreshold ? 'loosing' : 'pulling';\n }\n });\n }\n\n // 仅在向下拖时消费事件(避免误吞用户向上滚动的意图)\n return dy > 0;\n}\n\n// 下拉刷新核心:松开\nfunction pullEnd() {\n if (!props.pullRefresh || isRefreshing) return;\n\n // 取消待执行的 rAF,避免松手后还有一帧异步更新把 UI 拉回 loosing 卡住\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n\n isDragging.value = false;\n\n // 用最新的 lastDistance 同步判定,不依赖可能尚未生效的 pullStatus\n if (lastDistance > props.pullThreshold) {\n isRefreshing = true;\n pullStatus.value = 'refreshing';\n pullDistance.value = 50;\n emit('refresh');\n } else {\n pullDistance.value = 0;\n pullStatus.value = 'idle';\n }\n\n resetTouch();\n}\n\n// 下拉刷新核心:取消(回到 idle)\nfunction pullCancel() {\n if (!props.pullRefresh || isRefreshing) return;\n\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n\n isDragging.value = false;\n pullDistance.value = 0;\n pullStatus.value = 'idle';\n resetTouch();\n}\n\n// 下拉刷新 - touchstart\nfunction handleTouchStart(e: TouchEvent) {\n pullStart(e.touches[0].clientX, e.touches[0].clientY);\n}\n\n// 下拉刷新 - touchmove\nfunction handleTouchMove(e: TouchEvent) {\n const touch = e.touches[0];\n if (pullMove(touch.clientX, touch.clientY, e.cancelable) && e.cancelable) {\n e.preventDefault();\n }\n}\n\n// 下拉刷新 - touchend\nfunction handleTouchEnd() {\n pullEnd();\n}\n\n// 下拉刷新 - touchcancel(微信 X5 内核会在系统手势介入时派发 cancel 而非 end)\nfunction handleTouchCancel() {\n pullCancel();\n}\n\n// 下拉刷新 - mousedown(PC 端鼠标拖拽)\nfunction handleMouseDown(e: MouseEvent) {\n // 仅左键触发\n if (!props.pullRefresh || isRefreshing || e.button !== 0) return;\n isMouseDown = true;\n pullStart(e.clientX, e.clientY);\n}\n\n// 下拉刷新 - mousemove(挂在 window 上,允许拖出容器)\nfunction handleMouseMove(e: MouseEvent) {\n if (!isMouseDown) return;\n if (pullMove(e.clientX, e.clientY, true)) {\n // 阻止拖拽时选中文本\n e.preventDefault();\n }\n}\n\n// 下拉刷新 - mouseup\nfunction handleMouseUp() {\n if (!isMouseDown) return;\n isMouseDown = false;\n pullEnd();\n}\n\nfunction resetTouch() {\n startX = 0;\n startY = 0;\n lastDistance = 0;\n gestureLock = 'none';\n}\n\n// 上拉加载 - scroll(节流处理)\nfunction handleScroll() {\n if (props.loading || props.finished || props.error || isLoading) return;\n if (scrollTimer) return;\n\n scrollTimer = window.setTimeout(() => {\n scrollTimer = null;\n const container = containerRef.value;\n if (!container) return;\n\n const { scrollTop, clientHeight, scrollHeight } = container;\n const contentHeight = contentRef.value?.clientHeight || 0;\n\n // 情况1:内容不足一屏,自动触发加载\n if (contentHeight <= clientHeight) {\n // 死循环防护:上一次 auto-load 后内容没增长,不再继续触发\n if (lastTriggerContentHeight === contentHeight) return;\n lastTriggerContentHeight = contentHeight;\n // 不足一屏时主动滚到底,让 loading 指示器进入视口\n triggerLoad(true);\n return;\n }\n\n // 情况2:滚动到距离底部阈值范围内\n if (scrollHeight - scrollTop - clientHeight <= props.loadThreshold) {\n // 用户已经手动滚到底,不再强制 scrollTop(避免打断惯性滚动)\n triggerLoad(false);\n }\n }, 100);\n}\n\nfunction triggerLoad(scrollToBottom = false) {\n isLoading = true;\n emit('load');\n nextTick(() => {\n const c = containerRef.value;\n if (!c) return;\n if (scrollToBottom) {\n // 内容不足一屏:直接滚到底(没有\"惯性\"可以打断)\n c.scrollTop = c.scrollHeight;\n } else {\n // 阈值触发:用 scrollIntoView 平滑进入视口,避免突兀打断;\n // 旧 WebKit 不识别 options 会降级为即时滚动,效果等价\n indicatorRef.value?.scrollIntoView({ block: 'end', behavior: 'smooth' });\n }\n });\n}\n\n// 获取滚动距离\nfunction getScrollTop(): number {\n return containerRef.value?.scrollTop ?? 0;\n}\n\n// 完成下拉刷新\nfunction finishRefresh() {\n isRefreshing = false;\n pullDistance.value = 0;\n // 让 transform snap-back 完成后再切 idle,避免 spinner 在动画中途突然消失\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = window.setTimeout(() => {\n pullStatus.value = 'idle';\n idleTimer = null;\n }, 250);\n // 重置死循环计数\n lastTriggerContentHeight = -1;\n nextTick(() => {\n checkContentHeight();\n });\n}\n\n// 完成上拉加载\nfunction finishLoad() {\n isLoading = false;\n nextTick(() => {\n const contentHeight = contentRef.value?.clientHeight || 0;\n // 内容没增长就不再触发 auto-load,避免空页死循环\n if (lastTriggerContentHeight !== -1 && contentHeight === lastTriggerContentHeight) return;\n lastTriggerContentHeight = -1;\n checkContentHeight();\n });\n}\n\n// 检查内容高度,不足一屏时自动触发加载\nfunction checkContentHeight() {\n if (props.finished || props.loading || props.error) return;\n\n nextTick(() => {\n const contentHeight = contentRef.value?.clientHeight || 0;\n const clientHeight = containerRef.value?.clientHeight || 0;\n\n if (contentHeight <= clientHeight && !isLoading) {\n handleScroll();\n }\n });\n}\n\n// 点击错误重试 — 由父组件决定具体行为,避免 retry + load 双触发\nfunction handleRetry() {\n emit('retry');\n}\n\n// loading 完成时同步重置 isLoading\nwatch(\n () => props.loading,\n newVal => {\n if (!newVal) {\n isLoading = false;\n }\n }\n);\n\n// finished 翻为 true 时重置死循环计数\nwatch(\n () => props.finished,\n newVal => {\n if (newVal) {\n lastTriggerContentHeight = -1;\n }\n }\n);\n\nonMounted(() => {\n const container = containerRef.value;\n if (!container) return;\n\n if (props.pullRefresh) {\n container.addEventListener('touchstart', handleTouchStart, { passive: true });\n container.addEventListener('touchmove', handleTouchMove, { passive: false });\n container.addEventListener('touchend', handleTouchEnd, { passive: true });\n container.addEventListener('touchcancel', handleTouchCancel, { passive: true });\n // 鼠标拖拽(PC):move/up 挂在 window 上,允许拖出容器边界\n container.addEventListener('mousedown', handleMouseDown);\n window.addEventListener('mousemove', handleMouseMove);\n window.addEventListener('mouseup', handleMouseUp);\n }\n\n container.addEventListener('scroll', handleScroll, { passive: true });\n\n // 监听容器/内容尺寸变化(旋转、WebView resize、软键盘弹出收起等)\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(() => {\n checkContentHeight();\n });\n resizeObserver.observe(container);\n if (contentRef.value) resizeObserver.observe(contentRef.value);\n }\n\n // 初始检查内容高度\n nextTick(() => {\n checkContentHeight();\n });\n});\n\nonBeforeUnmount(() => {\n const container = containerRef.value;\n if (container) {\n container.removeEventListener('touchstart', handleTouchStart);\n container.removeEventListener('touchmove', handleTouchMove);\n container.removeEventListener('touchend', handleTouchEnd);\n container.removeEventListener('touchcancel', handleTouchCancel);\n container.removeEventListener('mousedown', handleMouseDown);\n container.removeEventListener('scroll', handleScroll);\n }\n window.removeEventListener('mousemove', handleMouseMove);\n window.removeEventListener('mouseup', handleMouseUp);\n if (resizeObserver) {\n resizeObserver.disconnect();\n resizeObserver = null;\n }\n if (touchMoveRaf !== null) {\n cancelAnimationFrame(touchMoveRaf);\n touchMoveRaf = null;\n }\n if (scrollTimer !== null) {\n clearTimeout(scrollTimer);\n scrollTimer = null;\n }\n if (idleTimer !== null) {\n clearTimeout(idleTimer);\n idleTimer = null;\n }\n});\n\ndefineExpose({\n refresh: finishRefresh,\n finishRefresh,\n finishPullDown: finishRefresh,\n finishLoad,\n finishPullUp: finishLoad,\n checkContentHeight\n});\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"infinite-scroll\">\n <!-- 下拉刷新指示器 -->\n <div\n v-if=\"pullRefresh\"\n class=\"pull-refresh-indicator\"\n :class=\"{ 'no-transition': isDragging }\"\n :style=\"{ transform: `translateY(${pullDistance}px)` }\"\n >\n <div v-show=\"pullStatus !== 'idle'\" class=\"pull-refresh-content\">\n <!-- <RequestLoading v-if=\"pullStatus === 'refreshing'\" text=\"\" /> -->\n <span v-if=\"pullStatus === 'refreshing'\"></span>\n <span v-else-if=\"pullStatus === 'loosing'\" class=\"pull-text\">{{ loosingText }}</span>\n <span v-else class=\"pull-text\">{{ pullingText }}</span>\n </div>\n </div>\n\n <!-- 内容区域 -->\n <div\n ref=\"contentRef\"\n class=\"infinite-scroll-content\"\n :class=\"{ 'no-transition': isDragging }\"\n :style=\"pullRefresh ? { transform: `translateY(${pullDistance}px)` } : {}\"\n >\n <!-- 内容区域加载遮罩 -->\n <div v-if=\"contentLoading\" class=\"content-loading-mask\">\n <RequestLoading :text=\"refreshingText\" />\n </div>\n\n <!-- 空状态 -->\n <EmptyComponent v-if=\"isEmpty && !loading\" :text=\"emptyText\" />\n\n <!-- 默认插槽 -->\n <slot></slot>\n\n <!-- 上拉加载指示器 -->\n <div v-if=\"!isEmpty\" ref=\"indicatorRef\" class=\"load-more-indicator\">\n <RequestLoading v-if=\"loading && pullStatus !== 'refreshing'\" :text=\"loadingText\" />\n <span v-else-if=\"finished\" class=\"finished-text\">{{ finishedText }}</span>\n <span v-else-if=\"error\" class=\"error-text\" @click=\"handleRetry\">{{ errorText }}</span>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped lang=\"scss\">\n.infinite-scroll {\n height: 100%;\n position: relative;\n min-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n // 阻止滚动到边界时把滚动链传递给祖先(修复 iOS 16+ / 微信 X5 下拉变成整页拖拽)\n overscroll-behavior: contain;\n // 显式声明只接受纵向手势,避免被外层 touch-action 影响\n touch-action: pan-y;\n // iOS 下让原生回弹有惯性\n -webkit-overflow-scrolling: touch;\n}\n\n.pull-refresh-indicator {\n position: absolute;\n top: -50px;\n left: 0;\n right: 0;\n height: 50px;\n transition: transform 0.25s;\n\n // 拖动期间禁用过渡,让 transform 1:1 跟随手指\n &.no-transition {\n transition: none;\n }\n}\n\n.pull-refresh-content {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 14px;\n color: #969799;\n}\n\n.pull-text {\n color: var(--zx-primary-color, #1989fa);\n font-size: 14px;\n}\n\n.infinite-scroll-content {\n position: relative;\n transition: transform 0.25s;\n min-height: 100%;\n\n &.no-transition {\n transition: none;\n }\n}\n\n.content-loading-mask {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: rgba(255, 255, 255, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10;\n backdrop-filter: blur(2px);\n}\n\n.load-more-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n // 预留高度避免 loading 渲染后被推到视口外造成遮挡\n min-height: 50px;\n padding: 16px 0;\n font-size: 14px;\n color: #8c8f97;\n}\n\n.finished-text {\n color: #8c8f97;\n font-size: 14px;\n}\n\n.error-text {\n color: #ee0a24;\n font-size: 14px;\n cursor: pointer;\n\n &:active {\n opacity: 0.7;\n }\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T\">\nimport { type Ref, nextTick, onMounted, ref, watch } from 'vue';\nimport InfiniteScroll from './infinite-scroll.vue';\n\ndefineOptions({\n name: 'InfiniteScrollV2'\n});\n\ninterface FetchParams {\n page: number;\n pageSize: number;\n [key: string]: any;\n}\n\ninterface FetchResult<R> {\n records: R[];\n total: number;\n}\n\ninterface Props {\n /** 数据请求函数,需返回 { records, total },失败时抛错 */\n fetchFn: (params: FetchParams) => Promise<FetchResult<T>>;\n /** 业务查询参数(如筛选条件),变化时自动 reset 重新加载 */\n params?: Record<string, any>;\n /** 每页条数 */\n pageSize?: number;\n /** params 变化时是否自动重新加载 */\n autoReload?: boolean;\n /** 是否在挂载时自动加载首页 */\n immediate?: boolean;\n /** 是否启用下拉刷新 */\n pullRefresh?: boolean;\n /** 下拉刷新触发距离(px) */\n pullThreshold?: number;\n /** 上拉加载触发距离(距离底部 px) */\n loadThreshold?: number;\n /** 空状态文案 */\n emptyText?: string;\n /** 下拉中的文案 */\n pullingText?: string;\n /** 释放刷新的文案 */\n loosingText?: string;\n /** 刷新中的文案 */\n refreshingText?: string;\n /** 加载中的文案 */\n loadingText?: string;\n /** 加载完成的文案 */\n finishedText?: string;\n /** 加载失败的文案 */\n errorText?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n params: () => ({}),\n pageSize: 10,\n autoReload: true,\n immediate: true,\n pullRefresh: false,\n pullThreshold: 70,\n loadThreshold: 50,\n emptyText: '暂无数据',\n pullingText: '下拉刷新',\n loosingText: '释放即可刷新',\n refreshingText: '加载中...',\n loadingText: '加载中...',\n finishedText: '没有更多了~',\n errorText: '加载失败,点击重试'\n});\n\nconst emit = defineEmits<{\n /** 加载成功,每次成功(含分页)都会触发 */\n success: [payload: { records: T[]; total: number; reset: boolean }];\n /** 加载失败 */\n error: [err: unknown];\n /** 列表内容变化(v-model:list 支持) */\n 'update:list': [list: T[]];\n}>();\n\nconst scrollRef = ref<InstanceType<typeof InfiniteScroll> | null>(null);\n\nconst list = ref<T[]>([]) as Ref<T[]>;\nconst loading = ref(false);\nconst finished = ref(false);\nconst isEmpty = ref(false);\nconst loadError = ref(false);\nconst total = ref(0);\nconst page = ref(1);\nconst isRefreshing = ref(false);\n\nlet requestSeq = 0;\n\nasync function load(reset = false) {\n if (reset) {\n page.value = 1;\n finished.value = false;\n isEmpty.value = false;\n // 不立即清空 list,保留旧数据直到新数据到达\n }\n loading.value = true;\n loadError.value = false;\n\n requestSeq += 1;\n const seq = requestSeq;\n\n try {\n const result = await props.fetchFn({\n page: page.value,\n pageSize: props.pageSize,\n ...props.params\n });\n\n if (seq !== requestSeq) return;\n\n const records = result?.records || [];\n if (reset) {\n // reset 时替换整个列表\n list.value = records;\n total.value = 0;\n } else {\n list.value = [...list.value, ...records];\n }\n total.value = Number(result?.total || 0);\n finished.value = list.value.length >= total.value;\n isEmpty.value = list.value.length === 0;\n\n emit('success', { records, total: total.value, reset });\n emit('update:list', list.value);\n } catch (err) {\n if (seq !== requestSeq) return;\n loadError.value = true;\n emit('error', err);\n } finally {\n if (seq === requestSeq) {\n loading.value = false;\n }\n }\n}\n\nasync function handleRefresh() {\n isRefreshing.value = true;\n // 先让下拉刷新指示器收回\n scrollRef.value?.finishRefresh();\n // 等待 250ms 让指示器完全收回(对应 infinite-scroll.vue 的 transition 时长)\n await new Promise<void>(resolve => {\n setTimeout(() => resolve(), 250);\n });\n // 指示器收回后再开始请求新数据\n await load(true);\n isRefreshing.value = false;\n}\n\nasync function handleLoad() {\n if (finished.value) {\n scrollRef.value?.finishLoad();\n return;\n }\n // 列表为空时是首次加载(page 已经是 1),其它情况 page++\n if (list.value.length > 0) {\n page.value += 1;\n }\n await load(false);\n scrollRef.value?.finishLoad();\n}\n\nfunction handleRetry() {\n loadError.value = false;\n if (list.value.length === 0) {\n load(true);\n } else {\n handleLoad();\n }\n}\n\nasync function reload() {\n await load(true);\n}\n\nwatch(\n () => props.params,\n () => {\n if (props.autoReload) {\n reload();\n }\n },\n { deep: true }\n);\n\nwatch(\n list,\n async () => {\n await nextTick();\n scrollRef.value?.checkContentHeight();\n },\n { deep: true }\n);\n\nonMounted(() => {\n if (props.immediate) {\n load(true);\n }\n});\n\ndefineExpose({\n /** 重新加载第一页 */\n reload,\n /** 兼容别名 */\n refresh: reload,\n /** 当前列表数据 */\n list,\n /** 加载中状态 */\n loading,\n /** 是否已加载完 */\n finished,\n /** 是否空数据 */\n isEmpty,\n /** 总条数 */\n total,\n /** 透传 v1 方法 */\n finishLoad: () => scrollRef.value?.finishLoad(),\n finishRefresh: () => scrollRef.value?.finishRefresh(),\n checkContentHeight: () => scrollRef.value?.checkContentHeight()\n});\n</script>\n\n<template>\n <InfiniteScroll\n ref=\"scrollRef\"\n :loading=\"loading\"\n :finished=\"finished\"\n :is-empty=\"isEmpty\"\n :error=\"loadError\"\n :pull-refresh=\"pullRefresh\"\n :pull-threshold=\"pullThreshold\"\n :load-threshold=\"loadThreshold\"\n :empty-text=\"emptyText\"\n :pulling-text=\"pullingText\"\n :loosing-text=\"loosingText\"\n :refreshing-text=\"refreshingText\"\n :loading-text=\"loadingText\"\n :finished-text=\"finishedText\"\n :error-text=\"errorText\"\n :content-loading=\"isRefreshing\"\n @load=\"handleLoad\"\n @refresh=\"handleRefresh\"\n @retry=\"handleRetry\"\n >\n <slot\n :list=\"list\"\n :loading=\"loading\"\n :finished=\"finished\"\n :is-empty=\"isEmpty\"\n :error=\"loadError\"\n :total=\"total\"\n :reload=\"reload\"\n />\n </InfiniteScroll>\n</template>\n","import { App } from 'vue';\nimport InfiniteScroll from './components/infinite-scroll.vue';\nimport InfiniteScrollV2 from './components/infinite-scroll-v2.vue';\nimport RequestLoading from './components/request-loading.vue';\nimport EmptyComponent from './components/empty-component.vue';\n\n// 单独导出组件\nexport { InfiniteScroll, InfiniteScrollV2, RequestLoading, EmptyComponent };\n\n// Vue plugin install 函数\nexport function install(app: App) {\n app.component('InfiniteScroll', InfiniteScroll);\n app.component('InfiniteScrollV2', InfiniteScrollV2);\n app.component('RequestLoading', RequestLoading);\n app.component('EmptyComponent', EmptyComponent);\n}\n\n// 默认导出(支持 app.use() 安装)\nexport default {\n install\n};\n"],"names":["_createElementBlock","_normalizeClass","__props","_createElementVNode","_hoisted_1","_hoisted_2","_toDisplayString","defaultEmptySvg","props","wrapStyle","computed","imageSrc","_openBlock","_renderSlot","_ctx","SLOPE_LOCK_DISTANCE","emit","__emit","containerRef","ref","contentRef","indicatorRef","pullStatus","pullDistance","isDragging","startX","startY","lastDistance","gestureLock","isRefreshing","isLoading","isMouseDown","scrollTimer","touchMoveRaf","idleTimer","resizeObserver","lastTriggerContentHeight","pullStart","clientX","clientY","pullMove","cancelable","dx","dy","ax","ay","atTop","getScrollTop","c","pullEnd","resetTouch","pullCancel","handleTouchStart","e","handleTouchMove","touch","handleTouchEnd","handleTouchCancel","handleMouseDown","handleMouseMove","handleMouseUp","handleScroll","container","scrollTop","clientHeight","scrollHeight","contentHeight","_a","triggerLoad","scrollToBottom","nextTick","finishRefresh","checkContentHeight","finishLoad","_b","handleRetry","watch","newVal","onMounted","onBeforeUnmount","__expose","_withDirectives","_hoisted_3","_hoisted_4","_normalizeStyle","_hoisted_5","_createVNode","RequestLoading","_createBlock","EmptyComponent","_hoisted_6","scrollRef","list","loading","finished","isEmpty","loadError","total","page","requestSeq","load","reset","seq","result","records","err","handleRefresh","resolve","handleLoad","reload","InfiniteScroll","install","app","InfiniteScrollV2","index"],"mappings":"qfAUEA,EAAAA,mBAKM,MAAA,CALD,MAAKC,EAAAA,eAAA,CAAC,oBAAmB,CAAA,WAAuBC,EAAA,WAAU,CAAA,CAAA,GAC7DC,EAAAA,mBAGM,MAHNC,GAGM,aAFJD,EAAAA,mBAAqC,OAAA,CAA/B,MAAM,iBAAA,EAAiB,KAAA,EAAA,GAC7BA,EAAAA,mBAA4C,OAA5CE,GAA4CC,EAAAA,gBAAdJ,EAAA,IAAI,EAAA,CAAA,CAAA,mKCalCK,GAAkB,izBAZxB,MAAMC,EAAQN,EAORO,EAAYC,EAAAA,SAAS,KAAO,CAChC,WAAY,OAAOF,EAAM,YAAe,SAAW,GAAGA,EAAM,UAAU,KAAOA,EAAM,UAAA,EACnF,EAKIG,EAAWD,EAAAA,SAAS,IAAMF,EAAM,YAAcD,EAAe,8BAIjEP,EAAAA,mBAKM,MAAA,CALD,MAAM,aAAc,uBAAOS,EAAA,KAAS,CAAA,GACvCN,EAAAA,mBAAkD,MAAA,CAA7C,MAAM,cAAe,IAAKQ,EAAA,MAAU,IAAI,EAAA,aACpCT,EAAA,UAATU,EAAAA,UAAA,EAAAZ,EAAAA,mBAEI,IAFJK,GAEI,CADFQ,EAAAA,WAAuBC,sBAAvB,IAAuB,qCAAdZ,EAAA,IAAI,EAAA,CAAA,CAAA,gQCkDba,EAAsB,gnBA3C5B,MAAMP,EAAQN,EAkBRc,EAAOC,EASPC,EAAeC,EAAAA,IAAwB,IAAI,EAC3CC,EAAaD,EAAAA,IAAwB,IAAI,EACzCE,EAAeF,EAAAA,IAAwB,IAAI,EAG3CG,EAAaH,EAAAA,IAAmD,MAAM,EACtEI,EAAeJ,EAAAA,IAAI,CAAC,EAEpBK,EAAaL,EAAAA,IAAI,EAAK,EAG5B,IAAIM,EAAS,EACTC,EAAS,EACTC,EAAe,EAEfC,EAAkD,OAGlDC,EAAe,GACfC,EAAY,GAEZC,EAAc,GAGdC,EAA6B,KAC7BC,EAA8B,KAC9BC,EAA2B,KAE3BC,EAAwC,KAExCC,EAA2B,GAG/B,SAASC,EAAUC,EAAiBC,EAAiB,CAC/C,CAAC/B,EAAM,aAAeqB,IAC1BJ,EAASa,EACTZ,EAASa,EACTZ,EAAe,EACfC,EAAc,OACdN,EAAW,MAAQ,OACrB,CAIA,SAASkB,EAASF,EAAiBC,EAAiBE,EAA8B,CAChF,GAAI,CAACjC,EAAM,aAAeqB,GAAgBH,IAAW,EAAG,MAAO,GAE/D,MAAMgB,EAAKJ,EAAUb,EACfkB,EAAKJ,EAAUb,EAGrB,GAAIE,IAAgB,OAAQ,CAC1B,MAAMgB,EAAK,KAAK,IAAIF,CAAE,EAChBG,EAAK,KAAK,IAAIF,CAAE,EACtB,GAAIC,EAAK7B,GAAuB8B,EAAK9B,EACnCa,EAAciB,EAAKD,EAAK,IAAM,WAAa,iBAE3C,OAAO,EAEX,CACA,GAAIhB,IAAgB,aAAc,MAAO,GAGzC,MAAMkB,EAAQC,MAAkB,EAIhC,GAAI,CAACD,GAASH,EAAK,EAAG,CACpB,MAAMK,EAAI9B,EAAa,MACvB,MAAI,GAAA8B,GACeA,EAAE,aAAeA,EAAE,UAAYA,EAAE,cAAgB,GAClDP,EAKpB,CACA,OAAKK,GAKLnB,EAFgB,KAAK,IAAI,EAAG,KAAK,IAAIgB,EAAI,GAAG,CAAC,EAG7CnB,EAAW,MAAQ,GAEfS,IAAiB,OACnBA,EAAe,sBAAsB,IAAM,CACzCA,EAAe,KACfV,EAAa,MAAQI,EAAe,GAChCA,IAAiB,EACnBL,EAAW,MAAQ,OAEnBA,EAAW,MAAQK,EAAenB,EAAM,cAAgB,UAAY,SAExE,CAAC,GAIImC,EAAK,GArBO,EAsBrB,CAGA,SAASM,GAAU,CACb,CAACzC,EAAM,aAAeqB,IAGtBI,IAAiB,OACnB,qBAAqBA,CAAY,EACjCA,EAAe,MAGjBT,EAAW,MAAQ,GAGfG,EAAenB,EAAM,eACvBqB,EAAe,GACfP,EAAW,MAAQ,aACnBC,EAAa,MAAQ,GACrBP,EAAK,SAAS,IAEdO,EAAa,MAAQ,EACrBD,EAAW,MAAQ,QAGrB4B,EAAA,EACF,CAGA,SAASC,IAAa,CAChB,CAAC3C,EAAM,aAAeqB,IAEtBI,IAAiB,OACnB,qBAAqBA,CAAY,EACjCA,EAAe,MAGjBT,EAAW,MAAQ,GACnBD,EAAa,MAAQ,EACrBD,EAAW,MAAQ,OACnB4B,EAAA,EACF,CAGA,SAASE,EAAiBC,EAAe,CACvChB,EAAUgB,EAAE,QAAQ,CAAC,EAAE,QAASA,EAAE,QAAQ,CAAC,EAAE,OAAO,CACtD,CAGA,SAASC,EAAgBD,EAAe,CACtC,MAAME,EAAQF,EAAE,QAAQ,CAAC,EACrBb,EAASe,EAAM,QAASA,EAAM,QAASF,EAAE,UAAU,GAAKA,EAAE,YAC5DA,EAAE,eAAA,CAEN,CAGA,SAASG,GAAiB,CACxBP,EAAA,CACF,CAGA,SAASQ,GAAoB,CAC3BN,GAAA,CACF,CAGA,SAASO,EAAgBL,EAAe,CAElC,CAAC7C,EAAM,aAAeqB,GAAgBwB,EAAE,SAAW,IACvDtB,EAAc,GACdM,EAAUgB,EAAE,QAASA,EAAE,OAAO,EAChC,CAGA,SAASM,EAAgBN,EAAe,CACjCtB,GACDS,EAASa,EAAE,QAASA,EAAE,QAAS,EAAI,GAErCA,EAAE,eAAA,CAEN,CAGA,SAASO,GAAgB,CAClB7B,IACLA,EAAc,GACdkB,EAAA,EACF,CAEA,SAASC,GAAa,CACpBzB,EAAS,EACTC,EAAS,EACTC,EAAe,EACfC,EAAc,MAChB,CAGA,SAASiC,GAAe,CAClBrD,EAAM,SAAWA,EAAM,UAAYA,EAAM,OAASsB,GAClDE,IAEJA,EAAc,OAAO,WAAW,IAAM,OACpCA,EAAc,KACd,MAAM8B,EAAY5C,EAAa,MAC/B,GAAI,CAAC4C,EAAW,OAEhB,KAAM,CAAE,UAAAC,EAAW,aAAAC,EAAc,aAAAC,CAAA,EAAiBH,EAC5CI,IAAgBC,EAAA/C,EAAW,QAAX,YAAA+C,EAAkB,eAAgB,EAGxD,GAAID,GAAiBF,EAAc,CAEjC,GAAI5B,IAA6B8B,EAAe,OAChD9B,EAA2B8B,EAE3BE,GAAY,EAAI,EAChB,MACF,CAGIH,EAAeF,EAAYC,GAAgBxD,EAAM,eAEnD4D,GAAY,EAAK,CAErB,EAAG,GAAG,EACR,CAEA,SAASA,GAAYC,EAAiB,GAAO,CAC3CvC,EAAY,GACZd,EAAK,MAAM,EACXsD,EAAAA,SAAS,IAAM,OACb,MAAMtB,EAAI9B,EAAa,MAClB8B,IACDqB,EAEFrB,EAAE,UAAYA,EAAE,cAIhBmB,EAAA9C,EAAa,QAAb,MAAA8C,EAAoB,eAAe,CAAE,MAAO,MAAO,SAAU,WAEjE,CAAC,CACH,CAGA,SAASpB,IAAuB,OAC9B,QAAOoB,EAAAjD,EAAa,QAAb,YAAAiD,EAAoB,YAAa,CAC1C,CAGA,SAASI,GAAgB,CACvB1C,EAAe,GACfN,EAAa,MAAQ,EAEjBW,IAAc,MAAM,aAAaA,CAAS,EAC9CA,EAAY,OAAO,WAAW,IAAM,CAClCZ,EAAW,MAAQ,OACnBY,EAAY,IACd,EAAG,GAAG,EAENE,EAA2B,GAC3BkC,EAAAA,SAAS,IAAM,CACbE,EAAA,CACF,CAAC,CACH,CAGA,SAASC,IAAa,CACpB3C,EAAY,GACZwC,EAAAA,SAAS,IAAM,OACb,MAAMJ,IAAgBC,EAAA/C,EAAW,QAAX,YAAA+C,EAAkB,eAAgB,EAEpD/B,IAA6B,IAAM8B,IAAkB9B,IACzDA,EAA2B,GAC3BoC,EAAA,EACF,CAAC,CACH,CAGA,SAASA,GAAqB,CACxBhE,EAAM,UAAYA,EAAM,SAAWA,EAAM,OAE7C8D,EAAAA,SAAS,IAAM,SACb,MAAMJ,IAAgBC,EAAA/C,EAAW,QAAX,YAAA+C,EAAkB,eAAgB,EAClDH,IAAeU,EAAAxD,EAAa,QAAb,YAAAwD,EAAoB,eAAgB,EAErDR,GAAiBF,GAAgB,CAAClC,GACpC+B,EAAA,CAEJ,CAAC,CACH,CAGA,SAASc,IAAc,CACrB3D,EAAK,OAAO,CACd,CAGA4D,OAAAA,EAAAA,MACE,IAAMpE,EAAM,QACZqE,GAAU,CACHA,IACH/C,EAAY,GAEhB,CAAA,EAIF8C,EAAAA,MACE,IAAMpE,EAAM,SACZqE,GAAU,CACJA,IACFzC,EAA2B,GAE/B,CAAA,EAGF0C,EAAAA,UAAU,IAAM,CACd,MAAMhB,EAAY5C,EAAa,MAC1B4C,IAEDtD,EAAM,cACRsD,EAAU,iBAAiB,aAAcV,EAAkB,CAAE,QAAS,GAAM,EAC5EU,EAAU,iBAAiB,YAAaR,EAAiB,CAAE,QAAS,GAAO,EAC3EQ,EAAU,iBAAiB,WAAYN,EAAgB,CAAE,QAAS,GAAM,EACxEM,EAAU,iBAAiB,cAAeL,EAAmB,CAAE,QAAS,GAAM,EAE9EK,EAAU,iBAAiB,YAAaJ,CAAe,EACvD,OAAO,iBAAiB,YAAaC,CAAe,EACpD,OAAO,iBAAiB,UAAWC,CAAa,GAGlDE,EAAU,iBAAiB,SAAUD,EAAc,CAAE,QAAS,GAAM,EAGhE,OAAO,eAAmB,MAC5B1B,EAAiB,IAAI,eAAe,IAAM,CACxCqC,EAAA,CACF,CAAC,EACDrC,EAAe,QAAQ2B,CAAS,EAC5B1C,EAAW,OAAOe,EAAe,QAAQf,EAAW,KAAK,GAI/DkD,EAAAA,SAAS,IAAM,CACbE,EAAA,CACF,CAAC,EACH,CAAC,EAEDO,EAAAA,gBAAgB,IAAM,CACpB,MAAMjB,EAAY5C,EAAa,MAC3B4C,IACFA,EAAU,oBAAoB,aAAcV,CAAgB,EAC5DU,EAAU,oBAAoB,YAAaR,CAAe,EAC1DQ,EAAU,oBAAoB,WAAYN,CAAc,EACxDM,EAAU,oBAAoB,cAAeL,CAAiB,EAC9DK,EAAU,oBAAoB,YAAaJ,CAAe,EAC1DI,EAAU,oBAAoB,SAAUD,CAAY,GAEtD,OAAO,oBAAoB,YAAaF,CAAe,EACvD,OAAO,oBAAoB,UAAWC,CAAa,EAC/CzB,IACFA,EAAe,WAAA,EACfA,EAAiB,MAEfF,IAAiB,OACnB,qBAAqBA,CAAY,EACjCA,EAAe,MAEbD,IAAgB,OAClB,aAAaA,CAAW,EACxBA,EAAc,MAEZE,IAAc,OAChB,aAAaA,CAAS,EACtBA,EAAY,KAEhB,CAAC,EAED8C,EAAa,CACX,QAAST,EACT,cAAAA,EACA,eAAgBA,EAChB,WAAAE,GACA,aAAcA,GACd,mBAAAD,CAAA,CACD,wBAICxE,EAAAA,mBAyCM,MAAA,SAzCG,eAAJ,IAAIkB,EAAe,MAAM,iBAAA,GAGpBhB,EAAA,2BADRF,EAAAA,mBAYM,MAAA,OAVJ,MAAKC,EAAAA,eAAA,CAAC,yBAAwB,CAAA,gBACHuB,EAAA,KAAA,CAAU,CAAA,EACpC,gDAAkCD,EAAA,KAAY,MAAA,CAAA,GAE/C0D,iBAAA9E,EAAAA,mBAKM,MALNC,GAKM,CAHQkB,EAAA,QAAU,4BAAtBtB,EAAAA,mBAAgD,OAAAK,EAAA,GAC/BiB,EAAA,QAAU,yBAA3BtB,EAAAA,mBAAqF,OAArFkF,GAAqF5E,EAAAA,gBAArBJ,EAAA,WAAW,EAAA,CAAA,kBAC3EF,EAAAA,mBAAuD,OAAvDmF,GAAuD7E,EAAAA,gBAArBJ,EAAA,WAAW,EAAA,CAAA,EAAA,iBAJlCoB,EAAA,QAAU,MAAA,CAAA,oCASzBnB,EAAAA,mBAuBM,MAAA,SAtBA,aAAJ,IAAIiB,EACJ,MAAKnB,EAAAA,eAAA,CAAC,0BAAyB,CAAA,gBACJuB,EAAA,KAAA,CAAU,CAAA,EACpC,MAAK4D,EAAAA,eAAElF,EAAA,YAAW,CAAA,UAAA,cAA8BqB,EAAA,KAAY,KAAA,EAAA,CAAA,CAAA,CAAA,GAGlDrB,EAAA,gBAAXU,EAAAA,UAAA,EAAAZ,EAAAA,mBAEM,MAFNqF,GAEM,CADJC,cAAyCC,EAAA,CAAxB,KAAMrF,EAAA,cAAA,EAAc,KAAA,EAAA,CAAA,MAAA,CAAA,CAAA,gCAIjBA,EAAA,UAAYA,EAAA,uBAAlCsF,EAAAA,YAA+DC,EAAA,OAAnB,KAAMvF,EAAA,SAAA,gDAGlDW,EAAAA,WAAaC,EAAA,OAAA,UAAA,CAAA,EAAA,OAAA,EAAA,EAGDZ,EAAA,mDAAZF,EAAAA,mBAIM,MAAA,eAJmB,eAAJ,IAAIqB,EAAe,MAAM,qBAAA,GACtBnB,EAAA,SAAWoB,EAAA,QAAU,4BAA3CkE,EAAAA,YAAoFD,EAAA,OAArB,KAAMrF,EAAA,WAAA,oBACpDA,EAAA,wBAAjBF,EAAAA,mBAA0E,OAA1E0F,GAA0EpF,EAAAA,gBAAtBJ,EAAA,YAAY,EAAA,CAAA,GAC/CA,EAAA,qBAAjBF,EAAAA,mBAAsF,OAAA,OAA9D,MAAM,aAAc,QAAO2E,EAAA,oBAAgBzE,EAAA,SAAS,EAAA,CAAA,mqBC7bpF,MAAMM,EAAQN,EAiBRc,EAAOC,EASP0E,EAAYxE,EAAAA,IAAgD,IAAI,EAEhEyE,EAAOzE,EAAAA,IAAS,EAAE,EAClB0E,EAAU1E,EAAAA,IAAI,EAAK,EACnB2E,EAAW3E,EAAAA,IAAI,EAAK,EACpB4E,EAAU5E,EAAAA,IAAI,EAAK,EACnB6E,EAAY7E,EAAAA,IAAI,EAAK,EACrB8E,EAAQ9E,EAAAA,IAAI,CAAC,EACb+E,EAAO/E,EAAAA,IAAI,CAAC,EACZU,EAAeV,EAAAA,IAAI,EAAK,EAE9B,IAAIgF,EAAa,EAEjB,eAAeC,EAAKC,EAAQ,GAAO,CAC7BA,IACFH,EAAK,MAAQ,EACbJ,EAAS,MAAQ,GACjBC,EAAQ,MAAQ,IAGlBF,EAAQ,MAAQ,GAChBG,EAAU,MAAQ,GAElBG,GAAc,EACd,MAAMG,EAAMH,EAEZ,GAAI,CACF,MAAMI,EAAS,MAAM/F,EAAM,QAAQ,CACjC,KAAM0F,EAAK,MACX,SAAU1F,EAAM,SAChB,GAAGA,EAAM,MAAA,CACV,EAED,GAAI8F,IAAQH,EAAY,OAExB,MAAMK,GAAUD,GAAA,YAAAA,EAAQ,UAAW,CAAA,EAC/BF,GAEFT,EAAK,MAAQY,EACbP,EAAM,MAAQ,GAEdL,EAAK,MAAQ,CAAC,GAAGA,EAAK,MAAO,GAAGY,CAAO,EAEzCP,EAAM,MAAQ,QAAOM,GAAA,YAAAA,EAAQ,QAAS,CAAC,EACvCT,EAAS,MAAQF,EAAK,MAAM,QAAUK,EAAM,MAC5CF,EAAQ,MAAQH,EAAK,MAAM,SAAW,EAEtC5E,EAAK,UAAW,CAAE,QAAAwF,EAAS,MAAOP,EAAM,MAAO,MAAAI,EAAO,EACtDrF,EAAK,cAAe4E,EAAK,KAAK,CAChC,OAASa,EAAK,CACZ,GAAIH,IAAQH,EAAY,OACxBH,EAAU,MAAQ,GAClBhF,EAAK,QAASyF,CAAG,CACnB,QAAA,CACMH,IAAQH,IACVN,EAAQ,MAAQ,GAEpB,CACF,CAEA,eAAea,GAAgB,OAC7B7E,EAAa,MAAQ,IAErBsC,EAAAwB,EAAU,QAAV,MAAAxB,EAAiB,gBAEjB,MAAM,IAAI,QAAcwC,GAAW,CACjC,WAAW,IAAMA,EAAA,EAAW,GAAG,CACjC,CAAC,EAED,MAAMP,EAAK,EAAI,EACfvE,EAAa,MAAQ,EACvB,CAEA,eAAe+E,GAAa,SAC1B,GAAId,EAAS,MAAO,EAClB3B,EAAAwB,EAAU,QAAV,MAAAxB,EAAiB,aACjB,MACF,CAEIyB,EAAK,MAAM,OAAS,IACtBM,EAAK,OAAS,GAEhB,MAAME,EAAK,EAAK,GAChB1B,EAAAiB,EAAU,QAAV,MAAAjB,EAAiB,YACnB,CAEA,SAASC,GAAc,CACrBqB,EAAU,MAAQ,GACdJ,EAAK,MAAM,SAAW,EACxBQ,EAAK,EAAI,EAETQ,EAAA,CAEJ,CAEA,eAAeC,GAAS,CACtB,MAAMT,EAAK,EAAI,CACjB,CAEAxB,OAAAA,EAAAA,MACE,IAAMpE,EAAM,OACZ,IAAM,CACAA,EAAM,YACRqG,EAAA,CAEJ,EACA,CAAE,KAAM,EAAA,CAAK,EAGfjC,EAAAA,MACEgB,EACA,SAAY,OACV,MAAMtB,WAAA,GACNH,EAAAwB,EAAU,QAAV,MAAAxB,EAAiB,oBACnB,EACA,CAAE,KAAM,EAAA,CAAK,EAGfW,EAAAA,UAAU,IAAM,CACVtE,EAAM,WACR4F,EAAK,EAAI,CAEb,CAAC,EAEDpB,EAAa,CAEX,OAAA6B,EAEA,QAASA,EAET,KAAAjB,EAEA,QAAAC,EAEA,SAAAC,EAEA,QAAAC,EAEA,MAAAE,EAEA,WAAY,IAAA,OAAM,OAAA9B,EAAAwB,EAAU,QAAV,YAAAxB,EAAiB,cACnC,cAAe,IAAA,OAAM,OAAAA,EAAAwB,EAAU,QAAV,YAAAxB,EAAiB,iBACtC,mBAAoB,IAAA,OAAM,OAAAA,EAAAwB,EAAU,QAAV,YAAAxB,EAAiB,qBAAmB,CAC/D,wBAICqB,EAAAA,YA8BiBsB,EAAA,SA7BX,YAAJ,IAAInB,EACH,QAASE,EAAA,MACT,SAAUC,EAAA,MACV,WAAUC,EAAA,MACV,MAAOC,EAAA,MACP,eAAc9F,EAAA,YACd,iBAAgBA,EAAA,cAChB,iBAAgBA,EAAA,cAChB,aAAYA,EAAA,UACZ,eAAcA,EAAA,YACd,eAAcA,EAAA,YACd,kBAAiBA,EAAA,eACjB,eAAcA,EAAA,YACd,gBAAeA,EAAA,aACf,aAAYA,EAAA,UACZ,kBAAiB2B,EAAA,MACjB,OAAM+E,EACN,UAASF,EACT,QAAO/B,CAAA,qBAER,IAQE,CARF9D,aAQEC,EAAA,OAAA,UAAA,CAPC,KAAM8E,EAAA,MACN,QAASC,EAAA,MACT,SAAUC,EAAA,MACV,QAAUC,EAAA,MACV,MAAOC,EAAA,MACP,MAAOC,EAAA,MACP,OAAAY,CAAA,uOCnPA,SAASE,EAAQC,EAAU,CAChCA,EAAI,UAAU,iBAAkBF,CAAc,EAC9CE,EAAI,UAAU,mBAAoBC,CAAgB,EAClDD,EAAI,UAAU,iBAAkBzB,CAAc,EAC9CyB,EAAI,UAAU,iBAAkBvB,CAAc,CAChD,CAGA,MAAAyB,GAAe,CACb,QAAAH,CACF"}
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .loading-content[data-v-d89150ba]{display:flex;align-items:center;justify-content:center;gap:8px;padding:6px 12px;border-radius:16px;color:var(--zx-primary-color, #1e5efd);font-size:14px}.loading-spinner[data-v-d89150ba]{width:18px;height:18px;border-radius:50%;border:2.5px solid var(--zx-primary-color, #1e5efd);border-top-color:transparent;animation:spin-d89150ba .8s linear infinite}@keyframes spin-d89150ba{to{transform:rotate(360deg)}}.loading-container.fullscreen[data-v-d89150ba]{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#ffffff80}.empty-wrap[data-v-3c452901]{display:flex;flex-direction:column;align-items:center}.empty-image[data-v-3c452901]{width:96px;height:80px}.empty-text[data-v-3c452901]{margin:12px 0 0;color:#8c8f97;font-size:12px;line-height:22px}.infinite-scroll[data-v-bf5ca1d9]{height:100%;position:relative;min-height:100%;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;touch-action:pan-y;-webkit-overflow-scrolling:touch}.pull-refresh-indicator[data-v-bf5ca1d9]{position:absolute;top:-50px;left:0;right:0;height:50px;transition:transform .25s}.pull-refresh-indicator.no-transition[data-v-bf5ca1d9]{transition:none}.pull-refresh-content[data-v-bf5ca1d9]{display:flex;align-items:center;justify-content:center;height:100%;font-size:14px;color:#969799}.pull-text[data-v-bf5ca1d9]{color:var(--zx-primary-color, #1989fa);font-size:14px}.infinite-scroll-content[data-v-bf5ca1d9]{position:relative;transition:transform .25s;min-height:100%}.infinite-scroll-content.no-transition[data-v-bf5ca1d9]{transition:none}.content-loading-mask[data-v-bf5ca1d9]{position:absolute;top:0;left:0;right:0;bottom:0;background-color:#fffc;display:flex;align-items:center;justify-content:center;z-index:10;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.load-more-indicator[data-v-bf5ca1d9]{display:flex;align-items:center;justify-content:center;min-height:50px;padding:16px 0;font-size:14px;color:#8c8f97}.finished-text[data-v-bf5ca1d9]{color:#8c8f97;font-size:14px}.error-text[data-v-bf5ca1d9]{color:#ee0a24;font-size:14px;cursor:pointer}.error-text[data-v-bf5ca1d9]:active{opacity:.7}
@@ -0,0 +1,25 @@
1
+ interface Props {
2
+ text?: string;
3
+ showText?: boolean;
4
+ /** 顶部间距,可传数字(px)或带单位字符串,默认 168px */
5
+ paddingTop?: number | string;
6
+ /** 自定义图片 URL */
7
+ emptyImage?: string;
8
+ }
9
+ declare var __VLS_1: {};
10
+ type __VLS_Slots = {} & {
11
+ default?: (props: typeof __VLS_1) => any;
12
+ };
13
+ declare const __VLS_component: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
14
+ text: string;
15
+ showText: boolean;
16
+ paddingTop: number | string;
17
+ emptyImage: string;
18
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
19
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
20
+ export default _default;
21
+ type __VLS_WithSlots<T, S> = T & {
22
+ new (): {
23
+ $slots: S;
24
+ };
25
+ };
@@ -0,0 +1,78 @@
1
+ interface Props {
2
+ /** 是否处于加载中状态 */
3
+ loading?: boolean;
4
+ /** 是否已加载完所有数据 */
5
+ finished?: boolean;
6
+ /** 是否显示空状态 */
7
+ isEmpty?: boolean;
8
+ /** 空状态文案 */
9
+ emptyText?: string;
10
+ /** 是否启用下拉刷新 */
11
+ pullRefresh?: boolean;
12
+ /** 下拉刷新触发距离(px) */
13
+ pullThreshold?: number;
14
+ /** 上拉加载触发距离(距离底部 px) */
15
+ loadThreshold?: number;
16
+ /** 下拉中的文案 */
17
+ pullingText?: string;
18
+ /** 释放刷新的文案 */
19
+ loosingText?: string;
20
+ /** 刷新中的文案 */
21
+ refreshingText?: string;
22
+ /** 加载中的文案 */
23
+ loadingText?: string;
24
+ /** 加载完成的文案 */
25
+ finishedText?: string;
26
+ /** 加载失败的文案 */
27
+ errorText?: string;
28
+ /** 是否显示错误状态 */
29
+ error?: boolean;
30
+ /** 内容区域是否显示加载状态 */
31
+ contentLoading?: boolean;
32
+ }
33
+ declare function finishRefresh(): void;
34
+ declare function finishLoad(): void;
35
+ declare function checkContentHeight(): void;
36
+ declare var __VLS_7: {};
37
+ type __VLS_Slots = {} & {
38
+ default?: (props: typeof __VLS_7) => any;
39
+ };
40
+ declare const __VLS_component: import("vue").DefineComponent<Props, {
41
+ refresh: typeof finishRefresh;
42
+ finishRefresh: typeof finishRefresh;
43
+ finishPullDown: typeof finishRefresh;
44
+ finishLoad: typeof finishLoad;
45
+ finishPullUp: typeof finishLoad;
46
+ checkContentHeight: typeof checkContentHeight;
47
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
48
+ refresh: () => any;
49
+ load: () => any;
50
+ retry: () => any;
51
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
52
+ onRefresh?: (() => any) | undefined;
53
+ onLoad?: (() => any) | undefined;
54
+ onRetry?: (() => any) | undefined;
55
+ }>, {
56
+ loading: boolean;
57
+ finished: boolean;
58
+ isEmpty: boolean;
59
+ emptyText: string;
60
+ pullRefresh: boolean;
61
+ pullThreshold: number;
62
+ loadThreshold: number;
63
+ pullingText: string;
64
+ loosingText: string;
65
+ refreshingText: string;
66
+ loadingText: string;
67
+ finishedText: string;
68
+ errorText: string;
69
+ error: boolean;
70
+ contentLoading: boolean;
71
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
72
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
73
+ export default _default;
74
+ type __VLS_WithSlots<T, S> = T & {
75
+ new (): {
76
+ $slots: S;
77
+ };
78
+ };
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ text?: string;
3
+ fullscreen?: boolean;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
6
+ text: string;
7
+ fullscreen: boolean;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { App } from 'vue';
2
+ import InfiniteScroll from './components/infinite-scroll.vue';
3
+ import InfiniteScrollV2 from './components/infinite-scroll-v2.vue';
4
+ import RequestLoading from './components/request-loading.vue';
5
+ import EmptyComponent from './components/empty-component.vue';
6
+ export { InfiniteScroll, InfiniteScrollV2, RequestLoading, EmptyComponent };
7
+ export declare function install(app: App): void;
8
+ declare const _default: {
9
+ install: typeof install;
10
+ };
11
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "zx-infinite-scroll",
3
+ "version": "1.0.0",
4
+ "description": "Vue 3 infinite scroll component with pull-to-refresh support",
5
+ "main": "dist/index.umd.js",
6
+ "module": "dist/index.es.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.es.js",
11
+ "require": "./dist/index.umd.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --outDir dist",
20
+ "build:types": "vue-tsc --declaration --emitDeclarationOnly --outDir dist",
21
+ "dev": "vite build --watch"
22
+ },
23
+ "keywords": [
24
+ "vue3",
25
+ "infinite-scroll",
26
+ "pull-refresh",
27
+ "list"
28
+ ],
29
+ "author": "changhaotian",
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "vue": "^3.3.0"
33
+ },
34
+ "devDependencies": {
35
+ "@vitejs/plugin-vue": "^5.0.0",
36
+ "sass": "^1.77.0",
37
+ "typescript": "^5.4.0",
38
+ "vite": "^5.0.0",
39
+ "vite-plugin-dts": "^3.9.0",
40
+ "vue": "^3.4.0",
41
+ "vue-tsc": "^2.0.0"
42
+ }
43
+ }