版本发布

This commit is contained in:
icssoa
2025-07-21 16:47:04 +08:00
parent 1abed7a2e1
commit 6d8193880a
307 changed files with 41718 additions and 0 deletions

28
cool/hooks/cache.ts Normal file
View File

@@ -0,0 +1,28 @@
import { reactive, watch } from "vue";
import { isDark } from "../theme";
type CacheData = {
key: number;
};
type UseCache = {
cache: CacheData;
};
export const useCache = (source: () => any[]): UseCache => {
const cache = reactive<CacheData>({
key: 0
});
watch(source, () => {
cache.key++;
});
watch(isDark, () => {
cache.key++;
});
return {
cache
};
};

5
cool/hooks/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export * from "./refs";
export * from "./page";
export * from "./long-press";
export * from "./cache";
export * from "./parent";

100
cool/hooks/long-press.ts Normal file
View File

@@ -0,0 +1,100 @@
import { vibrate } from "@/uni_modules/cool-vibrate";
import { onUnmounted } from "vue";
// 长按触发延迟时间,单位毫秒
const DELAY = 500;
// 长按重复执行间隔时间,单位毫秒
const REPEAT = 100;
/**
* 长按操作钩子函数返回类型
*/
type UseLongPress = {
// 开始长按
start: (cb: () => void) => void;
// 停止长按
stop: () => void;
// 清除定时器
clear: () => void;
// 是否正在长按中
isPressing: boolean;
};
/**
* 长按操作钩子函数
* 支持长按持续触发,可用于数字输入框等需要连续操作的场景
*/
export const useLongPress = (): UseLongPress => {
// 长按延迟定时器
let pressTimer: number = 0;
// 重复执行定时器
let repeatTimer: number = 0;
// 是否正在长按中
let isPressing = false;
/**
* 清除所有定时器
* 重置长按状态
*/
const clear = () => {
// 清除长按延迟定时器
if (pressTimer != 0) {
clearTimeout(pressTimer);
pressTimer = 0;
}
// 清除重复执行定时器
if (repeatTimer != 0) {
clearInterval(repeatTimer);
repeatTimer = 0;
}
// 重置长按状态
isPressing = false;
};
/**
* 开始长按操作
* @param cb 长按时重复执行的回调函数
*/
const start = (cb: () => void) => {
// 清除已有定时器
clear();
// 立即执行一次回调
cb();
// 延迟500ms后开始长按
// @ts-ignore
pressTimer = setTimeout(() => {
// 震动
vibrate(1);
// 设置长按状态
isPressing = true;
// 每100ms重复执行回调
// @ts-ignore
repeatTimer = setInterval(() => {
cb();
}, REPEAT);
}, DELAY);
};
/**
* 停止长按操作
* 清除定时器并重置状态
*/
const stop = () => {
clear();
};
// 组件卸载时清理定时器
onUnmounted(() => {
clear();
});
return {
start,
stop,
clear,
isPressing
};
};

94
cool/hooks/page.ts Normal file
View File

@@ -0,0 +1,94 @@
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;
}
/**
* 获取 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;
}

22
cool/hooks/parent.ts Normal file
View File

@@ -0,0 +1,22 @@
import { getCurrentInstance } from "vue";
/**
* 获取父组件实例
*
* 用于在子组件中获取父组件实例,以便访问父组件的属性和方法
*
* @example
* ```ts
* // 在子组件中使用
* const parent = useParent<ParentType>();
* // 访问父组件属性
* console.log(parent.someProperty);
* ```
*
* @template T 父组件实例的类型
* @returns {T} 返回父组件实例
*/
export function useParent<T>(): T {
const { proxy } = getCurrentInstance()!;
return proxy?.$parent as T;
}

120
cool/hooks/refs.ts Normal file
View File

@@ -0,0 +1,120 @@
import { reactive } from "vue";
import { isNull } from "../utils";
// #ifdef APP
type Instance = ComponentPublicInstance | null;
// #endif
// #ifndef APP
type Instance = any;
// #endif
/**
* Refs 类用于管理组件引用,便于在组合式 API 中获取、操作子组件实例。
*/
class Refs {
// 存储所有 ref 的响应式对象key 为 ref 名称value 为组件实例
data = reactive({} as UTSJSONObject);
/**
* 生成 ref 绑定函数,用于在模板中设置 ref。
* @param name ref 名称
* @returns 绑定函数 (el: Instance) => void
*/
set(name: string) {
return (el: Instance) => {
this.data[name] = el;
};
}
/**
* 获取指定名称的组件实例
* @param name ref 名称
* @returns 组件实例或 null
*/
get(name: string): Instance {
const d = this.data[name] as ComponentPublicInstance;
if (isNull(d)) {
return null;
}
return d;
}
/**
* 获取组件实例暴露的属性或方法(兼容不同平台)
* @param name ref 名称
* @param key 暴露的属性名
* @returns 属性值或 null
*/
getExposed<T>(name: string, key: string): T | null {
// #ifdef APP-ANDROID
const d = this.get(name);
if (isNull(d)) {
return null;
}
// 安卓平台下,$exposed 为 Map<string, any>
const ex = d!.$exposed as Map<string, any>;
if (isNull(ex)) {
return null;
}
return ex[key] as T | null;
// #endif
// #ifndef APP-ANDROID
// 其他平台直接通过属性访问
return this.get(name)?.[key] as T;
// #endif
}
/**
* 调用组件实例暴露的方法,并返回结果
* @param name ref 名称
* @param method 方法名
* @param data 传递的数据
* @returns 方法返回值
*/
call<T>(name: string, method: string, data: UTSJSONObject | null = null): T {
return this.get(name)!.$callMethod(method, data) as T;
}
/**
* 调用组件实例暴露的方法,无返回值
* @param name ref 名称
* @param method 方法名
* @param data 传递的数据
*/
callMethod(name: string, method: string, data: UTSJSONObject | null = null): void {
this.get(name)!.$callMethod(method, data);
}
/**
* 调用组件的 open 方法,常用于弹窗、抽屉等组件
* @param name ref 名称
* @param data 传递的数据
*/
open(name: string, data: UTSJSONObject | null = null) {
this.callMethod(name, "open", data);
}
/**
* 调用组件的 close 方法,常用于弹窗、抽屉等组件
* @param name ref 名称
*/
close(name: string) {
return this.callMethod(name, "close");
}
}
/**
* useRefs 组合式函数,返回 Refs 实例
* @returns Refs 实例
*/
export function useRefs(): Refs {
return new Refs();
}