优化 usePage
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
export * from "./refs";
|
||||
export * from "./page";
|
||||
export * from "./pager";
|
||||
export * from "./long-press";
|
||||
export * from "./cache";
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import { config } from "@/config";
|
||||
import { router } from "../router";
|
||||
import { getPx, isH5, isHarmony } from "../utils";
|
||||
import { ctx } from "../ctx";
|
||||
|
||||
class Page {
|
||||
scrolls: Map<string, ((top: number) => void)[]> = new Map();
|
||||
|
||||
path() {
|
||||
return router.path();
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发滚动事件
|
||||
* @param top 滚动距离
|
||||
*/
|
||||
triggerScroll(top: number) {
|
||||
const callbacks = this.scrolls.get(this.path()) ?? [];
|
||||
callbacks.forEach((cb) => {
|
||||
cb(top);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册滚动事件回调
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
onPageScroll(callback: (top: number) => void) {
|
||||
const callbacks = this.scrolls.get(this.path()) ?? [];
|
||||
callbacks.push(callback);
|
||||
this.scrolls.set(this.path(), callbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要计算 tabBar 高度
|
||||
* @returns boolean
|
||||
*/
|
||||
hasCustomTabBar() {
|
||||
if (router.isTabPage()) {
|
||||
if (isHarmony()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return config.isCustomTabBar || isH5();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在自定义 topbar
|
||||
* @returns boolean
|
||||
*/
|
||||
hasCustomTopbar() {
|
||||
return router.route()?.isCustomNavbar ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 tabBar 高度
|
||||
* @returns tabBar 高度
|
||||
*/
|
||||
getTabBarHeight() {
|
||||
let h = ctx.tabBar.height == null ? 50 : getPx(ctx.tabBar.height!);
|
||||
|
||||
if (this.hasCustomTabBar()) {
|
||||
h += this.getSafeAreaHeight("bottom");
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取安全区域高度
|
||||
* @param type 类型
|
||||
* @returns 安全区域高度
|
||||
*/
|
||||
getSafeAreaHeight(type: "top" | "bottom") {
|
||||
const { safeAreaInsets } = uni.getWindowInfo();
|
||||
|
||||
let h: number;
|
||||
|
||||
if (type == "top") {
|
||||
h = safeAreaInsets.top;
|
||||
} else {
|
||||
h = safeAreaInsets.bottom;
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
if (h == 0) {
|
||||
h = 16;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
export const page = new Page();
|
||||
|
||||
export function usePage(): Page {
|
||||
return page;
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
import { page } from "./hooks";
|
||||
import { initTheme, setH5 } from "./theme";
|
||||
import { initLocale } from "@/locale";
|
||||
|
||||
export function cool(app: VueApp) {
|
||||
app.mixin({
|
||||
onPageScroll(e) {
|
||||
page.triggerScroll(e.scrollTop);
|
||||
},
|
||||
onShow() {
|
||||
// #ifdef H5
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -81,18 +81,10 @@ export function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
timeout,
|
||||
|
||||
success(res) {
|
||||
if (!isObject(res.data as any)) {
|
||||
resolve(res.data as T);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析响应数据
|
||||
const { code, message, data } = parse<Response>(res.data ?? { code: 0 })!;
|
||||
|
||||
// 401 无权限
|
||||
if (res.statusCode == 401) {
|
||||
user.logout();
|
||||
return reject({ message } as Response);
|
||||
return reject({ message: t("无权限") } as Response);
|
||||
}
|
||||
|
||||
// 502 服务异常
|
||||
@@ -111,6 +103,14 @@ export function request<T = any>(options: RequestOptions): Promise<T> {
|
||||
|
||||
// 200 正常响应
|
||||
if (res.statusCode == 200) {
|
||||
if (!isObject(res.data as any)) {
|
||||
resolve(res.data as T);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析响应数据
|
||||
const { code, message, data } = parse<Response>(res.data ?? { code: 0 })!;
|
||||
|
||||
switch (code) {
|
||||
case 1000:
|
||||
resolve(data as T);
|
||||
|
||||
@@ -4,4 +4,5 @@ export * from "./device";
|
||||
export * from "./file";
|
||||
export * from "./parse";
|
||||
export * from "./path";
|
||||
export * from "./rect";
|
||||
export * from "./storage";
|
||||
|
||||
68
cool/utils/rect.ts
Normal file
68
cool/utils/rect.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { config } from "@/config";
|
||||
import { router } from "../router";
|
||||
import { isH5, isHarmony } from "./comm";
|
||||
import { ctx } from "../ctx";
|
||||
import { getPx } from "./parse";
|
||||
|
||||
/**
|
||||
* 是否需要计算 tabBar 高度
|
||||
* @returns boolean
|
||||
*/
|
||||
export function hasCustomTabBar() {
|
||||
if (router.isTabPage()) {
|
||||
if (isHarmony()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return config.isCustomTabBar || isH5();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在自定义 topbar
|
||||
* @returns boolean
|
||||
*/
|
||||
export function hasCustomTopbar() {
|
||||
return router.route()?.isCustomNavbar ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取安全区域高度
|
||||
* @param type 类型
|
||||
* @returns 安全区域高度
|
||||
*/
|
||||
export function getSafeAreaHeight(type: "top" | "bottom") {
|
||||
const { safeAreaInsets } = uni.getWindowInfo();
|
||||
|
||||
let h: number;
|
||||
|
||||
if (type == "top") {
|
||||
h = safeAreaInsets.top;
|
||||
} else {
|
||||
h = safeAreaInsets.bottom;
|
||||
|
||||
// #ifdef APP-ANDROID
|
||||
if (h == 0) {
|
||||
h = 16;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 tabBar 高度
|
||||
* @returns tabBar 高度
|
||||
*/
|
||||
export function getTabBarHeight() {
|
||||
let h = ctx.tabBar.height == null ? 50 : getPx(ctx.tabBar.height!);
|
||||
|
||||
if (hasCustomTabBar()) {
|
||||
h += getSafeAreaHeight("bottom");
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="cl-back-top-wrapper" :style="{ bottom }">
|
||||
<view class="cl-back-top-wrapper" :style="{ bottom }" @tap="toTop">
|
||||
<view
|
||||
class="cl-back-top"
|
||||
:class="{
|
||||
@@ -12,8 +12,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getTabBarHeight, hasCustomTabBar } from "@/cool";
|
||||
import { computed, onMounted, ref, watch, type PropType } from "vue";
|
||||
import { usePage } from "@/cool";
|
||||
import { usePage } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-back-top"
|
||||
@@ -26,9 +27,11 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const { screenHeight } = uni.getWindowInfo();
|
||||
|
||||
// cl-page 上下文
|
||||
const page = usePage();
|
||||
|
||||
// 是否显示回到顶部按钮
|
||||
const visible = ref(false);
|
||||
|
||||
@@ -36,25 +39,30 @@ const visible = ref(false);
|
||||
const bottom = computed(() => {
|
||||
let h = 20;
|
||||
|
||||
if (page.hasCustomTabBar()) {
|
||||
h += page.getTabBarHeight();
|
||||
if (hasCustomTabBar()) {
|
||||
h += getTabBarHeight();
|
||||
}
|
||||
|
||||
return h + "px";
|
||||
});
|
||||
|
||||
// 控制是否显示回到顶部按钮
|
||||
function update(top: number) {
|
||||
// 控制是否显示
|
||||
function onVisible(top: number) {
|
||||
visible.value = top > screenHeight - 100;
|
||||
}
|
||||
|
||||
// 回到顶部
|
||||
function toTop() {
|
||||
page.scrollToTop();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.top != null) {
|
||||
// 监听参数变化
|
||||
watch(
|
||||
computed(() => props.top!),
|
||||
(top: number) => {
|
||||
update(top);
|
||||
onVisible(top);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
@@ -63,7 +71,7 @@ onMounted(() => {
|
||||
} else {
|
||||
// 监听页面滚动
|
||||
page.onPageScroll((top) => {
|
||||
update(top);
|
||||
onVisible(top);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, reactive, nextTick, getCurrentInstance } from "vue";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
import { canvasToPng, getDevicePixelRatio, parsePt, usePage, uuid } from "@/cool";
|
||||
import { canvasToPng, getDevicePixelRatio, getSafeAreaHeight, parsePt, uuid } from "@/cool";
|
||||
|
||||
// 定义遮罩层样式类型
|
||||
type MaskStyle = {
|
||||
@@ -225,9 +225,6 @@ const emit = defineEmits(["crop", "load", "error"]);
|
||||
// 获取当前实例
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// 获取页面实例,用于获取视图尺寸
|
||||
const page = usePage();
|
||||
|
||||
// 创建唯一的canvas ID
|
||||
const canvasId = `cl-cropper__${uuid()}`;
|
||||
|
||||
@@ -382,7 +379,7 @@ const maskStyle = computed<MaskStyle>(() => {
|
||||
|
||||
// 底部按钮组样式
|
||||
const opStyle = computed(() => {
|
||||
let bottom = page.getSafeAreaHeight("bottom");
|
||||
let bottom = getSafeAreaHeight("bottom");
|
||||
|
||||
if (bottom == 0) {
|
||||
bottom = 10;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { router, usePage } from "@/cool";
|
||||
import { getSafeAreaHeight, getTabBarHeight, hasCustomTabBar, router } from "@/cool";
|
||||
import { computed, reactive } from "vue";
|
||||
|
||||
defineOptions({
|
||||
@@ -60,9 +60,6 @@ const props = defineProps({
|
||||
// 获取设备屏幕信息
|
||||
const { screenWidth, statusBarHeight, screenHeight } = uni.getWindowInfo();
|
||||
|
||||
// 页面实例
|
||||
const page = usePage();
|
||||
|
||||
/**
|
||||
* 悬浮按钮位置状态类型定义
|
||||
*/
|
||||
@@ -110,8 +107,8 @@ const viewStyle = computed(() => {
|
||||
let bottomOffset = 0;
|
||||
|
||||
// 标签页需要额外减去标签栏高度和安全区域
|
||||
if (page.hasCustomTabBar()) {
|
||||
bottomOffset += page.getTabBarHeight();
|
||||
if (hasCustomTabBar()) {
|
||||
bottomOffset += getTabBarHeight();
|
||||
}
|
||||
|
||||
// 设置水平位置
|
||||
@@ -151,7 +148,7 @@ function calculateMaxY(): number {
|
||||
|
||||
// 标签页需要额外减去标签栏高度和安全区域
|
||||
if (router.isTabPage()) {
|
||||
maxY -= page.getTabBarHeight();
|
||||
maxY -= getTabBarHeight();
|
||||
}
|
||||
|
||||
return maxY;
|
||||
@@ -206,7 +203,7 @@ function onTouchMove(e: TouchEvent) {
|
||||
let minY = 0;
|
||||
// 非标签页时,底部需要考虑安全区域
|
||||
if (!router.isTabPage()) {
|
||||
minY += page.getSafeAreaHeight("bottom");
|
||||
minY += getSafeAreaHeight("bottom");
|
||||
}
|
||||
|
||||
// 确保按钮不超出屏幕上下边界
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isDark, isHarmony, parsePt, usePage } from "@/cool";
|
||||
import { getSafeAreaHeight, isDark, isHarmony, parsePt } from "@/cool";
|
||||
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch } from "vue";
|
||||
import type { PassThroughProps } from "../../types";
|
||||
|
||||
@@ -46,7 +46,6 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
const page = usePage();
|
||||
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
@@ -77,7 +76,7 @@ function getHeight() {
|
||||
height.value = h;
|
||||
|
||||
// 如果内容高度大于最小高度,则显示
|
||||
visible.value = h > props.minHeight + page.getSafeAreaHeight("bottom");
|
||||
visible.value = h > props.minHeight + getSafeAreaHeight("bottom");
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<view class="cl-form-item" :class="[pt.className]">
|
||||
<view
|
||||
class="cl-form-item"
|
||||
:class="[
|
||||
{
|
||||
'cl-form-item--error': isError
|
||||
},
|
||||
pt.className
|
||||
]"
|
||||
>
|
||||
<view class="cl-form-item__inner" :class="[`is-${labelPosition}`, pt.inner?.className]">
|
||||
<view
|
||||
class="cl-form-item__label"
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, ref, watch, type PropType } from "vue";
|
||||
import { computed, getCurrentInstance, nextTick, ref, watch, type PropType } from "vue";
|
||||
import { isEmpty, isString, parsePt, parseToObject } from "@/cool";
|
||||
import type { ClFormLabelPosition, ClFormRule, ClFormValidateError } from "../../types";
|
||||
import { $t, t } from "@/locale";
|
||||
import { usePage } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-form"
|
||||
@@ -64,9 +65,19 @@ const props = defineProps({
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滚动到第一个错误位置
|
||||
scrollToError: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// cl-page 上下文
|
||||
const page = usePage();
|
||||
|
||||
// 透传样式类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
@@ -269,6 +280,31 @@ function validateField(prop: string): string | null {
|
||||
return error;
|
||||
}
|
||||
|
||||
// 滚动到第一个错误位置
|
||||
function scrollToError(prop: string) {
|
||||
if (props.scrollToError == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
let component = proxy;
|
||||
|
||||
// #ifdef MP
|
||||
component = proxy?.$children.find((e: any) => e.prop == prop);
|
||||
// #endif
|
||||
|
||||
uni.createSelectorQuery()
|
||||
.in(component)
|
||||
.select(".cl-form-item--error")
|
||||
.boundingClientRect((res) => {
|
||||
if (res != null) {
|
||||
page.scrollTo(((res as NodeInfo).top ?? 0) + page.getScrollTop());
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
// 验证整个表单
|
||||
function validate(callback: (valid: boolean, errors: ClFormValidateError[]) => void) {
|
||||
const errs = [] as ClFormValidateError[];
|
||||
@@ -284,6 +320,11 @@ function validate(callback: (valid: boolean, errors: ClFormValidateError[]) => v
|
||||
}
|
||||
});
|
||||
|
||||
// 滚动到第一个错误位置
|
||||
if (errs.length > 0) {
|
||||
scrollToError(errs[0].field);
|
||||
}
|
||||
|
||||
callback(errs.length == 0, errs);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
import { router, usePage } from "@/cool";
|
||||
import Theme from "./theme.uvue";
|
||||
import Ui from "./ui.uvue";
|
||||
import { locale, t } from "@/locale";
|
||||
import { config } from "@/config";
|
||||
import { router } from "@/cool";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-page"
|
||||
@@ -41,34 +41,45 @@ defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
// 滚动距离
|
||||
const scrollTop = ref(0);
|
||||
|
||||
// scroll-view 滚动位置
|
||||
const scrollViewTop = ref(0);
|
||||
|
||||
// view 滚动事件
|
||||
function onScroll(e: UniScrollEvent) {
|
||||
page.triggerScroll(e.detail.scrollTop);
|
||||
scrollTop.value = e.detail.scrollTop;
|
||||
}
|
||||
|
||||
// 回到顶部
|
||||
function scrollToTop() {
|
||||
// 页面滚动事件
|
||||
onPageScroll((e) => {
|
||||
scrollTop.value = e.scrollTop;
|
||||
});
|
||||
|
||||
// 滚动到指定位置
|
||||
function scrollTo(top: number) {
|
||||
// #ifdef H5
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
window.scrollTo({ top, behavior: "smooth" });
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
scrollTop: top,
|
||||
duration: 300
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef APP
|
||||
scrollViewTop.value = 0 + Math.random() / 1000;
|
||||
scrollViewTop.value = top;
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 回到顶部
|
||||
function scrollToTop() {
|
||||
scrollTo(0 + Math.random() / 1000);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 标题多语言
|
||||
// #ifdef H5 || APP
|
||||
@@ -91,4 +102,10 @@ onMounted(() => {
|
||||
);
|
||||
// #endif
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
scrollTop,
|
||||
scrollTo,
|
||||
scrollToTop
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch, type PropType } from "vue";
|
||||
import { parsePt, parseRpx, usePage } from "@/cool";
|
||||
import { getSafeAreaHeight, getTabBarHeight, hasCustomTabBar, parsePt, parseRpx } from "@/cool";
|
||||
import type { ClPopupDirection, PassThroughProps } from "../../types";
|
||||
import { isDark, router } from "@/cool";
|
||||
import { config } from "../../config";
|
||||
@@ -185,15 +185,12 @@ const props = defineProps({
|
||||
// 定义组件事件
|
||||
const emit = defineEmits(["update:modelValue", "open", "opened", "close", "closed", "maskClose"]);
|
||||
|
||||
// 页面实例
|
||||
const page = usePage();
|
||||
|
||||
// 透传样式类型定义
|
||||
type HeaderPassThrough = {
|
||||
className?: string;
|
||||
text?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
inner?: PassThroughProps;
|
||||
@@ -255,7 +252,7 @@ const paddingBottom = computed(() => {
|
||||
let h = 0;
|
||||
|
||||
if (props.direction == "bottom") {
|
||||
h += page.hasCustomTabBar() ? page.getTabBarHeight() : page.getSafeAreaHeight("bottom");
|
||||
h += hasCustomTabBar() ? getTabBarHeight() : getSafeAreaHeight("bottom");
|
||||
}
|
||||
|
||||
return h + "px";
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { isDark, page, parsePt } from "@/cool";
|
||||
import { getSafeAreaHeight, isDark, parsePt } from "@/cool";
|
||||
import { computed, type PropType } from "vue";
|
||||
|
||||
defineOptions({
|
||||
@@ -37,7 +37,7 @@ const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 高度
|
||||
const height = computed(() => {
|
||||
return page.getSafeAreaHeight(props.type) + "px";
|
||||
return getSafeAreaHeight(props.type) + "px";
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { isEmpty, router, usePage } from "@/cool";
|
||||
import { isEmpty, router } from "@/cool";
|
||||
import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref, watch } from "vue";
|
||||
import { usePage } from "../../hooks";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-sticky"
|
||||
@@ -53,9 +54,11 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// cl-page 上下文
|
||||
const page = usePage();
|
||||
|
||||
// 定义Rect类型,表示元素的位置信息
|
||||
type Rect = {
|
||||
height: number; // 高度
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./ui";
|
||||
export * from "./component";
|
||||
export * from "./form";
|
||||
export * from "./page";
|
||||
export * from "./ui";
|
||||
|
||||
68
uni_modules/cool-ui/hooks/page.ts
Normal file
68
uni_modules/cool-ui/hooks/page.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { router, useParent } from "@/cool";
|
||||
import { computed, watch } from "vue";
|
||||
|
||||
type PageScrollCallback = (top: number) => void;
|
||||
|
||||
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() {
|
||||
return router.path();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取滚动位置
|
||||
* @returns 滚动位置
|
||||
*/
|
||||
getScrollTop(): number {
|
||||
return this.pageRef!.scrollTop as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定位置
|
||||
* @param top 滚动位置
|
||||
*/
|
||||
scrollTo(top: number) {
|
||||
this.pageRef!.scrollTo(top);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回到顶部
|
||||
*/
|
||||
scrollToTop() {
|
||||
this.pageRef!.scrollToTop();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听页面滚动
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
onPageScroll(callback: PageScrollCallback) {
|
||||
this.listeners.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export function usePage(): Page {
|
||||
return new Page();
|
||||
}
|
||||
6
uni_modules/cool-ui/types/component.d.ts
vendored
6
uni_modules/cool-ui/types/component.d.ts
vendored
@@ -192,3 +192,9 @@ declare type ClFormItemComponentPublicInstance = {
|
||||
prop: string;
|
||||
isError: boolean;
|
||||
};
|
||||
|
||||
declare type ClPageComponentPublicInstance = {
|
||||
scrollTop: number;
|
||||
scrollTo: (top: number) => void;
|
||||
scrollToTop: () => void;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user