uniapp-dyckui 4.1.1
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/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/assets/style.BFlsbpSj.css +1472 -0
- package/dist/index.cjs.js +1380 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.es.js +1380 -0
- package/dist/index.es.js.map +1 -0
- package/dist/src/components/MyComs/Button/index.d.ts +3 -0
- package/dist/src/components/MyComs/Button/index.vue.d.ts +93 -0
- package/dist/src/components/MyComs/Dialog/index.d.ts +3 -0
- package/dist/src/components/MyComs/Dialog/index.vue.d.ts +65 -0
- package/dist/src/components/MyComs/Divider/index.d.ts +3 -0
- package/dist/src/components/MyComs/Divider/index.vue.d.ts +53 -0
- package/dist/src/components/MyComs/DropdownSelect/dropdownSelect.d.ts +10 -0
- package/dist/src/components/MyComs/DropdownSelect/index.d.ts +4 -0
- package/dist/src/components/MyComs/DropdownSelect/index.vue.d.ts +26 -0
- package/dist/src/components/MyComs/DropdownSelect/type.d.ts +13 -0
- package/dist/src/components/MyComs/DropdownWithBadge/dropdownWithBadge.d.ts +5 -0
- package/dist/src/components/MyComs/DropdownWithBadge/index.d.ts +4 -0
- package/dist/src/components/MyComs/DropdownWithBadge/index.vue.d.ts +26 -0
- package/dist/src/components/MyComs/DropdownWithBadge/type.d.ts +9 -0
- package/dist/src/components/MyComs/FilterDrawer/hasBadge.d.ts +8 -0
- package/dist/src/components/MyComs/FilterDrawer/index.d.ts +5 -0
- package/dist/src/components/MyComs/FilterDrawer/index.vue.d.ts +26 -0
- package/dist/src/components/MyComs/FilterDrawer/type.d.ts +8 -0
- package/dist/src/components/MyComs/FilterDrawer/useFilterDrawer.d.ts +10 -0
- package/dist/src/components/MyComs/InfiniteScroll/index.d.ts +3 -0
- package/dist/src/components/MyComs/InfiniteScroll/index.vue.d.ts +65 -0
- package/dist/src/components/MyComs/Popup/index.d.ts +3 -0
- package/dist/src/components/MyComs/Popup/index.vue.d.ts +119 -0
- package/dist/src/components/MyComs/PullRefresh/index.d.ts +3 -0
- package/dist/src/components/MyComs/PullRefresh/index.vue.d.ts +117 -0
- package/dist/src/components/MyComs/Swiper/index.d.ts +3 -0
- package/dist/src/components/MyComs/Swiper/index.vue.d.ts +79 -0
- package/dist/src/components/MyComs/Toast/index.d.ts +3 -0
- package/dist/src/components/MyComs/Toast/index.vue.d.ts +108 -0
- package/dist/src/components/MyComs/index.d.ts +20 -0
- package/package.json +218 -0
- package/src/components/MyComs/Button/README.md +235 -0
- package/src/components/MyComs/Button/index.ts +3 -0
- package/src/components/MyComs/Button/index.vue +413 -0
- package/src/components/MyComs/Dialog/README.md +160 -0
- package/src/components/MyComs/Dialog/index.ts +2 -0
- package/src/components/MyComs/Dialog/index.vue +275 -0
- package/src/components/MyComs/Divider/README.md +0 -0
- package/src/components/MyComs/Divider/index.ts +2 -0
- package/src/components/MyComs/Divider/index.vue +106 -0
- package/src/components/MyComs/DropdownSelect/README.md +112 -0
- package/src/components/MyComs/DropdownSelect/dropdownSelect.less +75 -0
- package/src/components/MyComs/DropdownSelect/dropdownSelect.ts +59 -0
- package/src/components/MyComs/DropdownSelect/index.ts +4 -0
- package/src/components/MyComs/DropdownSelect/index.vue +88 -0
- package/src/components/MyComs/DropdownSelect/type.ts +15 -0
- package/src/components/MyComs/DropdownWithBadge/README.md +77 -0
- package/src/components/MyComs/DropdownWithBadge/dropdownWithBadge.less +11 -0
- package/src/components/MyComs/DropdownWithBadge/dropdownWithBadge.ts +10 -0
- package/src/components/MyComs/DropdownWithBadge/index.ts +4 -0
- package/src/components/MyComs/DropdownWithBadge/index.vue +39 -0
- package/src/components/MyComs/DropdownWithBadge/type.ts +12 -0
- package/src/components/MyComs/FilterDrawer/filterDrawer.less +117 -0
- package/src/components/MyComs/FilterDrawer/hasBadge.ts +41 -0
- package/src/components/MyComs/FilterDrawer/index.ts +5 -0
- package/src/components/MyComs/FilterDrawer/index.vue +53 -0
- package/src/components/MyComs/FilterDrawer/type.ts +9 -0
- package/src/components/MyComs/FilterDrawer/useFilterDrawer.ts +38 -0
- package/src/components/MyComs/InfiniteScroll/index.ts +2 -0
- package/src/components/MyComs/InfiniteScroll/index.vue +171 -0
- package/src/components/MyComs/Popup/README.md +684 -0
- package/src/components/MyComs/Popup/index.ts +2 -0
- package/src/components/MyComs/Popup/index.vue +835 -0
- package/src/components/MyComs/PullRefresh/README.md +600 -0
- package/src/components/MyComs/PullRefresh/index.ts +2 -0
- package/src/components/MyComs/PullRefresh/index.vue +599 -0
- package/src/components/MyComs/Swiper/README.md +202 -0
- package/src/components/MyComs/Swiper/index.ts +2 -0
- package/src/components/MyComs/Swiper/index.vue +245 -0
- package/src/components/MyComs/Toast/README.md +604 -0
- package/src/components/MyComs/Toast/index.ts +2 -0
- package/src/components/MyComs/Toast/index.vue +372 -0
- package/src/components/MyComs/index.ts +33 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view ref="containerRef" class="infinite-scroll-container" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @scroll="handleScroll">
|
|
3
|
+
<!-- 下拉刷新指示器 -->
|
|
4
|
+
<view
|
|
5
|
+
class="refresh-indicator"
|
|
6
|
+
:style="{ height: `${pullDistance}px` }"
|
|
7
|
+
>
|
|
8
|
+
<view v-if="refreshStatus !== 'normal' && (refreshStatus === 'refreshing' || pullDistance > 0)" class="refresh-content">
|
|
9
|
+
<view class="refresh-icon">
|
|
10
|
+
<!-- 加载中图标 -->
|
|
11
|
+
<text v-if="props.refreshLoading" class="refresh-loading">●</text>
|
|
12
|
+
<!-- 下拉箭头图标 -->
|
|
13
|
+
<text v-else class="refresh-arrow">↓</text>
|
|
14
|
+
</view>
|
|
15
|
+
<text class="refresh-text">
|
|
16
|
+
<template v-if="props.refreshLoading">{{ props.refreshingText }}</template>
|
|
17
|
+
<template v-else-if="refreshStatus === 'triggered'">{{ props.releaseText }}</template>
|
|
18
|
+
<template v-else>{{ props.refreshText }}</template>
|
|
19
|
+
</text>
|
|
20
|
+
</view>
|
|
21
|
+
</view>
|
|
22
|
+
|
|
23
|
+
<!-- 内容区域 -->
|
|
24
|
+
<view class="infinite-scroll-content">
|
|
25
|
+
<slot />
|
|
26
|
+
</view>
|
|
27
|
+
|
|
28
|
+
<!-- 加载指示器 -->
|
|
29
|
+
<view class="infinite-scroll-indicator">
|
|
30
|
+
<!-- 加载中 -->
|
|
31
|
+
<view v-if="loading" class="infinite-scroll-loading">
|
|
32
|
+
<view class="loading-spinner">
|
|
33
|
+
<text class="loading-icon">●</text>
|
|
34
|
+
</view>
|
|
35
|
+
<text class="loading-text">{{ loadingText }}</text>
|
|
36
|
+
</view>
|
|
37
|
+
|
|
38
|
+
<!-- 加载失败 -->
|
|
39
|
+
<view v-else-if="error" class="infinite-scroll-error">
|
|
40
|
+
<text class="error-icon">×</text>
|
|
41
|
+
<text class="error-text">{{ errorText }}</text>
|
|
42
|
+
<view class="retry-btn" @click="onRetry">
|
|
43
|
+
{{ retryText }}
|
|
44
|
+
</view>
|
|
45
|
+
</view>
|
|
46
|
+
|
|
47
|
+
<!-- 无更多数据 -->
|
|
48
|
+
<view v-else-if="!hasMore" class="infinite-scroll-no-more">
|
|
49
|
+
<text class="no-more-text">{{ noMoreText }}</text>
|
|
50
|
+
</view>
|
|
51
|
+
|
|
52
|
+
<!-- 隐藏的触发区域 -->
|
|
53
|
+
<view
|
|
54
|
+
v-else
|
|
55
|
+
class="infinite-scroll-trigger"
|
|
56
|
+
:style="{ height: `0px` }"
|
|
57
|
+
/>
|
|
58
|
+
</view>
|
|
59
|
+
</view>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { nextTick, onMounted, onUnmounted, ref, watch, withDefaults } from 'vue'
|
|
64
|
+
|
|
65
|
+
// Props
|
|
66
|
+
interface Props {
|
|
67
|
+
// 加载状态(无限滚动)
|
|
68
|
+
loading?: boolean
|
|
69
|
+
// 下拉刷新加载状态
|
|
70
|
+
refreshLoading?: boolean
|
|
71
|
+
// 是否还有更多数据
|
|
72
|
+
hasMore?: boolean
|
|
73
|
+
// 加载失败状态
|
|
74
|
+
error?: boolean
|
|
75
|
+
// 触发无限滚动的阈值距离(像素)
|
|
76
|
+
threshold?: number
|
|
77
|
+
// 下拉刷新触发阈值(像素)
|
|
78
|
+
refreshThreshold?: number
|
|
79
|
+
// 节流时间(毫秒)
|
|
80
|
+
throttleTime?: number
|
|
81
|
+
// 加载中提示文本
|
|
82
|
+
loadingText?: string
|
|
83
|
+
// 加载失败提示文本
|
|
84
|
+
errorText?: string
|
|
85
|
+
// 重试按钮文本
|
|
86
|
+
retryText?: string
|
|
87
|
+
// 无更多数据提示文本
|
|
88
|
+
noMoreText?: string
|
|
89
|
+
// 下拉刷新提示文本
|
|
90
|
+
refreshText?: string
|
|
91
|
+
// 释放可刷新提示文本
|
|
92
|
+
releaseText?: string
|
|
93
|
+
// 正在刷新提示文本
|
|
94
|
+
refreshingText?: string
|
|
95
|
+
// 刷新成功提示文本
|
|
96
|
+
refreshSuccessText?: string
|
|
97
|
+
// 刷新失败提示文本
|
|
98
|
+
refreshErrorText?: string
|
|
99
|
+
// 是否使用窗口作为滚动容器
|
|
100
|
+
useWindowScroll?: boolean
|
|
101
|
+
// 是否启用无限滚动
|
|
102
|
+
enableInfiniteScroll?: boolean
|
|
103
|
+
// 是否启用下拉刷新
|
|
104
|
+
enableRefresh?: boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 设置默认属性
|
|
108
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
109
|
+
loading: false,
|
|
110
|
+
refreshLoading: false,
|
|
111
|
+
hasMore: true,
|
|
112
|
+
error: false,
|
|
113
|
+
threshold: 30,
|
|
114
|
+
refreshThreshold: 40,
|
|
115
|
+
throttleTime: 200,
|
|
116
|
+
loadingText: '加载中...',
|
|
117
|
+
errorText: '加载失败',
|
|
118
|
+
retryText: '点击重试',
|
|
119
|
+
noMoreText: '没有更多数据了',
|
|
120
|
+
refreshText: '下拉刷新',
|
|
121
|
+
releaseText: '释放可刷新',
|
|
122
|
+
refreshingText: '正在刷新...',
|
|
123
|
+
refreshSuccessText: '刷新成功',
|
|
124
|
+
refreshErrorText: '刷新失败',
|
|
125
|
+
useWindowScroll: false,
|
|
126
|
+
enableInfiniteScroll: false,
|
|
127
|
+
enableRefresh: true,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Emits
|
|
131
|
+
const emit = defineEmits<{
|
|
132
|
+
(e: 'loadMore'): void
|
|
133
|
+
(e: 'retry'): void
|
|
134
|
+
(e: 'refresh'): void
|
|
135
|
+
}>()
|
|
136
|
+
|
|
137
|
+
// Refs
|
|
138
|
+
const containerRef = ref<any>(null)
|
|
139
|
+
let scrollContainer: any = null
|
|
140
|
+
let lastScrollTime = 0
|
|
141
|
+
|
|
142
|
+
// 下拉刷新相关状态
|
|
143
|
+
const pullDistance = ref(0) // 当前下拉距离
|
|
144
|
+
const startY = ref(0) // 触摸起始Y坐标
|
|
145
|
+
const isPulling = ref(false) // 是否正在下拉
|
|
146
|
+
const isRefreshing = ref(false) // 是否正在刷新
|
|
147
|
+
const refreshStatus = ref('normal') // 刷新状态:normal(正常)、pulling(下拉中)、triggered(已触发刷新)、refreshing(刷新中)
|
|
148
|
+
|
|
149
|
+
// Methods
|
|
150
|
+
/**
|
|
151
|
+
* 获取滚动容器
|
|
152
|
+
* @returns {any} - 滚动容器对象
|
|
153
|
+
*/
|
|
154
|
+
function getScrollContainer() {
|
|
155
|
+
if (props.useWindowScroll) {
|
|
156
|
+
// #ifdef H5
|
|
157
|
+
return window
|
|
158
|
+
// #endif
|
|
159
|
+
// 其他平台返回null或使用默认容器
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
return containerRef.value
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 节流函数,限制函数的执行频率
|
|
167
|
+
* @param {Function} fn - 要执行的函数
|
|
168
|
+
* @param {number} delay - 延迟时间(毫秒)
|
|
169
|
+
* @returns {Function} - 节流后的函数
|
|
170
|
+
*/
|
|
171
|
+
function throttle(fn, delay) {
|
|
172
|
+
return (...args) => {
|
|
173
|
+
const now = Date.now()
|
|
174
|
+
if (now - lastScrollTime >= delay) {
|
|
175
|
+
lastScrollTime = now
|
|
176
|
+
fn.apply(null, args)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 检查是否需要加载更多数据
|
|
183
|
+
*/
|
|
184
|
+
function checkLoadMore() {
|
|
185
|
+
// 如果无限滚动未启用,直接返回
|
|
186
|
+
if (!props.enableInfiniteScroll) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (props.loading || !props.hasMore || props.error) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let scrollTop = 0
|
|
195
|
+
let scrollHeight = 0
|
|
196
|
+
let clientHeight = 0
|
|
197
|
+
|
|
198
|
+
if (props.useWindowScroll) {
|
|
199
|
+
// #ifdef H5
|
|
200
|
+
// 使用窗口作为滚动容器
|
|
201
|
+
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
|
|
202
|
+
scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
|
|
203
|
+
clientHeight = window.innerHeight
|
|
204
|
+
// #endif
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// 使用组件容器作为滚动容器
|
|
208
|
+
if (!containerRef.value)
|
|
209
|
+
return
|
|
210
|
+
// 确保容器具有滚动相关属性
|
|
211
|
+
if (typeof containerRef.value.scrollTop !== 'number'
|
|
212
|
+
|| typeof containerRef.value.scrollHeight !== 'number'
|
|
213
|
+
|| typeof containerRef.value.clientHeight !== 'number') {
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
scrollTop = containerRef.value.scrollTop
|
|
217
|
+
scrollHeight = containerRef.value.scrollHeight
|
|
218
|
+
clientHeight = containerRef.value.clientHeight
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 检查是否滚动到了阈值范围内
|
|
222
|
+
// 如果内容高度小于等于容器高度,说明内容不足一屏,应该直接触发加载更多
|
|
223
|
+
if ((props.useWindowScroll ? scrollTop > 0 : true) && (scrollHeight <= clientHeight || scrollHeight - scrollTop - clientHeight <= props.threshold)) {
|
|
224
|
+
// 触发加载更多事件
|
|
225
|
+
emit('loadMore')
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 触发重试加载事件
|
|
231
|
+
*/
|
|
232
|
+
function onRetry() {
|
|
233
|
+
emit('retry')
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// 触摸开始事件处理
|
|
237
|
+
/**
|
|
238
|
+
* 处理触摸开始事件,用于初始化下拉刷新
|
|
239
|
+
* @param {TouchEvent} e - 触摸事件对象
|
|
240
|
+
*/
|
|
241
|
+
function handleTouchStart(e) {
|
|
242
|
+
// 如果未启用下拉刷新或正在刷新,则不处理
|
|
243
|
+
if (!props.enableRefresh || props.refreshLoading) {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 如果滚动容器不在顶部,则不处理下拉刷新
|
|
248
|
+
let scrollTop = 0
|
|
249
|
+
if (props.useWindowScroll) {
|
|
250
|
+
// #ifdef H5
|
|
251
|
+
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
|
|
252
|
+
// #endif
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
scrollTop = containerRef.value?.scrollTop || 0
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (scrollTop > 0) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 记录触摸起始位置
|
|
263
|
+
startY.value = e.touches[0].clientY
|
|
264
|
+
isPulling.value = true
|
|
265
|
+
refreshStatus.value = 'pulling'
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 触摸移动事件处理
|
|
269
|
+
/**
|
|
270
|
+
* 处理触摸移动事件,用于计算下拉距离和更新刷新状态
|
|
271
|
+
* @param {TouchEvent} e - 触摸事件对象
|
|
272
|
+
*/
|
|
273
|
+
function handleTouchMove(e) {
|
|
274
|
+
if (!isPulling.value || props.refreshLoading) {
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 计算下拉距离
|
|
279
|
+
const currentY = e.touches[0].clientY
|
|
280
|
+
const distance = currentY - startY.value
|
|
281
|
+
|
|
282
|
+
// 只有向下拉才处理
|
|
283
|
+
if (distance <= 0) {
|
|
284
|
+
// 如果拉到顶部以下,恢复正常状态
|
|
285
|
+
if (pullDistance.value > 0) {
|
|
286
|
+
pullDistance.value = 0
|
|
287
|
+
refreshStatus.value = 'normal'
|
|
288
|
+
}
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 调整下拉距离计算,确保有足够空间实现垂直居中
|
|
293
|
+
pullDistance.value = Math.min(distance * 0.5, props.refreshThreshold * 1.5)
|
|
294
|
+
|
|
295
|
+
// 更新刷新状态
|
|
296
|
+
if (pullDistance.value >= props.refreshThreshold) {
|
|
297
|
+
refreshStatus.value = 'triggered'
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
refreshStatus.value = 'pulling'
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 阻止默认滚动行为
|
|
304
|
+
e.preventDefault()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 触摸结束事件处理
|
|
308
|
+
/**
|
|
309
|
+
* 处理触摸结束事件,用于判断是否触发刷新
|
|
310
|
+
*/
|
|
311
|
+
function handleTouchEnd() {
|
|
312
|
+
if (!isPulling.value) {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
isPulling.value = false
|
|
317
|
+
|
|
318
|
+
// 如果下拉距离达到阈值,触发刷新
|
|
319
|
+
if (pullDistance.value >= props.refreshThreshold && !props.refreshLoading) {
|
|
320
|
+
isRefreshing.value = true
|
|
321
|
+
refreshStatus.value = 'refreshing'
|
|
322
|
+
emit('refresh')
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// 否则恢复原状
|
|
326
|
+
pullDistance.value = 0
|
|
327
|
+
refreshStatus.value = 'normal'
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 处理滚动事件,检查是否需要加载更多(节流处理)
|
|
333
|
+
*/
|
|
334
|
+
const handleScroll = throttle(() => {
|
|
335
|
+
checkLoadMore()
|
|
336
|
+
}, props.throttleTime)
|
|
337
|
+
|
|
338
|
+
// 添加滚动事件监听
|
|
339
|
+
/**
|
|
340
|
+
* 添加滚动事件监听
|
|
341
|
+
*/
|
|
342
|
+
function addScrollListener() {
|
|
343
|
+
// 使用nextTick确保containerRef已初始化
|
|
344
|
+
nextTick(() => {
|
|
345
|
+
scrollContainer = getScrollContainer()
|
|
346
|
+
if (scrollContainer && typeof scrollContainer.addEventListener === 'function') {
|
|
347
|
+
scrollContainer.addEventListener('scroll', handleScroll, { passive: true })
|
|
348
|
+
// 初始检查一次
|
|
349
|
+
checkLoadMore()
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 移除滚动事件监听
|
|
355
|
+
/**
|
|
356
|
+
* 移除滚动事件监听
|
|
357
|
+
*/
|
|
358
|
+
function removeScrollListener() {
|
|
359
|
+
if (scrollContainer && typeof scrollContainer.removeEventListener === 'function') {
|
|
360
|
+
scrollContainer.removeEventListener('scroll', handleScroll)
|
|
361
|
+
scrollContainer = null
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 监听容器大小变化
|
|
366
|
+
/**
|
|
367
|
+
* 处理容器大小变化事件,检查是否需要加载更多(节流处理)
|
|
368
|
+
*/
|
|
369
|
+
const handleResize = throttle(() => {
|
|
370
|
+
checkLoadMore()
|
|
371
|
+
}, props.throttleTime)
|
|
372
|
+
|
|
373
|
+
// 监听加载状态变化
|
|
374
|
+
watch(() => props.loading, (newVal) => {
|
|
375
|
+
if (!newVal) {
|
|
376
|
+
// 加载完成后,检查是否还需要继续加载
|
|
377
|
+
// 使用nextTick确保DOM已更新
|
|
378
|
+
nextTick(() => {
|
|
379
|
+
checkLoadMore()
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
// 监听错误状态变化
|
|
385
|
+
watch(() => props.error, (newVal) => {
|
|
386
|
+
if (!newVal) {
|
|
387
|
+
// 错误状态清除后,检查是否还需要继续加载
|
|
388
|
+
// 使用nextTick确保DOM已更新
|
|
389
|
+
nextTick(() => {
|
|
390
|
+
checkLoadMore()
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// 监听下拉刷新加载状态变化
|
|
396
|
+
watch(() => props.refreshLoading, (newVal) => {
|
|
397
|
+
isRefreshing.value = newVal
|
|
398
|
+
|
|
399
|
+
if (newVal) {
|
|
400
|
+
refreshStatus.value = 'refreshing'
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
// 刷新完成后,先重置pullDistance开始回弹
|
|
404
|
+
pullDistance.value = 0
|
|
405
|
+
// 保持refreshStatus为'refreshing'直到回弹动画完成
|
|
406
|
+
// 然后再将状态设置为'normal'
|
|
407
|
+
// 使用标准setTimeout确保跨平台兼容
|
|
408
|
+
setTimeout(() => {
|
|
409
|
+
refreshStatus.value = 'normal'
|
|
410
|
+
}, 300)
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
// 生命周期
|
|
415
|
+
onMounted(() => {
|
|
416
|
+
// 添加滚动事件监听
|
|
417
|
+
addScrollListener()
|
|
418
|
+
|
|
419
|
+
// 添加窗口大小变化监听
|
|
420
|
+
// #ifdef H5
|
|
421
|
+
window.addEventListener('resize', handleResize, { passive: true })
|
|
422
|
+
// #endif
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
onUnmounted(() => {
|
|
426
|
+
// 移除滚动事件监听
|
|
427
|
+
removeScrollListener()
|
|
428
|
+
|
|
429
|
+
// 移除窗口大小变化监听
|
|
430
|
+
// #ifdef H5
|
|
431
|
+
window.removeEventListener('resize', handleResize)
|
|
432
|
+
// #endif
|
|
433
|
+
})
|
|
434
|
+
</script>
|
|
435
|
+
|
|
436
|
+
<style scoped>
|
|
437
|
+
.infinite-scroll-container {
|
|
438
|
+
position: relative;
|
|
439
|
+
width: 100%;
|
|
440
|
+
height: 100%;
|
|
441
|
+
overflow-y: auto;
|
|
442
|
+
overflow-x: hidden;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.infinite-scroll-content {
|
|
446
|
+
width: 100%;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.infinite-scroll-indicator {
|
|
450
|
+
width: 100%;
|
|
451
|
+
display: flex;
|
|
452
|
+
justify-content: center;
|
|
453
|
+
align-items: center;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.infinite-scroll-loading {
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
justify-content: center;
|
|
460
|
+
gap: 20rpx;
|
|
461
|
+
color: #666;
|
|
462
|
+
font-size: 28rpx;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.loading-spinner {
|
|
466
|
+
animation: spin 0.8s linear infinite;
|
|
467
|
+
color: #409eff;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.loading-icon {
|
|
471
|
+
font-size: 40rpx;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
@keyframes spin {
|
|
475
|
+
0% {
|
|
476
|
+
transform: rotate(0deg);
|
|
477
|
+
}
|
|
478
|
+
100% {
|
|
479
|
+
transform: rotate(360deg);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.loading-text {
|
|
484
|
+
font-size: 28rpx;
|
|
485
|
+
color: #666;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.infinite-scroll-error {
|
|
489
|
+
display: flex;
|
|
490
|
+
align-items: center;
|
|
491
|
+
justify-content: center;
|
|
492
|
+
gap: 20rpx;
|
|
493
|
+
color: #f56c6c;
|
|
494
|
+
font-size: 28rpx;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.error-icon {
|
|
498
|
+
font-size: 40rpx;
|
|
499
|
+
color: #f56c6c;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.error-text {
|
|
503
|
+
font-size: 28rpx;
|
|
504
|
+
color: #f56c6c;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.retry-btn {
|
|
508
|
+
padding: 8rpx 24rpx;
|
|
509
|
+
background-color: #409eff;
|
|
510
|
+
color: white;
|
|
511
|
+
border: none;
|
|
512
|
+
border-radius: 8rpx;
|
|
513
|
+
font-size: 24rpx;
|
|
514
|
+
cursor: pointer;
|
|
515
|
+
transition: background-color 0.3s ease;
|
|
516
|
+
box-sizing: border-box;
|
|
517
|
+
display: flex;
|
|
518
|
+
align-items: center;
|
|
519
|
+
justify-content: center;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.retry-btn:hover {
|
|
523
|
+
background-color: #66b1ff;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.infinite-scroll-no-more {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
justify-content: center;
|
|
530
|
+
color: #999;
|
|
531
|
+
font-size: 28rpx;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.no-more-text {
|
|
535
|
+
font-size: 28rpx;
|
|
536
|
+
color: #999;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.infinite-scroll-trigger {
|
|
540
|
+
width: 100%;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* 下拉刷新样式 */
|
|
544
|
+
.refresh-indicator {
|
|
545
|
+
width: 100%;
|
|
546
|
+
height: 0;
|
|
547
|
+
overflow: hidden;
|
|
548
|
+
transition: height 0.3s ease;
|
|
549
|
+
position: relative;
|
|
550
|
+
display: flex;
|
|
551
|
+
align-items: center;
|
|
552
|
+
justify-content: center;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.refresh-content {
|
|
556
|
+
display: flex;
|
|
557
|
+
flex-direction: row;
|
|
558
|
+
align-items: center;
|
|
559
|
+
justify-content: center;
|
|
560
|
+
width: 100%;
|
|
561
|
+
padding: 16rpx 0;
|
|
562
|
+
color: white;
|
|
563
|
+
font-size: 26rpx;
|
|
564
|
+
align-self: center;
|
|
565
|
+
flex-shrink: 0;
|
|
566
|
+
margin-top: 10rpx;
|
|
567
|
+
font-weight: 500;
|
|
568
|
+
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.3);
|
|
569
|
+
border-radius: 20rpx;
|
|
570
|
+
margin: 8rpx;
|
|
571
|
+
backdrop-filter: blur(10rpx);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.refresh-icon {
|
|
575
|
+
margin-right: 10rpx;
|
|
576
|
+
margin-bottom: 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.refresh-icon svg {
|
|
580
|
+
transition: transform 0.3s ease;
|
|
581
|
+
color: #409eff;
|
|
582
|
+
width: 36rpx;
|
|
583
|
+
height: 36rpx;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.refresh-status-triggered .refresh-icon svg,
|
|
587
|
+
.refresh-status-refreshing .refresh-icon svg {
|
|
588
|
+
transform: rotate(0deg);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.refresh-status-refreshing .refresh-icon svg {
|
|
592
|
+
animation: spin 0.8s linear infinite;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.refresh-text {
|
|
596
|
+
font-size: 28rpx;
|
|
597
|
+
color: white;
|
|
598
|
+
}
|
|
599
|
+
</style>
|