icssoa 7 місяців тому
батько
коміт
407c7b0521

+ 9 - 4
cool/index.ts

@@ -1,8 +1,12 @@
+import { scroller } from "./scroller";
 import { initTheme, setH5 } from "./theme";
 import { initLocale } from "@/locale";
 
 export function cool(app: VueApp) {
 	app.mixin({
+		onPageScroll(e) {
+			scroller.emit(e.scrollTop);
+		},
 		onShow() {
 			// #ifdef H5
 			setTimeout(() => {
@@ -18,11 +22,12 @@ export function cool(app: VueApp) {
 	console.log(app);
 }
 
-export * from "./utils";
-export * from "./theme";
+export * from "./ctx";
+export * from "./hooks";
 export * from "./router";
+export * from "./scroller";
 export * from "./service";
-export * from "./hooks";
-export * from "./ctx";
 export * from "./store";
+export * from "./theme";
 export * from "./upload";
+export * from "./utils";

+ 23 - 0
cool/scroller/index.ts

@@ -0,0 +1,23 @@
+import { router } from "../router";
+
+class Scroller {
+	list: Map<string, ((top: number) => void)[]> = new Map();
+
+	// 触发滚动
+	emit(top: number) {
+		const cbs = this.list.get(router.path()) ?? [];
+		cbs.forEach((cb) => {
+			cb(top);
+		});
+	}
+
+	// 监听页面滚动
+	on(callback: (top: number) => void) {
+		const path = router.path();
+		const cbs = this.list.get(path) ?? [];
+		cbs.push(callback);
+		this.list.set(path, cbs);
+	}
+}
+
+export const scroller = new Scroller();

+ 11 - 1
pages/demo/data/list-view.uvue

@@ -17,6 +17,9 @@
 					:pt="{
 						indexBar: {
 							className: '!fixed'
+						},
+						itemHover: {
+							className: 'bg-gray-200'
 						}
 					}"
 				>
@@ -29,12 +32,16 @@
 <script lang="ts" setup>
 import { request } from "@/cool";
 import DemoItem from "../components/item.uvue";
-import { useListView, type ClListViewItem } from "@/uni_modules/cool-ui";
+import { useListView, useUi, type ClListViewItem } from "@/uni_modules/cool-ui";
 import { ref } from "vue";
 
+const ui = useUi();
+
 const data = ref<ClListViewItem[]>([]);
 
 onReady(() => {
+	ui.showLoading();
+
 	request<UTSJSONObject[]>({
 		url: "https://unix.cool-js.com/data/pca_flat.json"
 	})
@@ -43,6 +50,9 @@ onReady(() => {
 		})
 		.catch((err) => {
 			console.error(err);
+		})
+		.finally(() => {
+			ui.hideLoading();
 		});
 });
 </script>

+ 20 - 13
uni_modules/cool-ui/components/cl-back-top/cl-back-top.uvue

@@ -12,7 +12,7 @@
 </template>
 
 <script setup lang="ts">
-import { getTabBarHeight, hasCustomTabBar } from "@/cool";
+import { getTabBarHeight, hasCustomTabBar, scroller } from "@/cool";
 import { computed, onMounted, ref, watch, type PropType } from "vue";
 import { usePage } from "../../hooks";
 
@@ -27,10 +27,12 @@ const props = defineProps({
 	}
 });
 
+const emit = defineEmits(["backTop"]);
+
 const { screenHeight } = uni.getWindowInfo();
 
 // cl-page 上下文
-const page = usePage();
+const { scrollToTop, onScroll } = usePage();
 
 // 是否显示回到顶部按钮
 const visible = ref(false);
@@ -46,6 +48,9 @@ const bottom = computed(() => {
 	return h + "px";
 });
 
+// 是否页面滚动
+const isPage = computed(() => props.top == null);
+
 // 控制是否显示
 function onVisible(top: number) {
 	visible.value = top > screenHeight - 100;
@@ -53,11 +58,20 @@ function onVisible(top: number) {
 
 // 回到顶部
 function toTop() {
-	page.scrollToTop();
+	if (isPage.value) {
+		scrollToTop();
+	}
+
+	emit("backTop");
 }
 
 onMounted(() => {
-	if (props.top != null) {
+	if (isPage.value) {
+		// 监听页面滚动
+		onScroll((top) => {
+			onVisible(top);
+		});
+	} else {
 		// 监听参数变化
 		watch(
 			computed(() => props.top!),
@@ -68,22 +82,16 @@ onMounted(() => {
 				immediate: true
 			}
 		);
-	} else {
-		// 监听页面滚动
-		page.onPageScroll((top) => {
-			onVisible(top);
-		});
 	}
 });
 </script>
 
 <style lang="scss" scoped>
 .cl-back-top {
-	@apply flex flex-row items-center justify-center bg-primary-500 rounded-full;
+	@apply flex flex-row items-center justify-center bg-primary-500 rounded-full duration-300;
 	width: 40px;
 	height: 40px;
 	transition-property: transform;
-	transition-duration: 0.3s;
 	transform: translateX(160rpx);
 
 	&.is-show {
@@ -91,8 +99,7 @@ onMounted(() => {
 	}
 
 	&-wrapper {
-		@apply fixed z-50 overflow-visible;
-		right: 0;
+		@apply fixed right-0 z-50 overflow-visible;
 	}
 }
 </style>

+ 25 - 12
uni_modules/cool-ui/components/cl-list-view/cl-list-view.uvue

@@ -1,16 +1,6 @@
 <template>
 	<view class="cl-list-view" :class="[pt.className]">
-		<cl-index-bar
-			v-if="hasIndex"
-			v-model="activeIndex"
-			:list="indexList"
-			:pt="{
-				className: parseClass([pt.indexBar?.className])
-			}"
-			@change="onIndexChange"
-		>
-		</cl-index-bar>
-
+		<!-- 滚动容器 -->
 		<scroll-view
 			class="cl-list-view__scroller"
 			:class="[pt.scroller?.className]"
@@ -33,6 +23,7 @@
 			@refresherrestore="onRefresherRestore"
 			@refresherabort="onRefresherAbort"
 		>
+			<!-- 下拉刷新 -->
 			<view
 				slot="refresher"
 				class="cl-list-view__refresher"
@@ -59,15 +50,18 @@
 				</slot>
 			</view>
 
+			<!-- 列表 -->
 			<view
 				class="cl-list-view__virtual-list"
 				:class="[pt.list?.className]"
 				:style="listStyle"
 			>
+				<!-- 顶部占位 -->
 				<view class="cl-list-view__spacer-top" :style="spacerTopStyle">
 					<slot name="top"></slot>
 				</view>
 
+				<!-- 列表项 -->
 				<view
 					v-for="(item, index) in visibleItems"
 					:key="item.key"
@@ -99,6 +93,7 @@
 							},
 							pt.item?.className
 						]"
+						:hover-class="pt.itemHover?.className"
 						:style="{
 							height: virtual ? itemHeight + 'px' : 'auto'
 						}"
@@ -118,15 +113,29 @@
 					</view>
 				</view>
 
+				<!-- 底部占位 -->
 				<view class="cl-list-view__spacer-bottom" :style="spacerBottomStyle">
 					<slot name="bottom"></slot>
 				</view>
 			</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>
 
+		<!-- 右侧索引栏 -->
+		<cl-index-bar
+			v-if="hasIndex"
+			v-model="activeIndex"
+			:list="indexList"
+			:pt="{
+				className: parseClass([pt.indexBar?.className])
+			}"
+			@change="onIndexChange"
+		>
+		</cl-index-bar>
+
+		<!-- 索引提示 -->
 		<view
 			class="cl-list-view__index"
 			:class="[
@@ -141,6 +150,9 @@
 				<cl-text> {{ indexList[activeIndex] }} </cl-text>
 			</slot>
 		</view>
+
+		<!-- 回到顶部 -->
+		<cl-back-top :top="scrollTop" v-if="showBackTop" @back-top="scrollToTop"></cl-back-top>
 	</view>
 </template>
 
@@ -295,6 +307,7 @@ const { proxy } = getCurrentInstance()!;
 type PassThrough = {
 	className?: string;
 	item?: PassThroughProps;
+	itemHover?: PassThroughProps;
 	list?: PassThroughProps;
 	indexBar?: PassThroughProps;
 	scroller?: PassThroughProps;

+ 7 - 6
uni_modules/cool-ui/components/cl-page/cl-page.uvue

@@ -6,7 +6,7 @@
 		:scroll-with-animation="true"
 		@scroll="onScroll"
 	>
-		<cl-back-top v-if="backTop" @tap="scrollToTop"></cl-back-top>
+		<cl-back-top v-if="backTop"></cl-back-top>
 		<theme></theme>
 		<ui></ui>
 		<slot></slot>
@@ -14,7 +14,7 @@
 	<!-- #endif -->
 
 	<!-- #ifndef APP -->
-	<cl-back-top v-if="backTop" @tap="scrollToTop"></cl-back-top>
+	<cl-back-top v-if="backTop"></cl-back-top>
 	<theme></theme>
 	<ui></ui>
 	<slot></slot>
@@ -27,7 +27,7 @@ import Theme from "./theme.uvue";
 import Ui from "./ui.uvue";
 import { locale, t } from "@/locale";
 import { config } from "@/config";
-import { router } from "@/cool";
+import { router, scroller } from "@/cool";
 
 defineOptions({
 	name: "cl-page"
@@ -49,12 +49,13 @@ const scrollViewTop = ref(0);
 
 // view 滚动事件
 function onScroll(e: UniScrollEvent) {
-	scrollTop.value = e.detail.scrollTop;
+	// 触发滚动事件
+	scroller.emit(e.detail.scrollTop);
 }
 
 // 页面滚动事件
-onPageScroll((e) => {
-	scrollTop.value = e.scrollTop;
+scroller.on((top) => {
+	scrollTop.value = top;
 });
 
 // 滚动到指定位置

+ 11 - 9
uni_modules/cool-ui/components/cl-sticky/cl-sticky.uvue

@@ -57,9 +57,9 @@ const props = defineProps({
 const { proxy } = getCurrentInstance()!;
 
 // cl-page 上下文
-const page = usePage();
+const { onScroll } = usePage();
 
-// 定义Rect类型,表示元素的位置信息
+// 表示元素的位置信息
 type Rect = {
 	height: number; // 高度
 	width: number; // 宽度
@@ -67,7 +67,7 @@ type Rect = {
 	top: number; // 距离页面顶部的距离
 };
 
-// rect为响应式对象,存储当前sticky元素的位置信息
+// 存储当前sticky元素的位置信息
 const rect = reactive<Rect>({
 	height: 0,
 	width: 0,
@@ -75,7 +75,7 @@ const rect = reactive<Rect>({
 	top: 0
 });
 
-// scrollTop为当前页面滚动的距离
+// 当前页面滚动的距离
 const scrollTop = ref(0);
 
 // 计算属性,判断当前是否处于吸顶状态
@@ -128,14 +128,16 @@ function getRect() {
 	});
 }
 
-// 监听页面滚动事件
-page.onPageScroll((top) => {
-	scrollTop.value = top;
-});
-
 onMounted(() => {
+	// 获取元素位置信息
 	getRect();
 
+	// 监听页面滚动事件
+	onScroll((top) => {
+		scrollTop.value = top;
+	});
+
+	// 监听参数变化
 	watch(
 		computed(() => props.scrollTop),
 		(top: number) => {

+ 12 - 28
uni_modules/cool-ui/hooks/page.ts

@@ -1,66 +1,50 @@
-import { router, useParent } from "@/cool";
-import { computed, watch } from "vue";
-
-type PageScrollCallback = (top: number) => void;
+import { router, scroller, useParent } from "@/cool";
 
 class Page {
-	listeners: PageScrollCallback[] = [];
 	pageRef: ClPageComponentPublicInstance | null = null;
 
 	constructor() {
 		this.pageRef = useParent<ClPageComponentPublicInstance>("cl-page");
-
-		if (this.pageRef != null) {
-			// TODO: 小程序异常
-			watch(
-				computed(() => this.pageRef!.scrollTop),
-				(top: number) => {
-					this.listeners.forEach((listener) => {
-						listener(top);
-					});
-				}
-			);
-		}
 	}
 
 	/**
 	 * 获取页面路径
 	 * @returns 页面路径
 	 */
-	path() {
+	path = () => {
 		return router.path();
-	}
+	};
 
 	/**
 	 * 获取滚动位置
 	 * @returns 滚动位置
 	 */
-	getScrollTop(): number {
+	getScrollTop = (): number => {
 		return this.pageRef!.scrollTop as number;
-	}
+	};
 
 	/**
 	 * 滚动到指定位置
 	 * @param top 滚动位置
 	 */
-	scrollTo(top: number) {
+	scrollTo = (top: number) => {
 		this.pageRef!.scrollTo(top);
-	}
+	};
 
 	/**
 	 * 回到顶部
 	 */
-	scrollToTop() {
+	scrollToTop = () => {
 		this.pageRef!.scrollToTop();
-	}
+	};
 
 	/**
 	 * 监听页面滚动
 	 * @param callback 回调函数
 	 */
-	onPageScroll(callback: PageScrollCallback) {
-		this.listeners.push(callback);
-	}
+	onScroll = (callback: (top: number) => void) => {
+		scroller.on(callback);
+	};
 }
 
 export function usePage(): Page {