更新列表刷新组件

This commit is contained in:
icssoa
2025-08-11 14:40:25 +08:00
parent 46bc67562e
commit dda78b9b07

View File

@@ -47,14 +47,16 @@
height: refresherThreshold + 'px'
}"
>
<cl-loading
v-if="refresherStatus === 'refreshing'"
:size="28"
:pt="{
className: 'mr-2'
}"
></cl-loading>
<cl-text> {{ refresherText }} </cl-text>
<slot name="refresher" :status="refresherStatus" :text="refresherText">
<cl-loading
v-if="refresherStatus === 'refreshing'"
:size="28"
:pt="{
className: 'mr-2'
}"
></cl-loading>
<cl-text> {{ refresherText }} </cl-text>
</slot>
</view>
<view
@@ -122,6 +124,7 @@
</view>
<cl-empty v-if="noData" :fixed="false"></cl-empty>
<cl-back-top :top="scrollTop" v-if="showBackTop" @tap="scrollToTop"></cl-back-top>
</scroll-view>
<view
@@ -142,30 +145,16 @@
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, onMounted, ref, watch, type PropType } from "vue";
import type { ClListViewItem, PassThroughProps } from "../../types";
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch, type PropType } from "vue";
import type {
ClListViewItem,
ClListViewGroup,
ClListViewVirtualItem,
PassThroughProps,
ClListViewRefresherStatus
} from "../../types";
import { isApp, isDark, isEmpty, parseClass, parsePt } from "@/cool";
// 定义虚拟列表项
type VirtualItem = {
// 每一项的唯一标识符,用于v-for的key
key: string;
// 项目类型:header表示分组头部,item表示列表项
type: "header" | "item";
// 在整个列表中的索引号
index: number;
// 该项距离列表顶部的像素距离
top: number;
// 该项的高度,header和item可以不同
height: number;
// 该项的具体数据
data: ClListViewItem;
};
type Group = {
index: string;
children: ClListViewItem[];
};
import { t } from "@/locale";
defineOptions({
name: "cl-list-view"
@@ -177,11 +166,18 @@ defineSlots<{
// 分组头部插槽
header(props: { index: string }): any;
// 列表项插槽
item(props: { data: ClListViewItem; item: VirtualItem; value: any | null; index: number }): any;
item(props: {
data: ClListViewItem;
item: ClListViewVirtualItem;
value: any | null;
index: number;
}): any;
// 底部插槽
bottom(): any;
// 索引插槽
index(props: { index: string }): any;
// 下拉刷新插槽
refresher(props: { status: ClListViewRefresherStatus; text: string }): any;
}>();
const props = defineProps({
@@ -248,7 +244,7 @@ const props = defineProps({
// 下拉刷新触发距离,相当于下拉内容高度
refresherThreshold: {
type: Number,
default: 45
default: 50
},
// 下拉刷新区域背景色
refresherBackground: {
@@ -258,17 +254,22 @@ const props = defineProps({
// 下拉刷新默认文案
refresherDefaultText: {
type: String,
default: "下拉刷新"
default: () => t("下拉刷新")
},
// 释放刷新文案
refresherPullingText: {
type: String,
default: "释放立即刷新"
default: () => t("释放立即刷新")
},
// 正在刷新文案
refresherRefreshingText: {
type: String,
default: "加载中"
default: () => t("加载中")
},
// 是否显示回到顶部按钮
showBackTop: {
type: Boolean,
default: true
}
});
@@ -317,9 +318,9 @@ const hasIndex = computed(() => {
});
// 计算属性:将原始数据按索引分组
const data = computed<Group[]>(() => {
const data = computed<ClListViewGroup[]>(() => {
// 初始化分组数组
const group: Group[] = [];
const group: ClListViewGroup[] = [];
// 遍历原始数据,按index字段进行分组
props.data.forEach((item) => {
@@ -334,7 +335,7 @@ const data = computed<Group[]>(() => {
group.push({
index: item.index ?? "",
children: [item]
} as Group);
} as ClListViewGroup);
}
});
@@ -347,9 +348,9 @@ const indexList = computed<string[]>(() => {
});
// 计算属性:将分组数据扁平化为虚拟列表项数组
const virtualItems = computed<VirtualItem[]>(() => {
const virtualItems = computed<ClListViewVirtualItem[]>(() => {
// 初始化虚拟列表数组
const items: VirtualItem[] = [];
const items: ClListViewVirtualItem[] = [];
// 初始化顶部位置,考虑预留空间
let top = props.topHeight;
@@ -415,7 +416,7 @@ const targetScrollTop = ref(0);
const scrollerHeight = ref(0);
// 计算属性:获取当前可见区域的列表项
const visibleItems = computed<VirtualItem[]>(() => {
const visibleItems = computed<ClListViewVirtualItem[]>(() => {
// 如果虚拟列表为空,返回空数组
if (isEmpty(virtualItems.value)) {
return [];
@@ -434,7 +435,7 @@ const visibleItems = computed<VirtualItem[]>(() => {
const viewportBottom = scrollTop.value + scrollerHeight.value + bufferHeight;
// 初始化可见项目数组
const visible: VirtualItem[] = [];
const visible: ClListViewVirtualItem[] = [];
// 使用二分查找优化查找起始位置
let startIndex = 0;
@@ -599,7 +600,7 @@ function onScrollEnd(e: UniScrollEvent) {
}
// 行点击事件处理函数
function onItemTap(item: VirtualItem) {
function onItemTap(item: ClListViewVirtualItem) {
emit("item-tap", item.data);
}
@@ -619,12 +620,13 @@ function onIndexChange(index: number) {
// 下拉刷新事件处理函数
function onRefresherPulling(e: UniRefresherEvent) {
if (e.detail.dy > props.refresherThreshold * 1.5) {
if (e.detail.dy > props.refresherThreshold) {
refresherStatus.value = "pulling";
}
emit("refresher-pulling", e);
}
// 下拉刷新事件处理函数
function onRefresherRefresh(e: UniRefresherEvent) {
refresherStatus.value = "refreshing";
refreshTriggered.value = true;
@@ -632,16 +634,27 @@ function onRefresherRefresh(e: UniRefresherEvent) {
emit("pull", e);
}
// 恢复下拉刷新
function onRefresherRestore(e: UniRefresherEvent) {
refresherStatus.value = "default";
emit("refresher-restore", e);
}
// 停止下拉刷新
function onRefresherAbort(e: UniRefresherEvent) {
refresherStatus.value = "default";
emit("refresher-abort", e);
}
// 滚动到顶部
function scrollToTop() {
targetScrollTop.value = 0.01;
nextTick(() => {
targetScrollTop.value = 0;
});
}
// 获取滚动容器的高度
function getScrollerHeight() {
setTimeout(() => {