Procházet zdrojové kódy

更新列表刷新组件

icssoa před 7 měsíci
rodič
revize
dda78b9b07

+ 58 - 45
uni_modules/cool-ui/components/cl-list-view/cl-list-view.uvue

@@ -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(() => {