vue_zhongyou 1.0.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.
@@ -0,0 +1,243 @@
1
+ <template>
2
+ <div class="mobile-list" ref="listContainer">
3
+ <!-- 列表内容 -->
4
+ <div class="list-content">
5
+ <slot
6
+ :items="displayItems"
7
+ :loading="loading"
8
+ :finished="finished"
9
+ ></slot>
10
+ </div>
11
+
12
+ <!-- 加载状态 -->
13
+ <div v-if="loading && !finished" class="loading-indicator">
14
+ <el-spinner size="small" />
15
+ <span class="loading-text">加载中...</span>
16
+ </div>
17
+
18
+ <!-- 无更多数据提示 -->
19
+ <div v-if="finished && displayItems.length > 0" class="no-more-data">
20
+ <span>已无更多数据</span>
21
+ </div>
22
+
23
+ <!-- 空状态 -->
24
+ <div v-if="!loading && displayItems.length === 0" class="empty-state">
25
+ <slot name="empty">
26
+ <div class="empty-content">
27
+ <el-icon size="40"><el-icon-document /></el-icon>
28
+ <p>暂无数据</p>
29
+ </div>
30
+ </slot>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup>
36
+ import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
37
+ // import { ElSpinner, ElIcon } from 'element-plus'
38
+ import { Document as ElIconDocument } from '@element-plus/icons-vue'
39
+
40
+ // 定义 props
41
+ const props = defineProps({
42
+ // 数据列表
43
+ items: {
44
+ type: Array,
45
+ default: () => []
46
+ },
47
+ // 每页数据量
48
+ pageSize: {
49
+ type: Number,
50
+ default: 10
51
+ },
52
+ // 总数据量
53
+ total: {
54
+ type: Number,
55
+ default: 0
56
+ },
57
+ // 是否正在加载
58
+ loading: {
59
+ type: Boolean,
60
+ default: false
61
+ },
62
+ // 是否禁用上拉加载
63
+ disabled: {
64
+ type: Boolean,
65
+ default: false
66
+ },
67
+ // 触发加载更多的距离阈值(px)
68
+ offset: {
69
+ type: Number,
70
+ default: 50
71
+ },
72
+ // 是否立即加载第一页
73
+ immediate: {
74
+ type: Boolean,
75
+ default: true
76
+ }
77
+ })
78
+
79
+ // 定义 emits
80
+ const emit = defineEmits([
81
+ 'load-more',
82
+ 'update:loading'
83
+ ])
84
+
85
+ // 响应式数据
86
+ const listContainer = ref(null)
87
+ const currentPage = ref(1)
88
+ const finished = ref(false)
89
+
90
+ // 计算显示的数据
91
+ const displayItems = computed(() => {
92
+ const start = 0
93
+ const end = currentPage.value * props.pageSize
94
+ return props.items.slice(start, end)
95
+ })
96
+
97
+ // 检查是否已经加载完所有数据
98
+ const checkFinished = () => {
99
+ finished.value = displayItems.value.length >= props.total
100
+ }
101
+
102
+ // 处理滚动事件
103
+ const handleScroll = () => {
104
+ if (!listContainer.value || props.loading || props.disabled || finished.value) {
105
+ return
106
+ }
107
+
108
+ const container = listContainer.value
109
+ const scrollTop = container.scrollTop
110
+ const scrollHeight = container.scrollHeight
111
+ const clientHeight = container.clientHeight
112
+
113
+ // 当滚动到底部时触发加载更多
114
+ if (scrollTop + clientHeight >= scrollHeight - props.offset) {
115
+ loadMore()
116
+ }
117
+ }
118
+
119
+ // 加载更多数据
120
+ const loadMore = () => {
121
+ if (finished.value || props.loading) return
122
+
123
+ currentPage.value++
124
+ checkFinished()
125
+
126
+ // 如果还有更多数据,则触发 load-more 事件
127
+ if (!finished.value) {
128
+ emit('load-more', currentPage.value)
129
+ }
130
+ }
131
+
132
+ // 重置列表状态
133
+ const reset = (silent = false) => {
134
+ currentPage.value = 1
135
+ finished.value = false
136
+ checkFinished()
137
+
138
+ // 如果不是静默重置,则触发第一页加载
139
+ if (!silent && props.immediate) {
140
+ emit('load-more', 1)
141
+ }
142
+ }
143
+
144
+ // 手动触发刷新
145
+ const refresh = () => {
146
+ reset()
147
+ }
148
+
149
+ // 监听数据变化
150
+ watch(() => props.items, () => {
151
+ checkFinished()
152
+ }, { deep: true })
153
+
154
+ // 监听总数据量变化
155
+ watch(() => props.total, () => {
156
+ checkFinished()
157
+ })
158
+
159
+ // 添加滚动事件监听
160
+ onMounted(() => {
161
+ if (listContainer.value) {
162
+ listContainer.value.addEventListener('scroll', handleScroll)
163
+ }
164
+ checkFinished()
165
+
166
+ // 如果设置了立即加载且有数据需求,则触发第一页加载
167
+ if (props.immediate && props.total > 0 && props.items.length === 0) {
168
+ emit('load-more', 1)
169
+ }
170
+ })
171
+
172
+ // 移除滚动事件监听
173
+ onUnmounted(() => {
174
+ if (listContainer.value) {
175
+ listContainer.value.removeEventListener('scroll', handleScroll)
176
+ }
177
+ })
178
+
179
+ // 暴露方法给父组件
180
+ defineExpose({
181
+ reset,
182
+ refresh,
183
+ loadMore
184
+ })
185
+ </script>
186
+
187
+ <style scoped>
188
+ .mobile-list {
189
+ width: 100%;
190
+ height: 100%;
191
+ overflow-y: auto;
192
+ -webkit-overflow-scrolling: touch;
193
+ position: relative;
194
+ }
195
+
196
+ .list-content {
197
+ min-height: 100%;
198
+ }
199
+
200
+ .loading-indicator {
201
+ display: flex;
202
+ justify-content: center;
203
+ align-items: center;
204
+ padding: 16px;
205
+ color: #606266;
206
+ }
207
+
208
+ .loading-text {
209
+ margin-left: 8px;
210
+ font-size: 14px;
211
+ }
212
+
213
+ .no-more-data {
214
+ text-align: center;
215
+ padding: 16px;
216
+ color: #909399;
217
+ font-size: 14px;
218
+ }
219
+
220
+ .empty-state {
221
+ display: flex;
222
+ justify-content: center;
223
+ align-items: center;
224
+ height: 200px;
225
+ color: #909399;
226
+ }
227
+
228
+ .empty-content {
229
+ text-align: center;
230
+ }
231
+
232
+ .empty-content p {
233
+ margin-top: 12px;
234
+ font-size: 14px;
235
+ }
236
+
237
+ /* 移动端适配 */
238
+ @media (max-width: 768px) {
239
+ .mobile-list {
240
+ height: calc(100vh - 50px); /* 预留一些空间给其他元素 */
241
+ }
242
+ }
243
+ </style>