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