看板
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
export * from './useEmitter';
|
||||
export * from './useRect';
|
||||
export * from './useCompRelation';
|
||||
export * from './useTheme';
|
||||
export * from './useColor';
|
||||
export * from './useLocale';
|
||||
export * from './useDebounce';
|
||||
export * from './useThrottle';
|
||||
export * from './useToast';
|
||||
export * from './useModal';
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 响应式颜色管理 composable
|
||||
* 提供响应式的颜色访问,支持主题切换和暗黑模式
|
||||
*
|
||||
* 使用方式:
|
||||
* const { color, getColor } = useColor()
|
||||
* // color 是响应式的,可以直接在模板中使用
|
||||
* // getColor('primary') 返回响应式的颜色值
|
||||
*/
|
||||
|
||||
import { computed, type ComputedRef } from 'vue';
|
||||
import type { ColorType } from '../../types/global';
|
||||
import configProvider from '../util/config-provider';
|
||||
|
||||
/**
|
||||
* 响应式颜色 composable
|
||||
* 返回响应式的颜色对象和获取颜色的方法
|
||||
*/
|
||||
export function useColor() {
|
||||
// 从 configProvider 获取当前主题的响应式引用
|
||||
const currentTheme = configProvider.currentThemeRef;
|
||||
const darkModeRef = configProvider.darkModeRef;
|
||||
|
||||
/**
|
||||
* 获取当前激活模式下的颜色对象(响应式)
|
||||
*/
|
||||
const color = computed(() => {
|
||||
const theme = currentTheme.value;
|
||||
if (!theme) return {} as Record<ColorType, string>;
|
||||
|
||||
const isDark = configProvider.isInDarkMode();
|
||||
const palette =
|
||||
isDark && theme.darkColor && Object.keys(theme.darkColor).length ? theme.darkColor : theme.color || {};
|
||||
|
||||
// 合并默认值,确保所有颜色都有值
|
||||
const defaultPalette = isDark
|
||||
? (configProvider as any).baseDarkColorTokens || {}
|
||||
: (configProvider as any).baseColorTokens || {};
|
||||
|
||||
return {
|
||||
...defaultPalette,
|
||||
...palette
|
||||
} as Record<ColorType, string>;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取指定颜色(响应式)
|
||||
* @param name 颜色名称
|
||||
* @returns 颜色值
|
||||
*/
|
||||
const getColor = (name: ColorType): ComputedRef<string> => {
|
||||
return computed(() => {
|
||||
return color.value[name] || '';
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
color,
|
||||
getColor
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
getCurrentInstance,
|
||||
onUnmounted,
|
||||
nextTick,
|
||||
computed,
|
||||
onMounted,
|
||||
type ComponentInternalInstance
|
||||
} from 'vue';
|
||||
import { logger } from '../util/logger';
|
||||
|
||||
// 类型定义
|
||||
interface ParentContext {
|
||||
name: string;
|
||||
addChild: (child: ChildContext) => void;
|
||||
removeChild: (childId: string) => void;
|
||||
broadcast: (event: string, data?: any, childIds?: string | string[]) => void;
|
||||
broadcastToChildren: (componentName: string, event: string, data?: any) => void;
|
||||
getChildren: () => ChildContext[];
|
||||
getExposed: () => Record<string, any>;
|
||||
getChildExposed: (childId: string) => Record<string, any>;
|
||||
getChildrenExposed: () => Array<{ id: string; name: string; exposed: Record<string, any> }>;
|
||||
getInstance: () => any;
|
||||
}
|
||||
|
||||
interface ChildContext {
|
||||
id: string;
|
||||
name: string;
|
||||
getChildIndex: () => number;
|
||||
emitToParent: (event: string, data?: any) => void;
|
||||
getParentExposed: () => Record<string, any>;
|
||||
getInstance: () => any;
|
||||
getExposed: () => Record<string, any>;
|
||||
}
|
||||
|
||||
// 符号定义
|
||||
const PARENT_CONTEXT_SYMBOL = Symbol('parent_context');
|
||||
|
||||
/**
|
||||
* 生成实例唯一ID
|
||||
*/
|
||||
function generateInstanceId(componentName: string): string {
|
||||
return `${componentName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找父组件实例
|
||||
*/
|
||||
function findParentInstance(name: string, instance: any): any {
|
||||
if (!instance) return null;
|
||||
|
||||
let parent = instance.parent;
|
||||
while (parent) {
|
||||
const parentName = parent.type?.name || parent.type?.__name;
|
||||
if (parentName === name) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父组件上下文
|
||||
*/
|
||||
function getParentContext(name: string, instance: any): ParentContext | null {
|
||||
const parentInstance = findParentInstance(name, instance);
|
||||
return parentInstance?.proxy?.[PARENT_CONTEXT_SYMBOL] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找所有指定名称的子组件
|
||||
*/
|
||||
function findAllChildComponents(componentName: string, instance: any): any[] {
|
||||
const components: any[] = [];
|
||||
|
||||
function traverse(currentInstance: any) {
|
||||
if (!currentInstance?.subTree) return;
|
||||
|
||||
const subTree = currentInstance.subTree?.children || [];
|
||||
const children = Array.isArray(subTree) ? subTree : [subTree];
|
||||
|
||||
children.forEach((vnode: any) => {
|
||||
const child = vnode.component;
|
||||
if (!child) return;
|
||||
|
||||
const name = child.type?.name || child.type?.__name;
|
||||
if (name === componentName) {
|
||||
components.push(child);
|
||||
}
|
||||
traverse(child);
|
||||
});
|
||||
}
|
||||
|
||||
traverse(instance);
|
||||
logger.log(`Found ${components.length} ${componentName} components`);
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* 父组件 Hook
|
||||
*/
|
||||
export function useParent(componentName?: string) {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) {
|
||||
throw new Error('useParent must be called within setup function');
|
||||
}
|
||||
|
||||
const name = componentName || instance.type.name || instance.type.__name;
|
||||
if (!name) {
|
||||
throw new Error('Component name is required for useParent');
|
||||
}
|
||||
|
||||
const children = reactive<ChildContext[]>([]);
|
||||
const childrenMap = new Map<string, ChildContext>();
|
||||
|
||||
const broadcast = (event: string, data?: any, childIds?: string | string[]) => {
|
||||
const targetChildren = childIds
|
||||
? ((Array.isArray(childIds) ? childIds : [childIds])
|
||||
.map(id => childrenMap.get(id))
|
||||
.filter(Boolean) as ChildContext[])
|
||||
: Array.from(childrenMap.values());
|
||||
|
||||
logger.log(`Parent ${name} broadcasting event: ${event} to ${targetChildren.length} children`);
|
||||
|
||||
targetChildren.forEach(child => {
|
||||
const exposed = child.getExposed();
|
||||
if (exposed && typeof exposed[event] === 'function') {
|
||||
try {
|
||||
exposed[event](data);
|
||||
} catch (error) {
|
||||
logger.warn(`Error calling child method ${event}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const broadcastToChildren = (componentName: string, event: string, data?: any) => {
|
||||
logger.log(`Parent ${name} broadcasting event: ${event} to all ${componentName} components`);
|
||||
|
||||
const childComponents = findAllChildComponents(componentName, instance);
|
||||
let successCount = 0;
|
||||
|
||||
childComponents.forEach(childComponent => {
|
||||
const exposed = childComponent.exposed || childComponent.proxy;
|
||||
if (exposed && typeof exposed[event] === 'function') {
|
||||
try {
|
||||
exposed[event](data);
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
logger.warn(`Error calling ${componentName} method ${event}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.log(
|
||||
`Parent ${name} successfully called ${successCount} of ${childComponents.length} ${componentName} components`
|
||||
);
|
||||
};
|
||||
|
||||
const parentContext: ParentContext = {
|
||||
name,
|
||||
addChild(child: ChildContext) {
|
||||
if (!childrenMap.has(child.id)) {
|
||||
childrenMap.set(child.id, child);
|
||||
children.push(child);
|
||||
logger.log(`Parent ${name} added child: ${child.name}`);
|
||||
}
|
||||
},
|
||||
removeChild(childId: string) {
|
||||
if (childrenMap.has(childId)) {
|
||||
const child = childrenMap.get(childId)!;
|
||||
childrenMap.delete(childId);
|
||||
const index = children.findIndex(c => c.id === childId);
|
||||
if (index > -1) children.splice(index, 1);
|
||||
logger.log(`Parent ${name} removed child: ${childId}`);
|
||||
}
|
||||
},
|
||||
broadcast,
|
||||
broadcastToChildren,
|
||||
getChildren: () => Array.from(childrenMap.values()),
|
||||
getExposed: () => instance.exposed || {},
|
||||
getChildExposed(childId: string) {
|
||||
const child = childrenMap.get(childId);
|
||||
return child?.getExposed?.() || {};
|
||||
},
|
||||
getChildrenExposed() {
|
||||
return Array.from(childrenMap.values())
|
||||
.filter(child => child.getExposed)
|
||||
.map(child => ({
|
||||
id: child.id,
|
||||
name: child.name,
|
||||
exposed: child.getExposed()
|
||||
}))
|
||||
.filter(item => Object.keys(item.exposed).length > 0);
|
||||
},
|
||||
getInstance: () => instance
|
||||
};
|
||||
|
||||
if (instance.proxy) {
|
||||
(instance.proxy as any)[PARENT_CONTEXT_SYMBOL] = parentContext;
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
childrenMap.forEach((_, childId) => parentContext.removeChild(childId));
|
||||
if (instance.proxy) {
|
||||
delete (instance.proxy as any)[PARENT_CONTEXT_SYMBOL];
|
||||
}
|
||||
logger.log(`Parent ${name} unmounted and cleaned up`);
|
||||
});
|
||||
|
||||
return {
|
||||
parentName: name,
|
||||
children,
|
||||
broadcast,
|
||||
broadcastToChildren,
|
||||
getChildren: parentContext.getChildren,
|
||||
getChildExposed: parentContext.getChildExposed,
|
||||
getChildrenExposed: parentContext.getChildrenExposed,
|
||||
getExposed: parentContext.getExposed,
|
||||
getInstance: parentContext.getInstance
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 子组件 Hook
|
||||
*/
|
||||
export function useChildren(componentName?: string, parentName?: string) {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) {
|
||||
throw new Error('useChildren must be called within setup function');
|
||||
}
|
||||
|
||||
const name = componentName || instance.type.name || instance.type.__name;
|
||||
const instanceId = generateInstanceId(name || 'anonymous');
|
||||
const parentRef = ref<any | null>(null);
|
||||
const parentExposed = ref<Record<string, any>>({});
|
||||
|
||||
const createSimulatedParentContext = (parentInstance: any): ParentContext => ({
|
||||
name: parentInstance?.type?.name || parentInstance?.type?.__name || 'unknown',
|
||||
addChild: () => logger.log('Simulated parent added child'),
|
||||
removeChild: () => logger.log('Simulated parent removed child'),
|
||||
broadcast: () => logger.log('Simulated parent broadcasting'),
|
||||
broadcastToChildren: () => logger.log('Simulated parent broadcasting to children'),
|
||||
getChildren: () => [],
|
||||
getExposed: () => parentInstance?.exposed || {},
|
||||
getChildExposed: () => ({}),
|
||||
getChildrenExposed: () => [],
|
||||
getInstance: () => parentInstance
|
||||
});
|
||||
|
||||
const getParentExposed = (): Record<string, any> => {
|
||||
if (parentRef.value) {
|
||||
const exposed = parentRef.value.getExposed();
|
||||
parentExposed.value = exposed;
|
||||
return exposed;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
const getExposed = (): Record<string, any> => instance.exposed || {};
|
||||
|
||||
const findParent = (): ParentContext | null => {
|
||||
if (parentName) {
|
||||
const parentContext = getParentContext(parentName, instance);
|
||||
if (parentContext) {
|
||||
if (!parentContext.getInstance) {
|
||||
parentContext.getInstance = () => findParentInstance(parentName, instance);
|
||||
}
|
||||
return parentContext;
|
||||
}
|
||||
|
||||
const parentInstance = findParentInstance(parentName, instance);
|
||||
if (parentInstance) {
|
||||
return createSimulatedParentContext(parentInstance);
|
||||
}
|
||||
}
|
||||
|
||||
let current = instance.parent;
|
||||
while (current) {
|
||||
const context = (current.proxy as any)?.[PARENT_CONTEXT_SYMBOL];
|
||||
if (context) {
|
||||
if (!context.getInstance) {
|
||||
context.getInstance = () => current;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return instance.parent ? createSimulatedParentContext(instance.parent) : null;
|
||||
};
|
||||
|
||||
const linkParent = (): boolean => {
|
||||
const parent = findParent();
|
||||
if (parent) {
|
||||
parentRef.value = parent;
|
||||
if (parent.addChild && childContext) {
|
||||
parent.addChild(childContext);
|
||||
}
|
||||
getParentExposed();
|
||||
logger.log(`Child ${name || 'anonymous'} linked to parent ${parent.name}`);
|
||||
return true;
|
||||
}
|
||||
logger.log(`Child ${name || 'anonymous'} no parent found, working in standalone mode`);
|
||||
return false;
|
||||
};
|
||||
|
||||
const emitToParent = (event: string, data?: any) => {
|
||||
if (parentRef.value) {
|
||||
const exposed = getParentExposed();
|
||||
if (exposed && typeof exposed[event] === 'function') {
|
||||
try {
|
||||
exposed[event](data, instanceId, name);
|
||||
} catch (error) {
|
||||
logger.warn(`Error calling parent method ${event}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getChildIndex = () => {
|
||||
if (!parentRef.value) return -1;
|
||||
try {
|
||||
const children = parentRef.value.getChildren();
|
||||
return children.findIndex((child: ChildContext) => child.id === instanceId);
|
||||
} catch (error) {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
const childContext: ChildContext = {
|
||||
id: instanceId,
|
||||
name: name || 'anonymous',
|
||||
getChildIndex,
|
||||
emitToParent,
|
||||
getParentExposed,
|
||||
getInstance: () => instance,
|
||||
getExposed
|
||||
};
|
||||
|
||||
logger.log(`Child ${name || 'anonymous'} registered, looking for parent`);
|
||||
|
||||
onMounted(() => {
|
||||
let connected = linkParent();
|
||||
nextTick(() => {
|
||||
connected = linkParent();
|
||||
if (!connected) {
|
||||
setTimeout(linkParent, 500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (parentRef.value?.removeChild) {
|
||||
parentRef.value.removeChild(instanceId);
|
||||
}
|
||||
logger.log(`Child ${name || 'anonymous'} unmounted`);
|
||||
});
|
||||
|
||||
return {
|
||||
childId: instanceId,
|
||||
childName: name || 'anonymous',
|
||||
childIndex: computed(getChildIndex),
|
||||
parent: parentRef,
|
||||
emitToParent,
|
||||
getParentExposed,
|
||||
parentExposed: computed(() => parentExposed.value),
|
||||
getExposed
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查父组件是否存在
|
||||
*/
|
||||
export function hasParent(parentName?: string): boolean {
|
||||
const instance = getCurrentInstance();
|
||||
if (!instance) return false;
|
||||
|
||||
if (parentName) {
|
||||
return getParentContext(parentName, instance) !== null;
|
||||
}
|
||||
|
||||
let current = instance.parent;
|
||||
while (current) {
|
||||
if ((current.proxy as any)?.[PARENT_CONTEXT_SYMBOL]) return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父组件上下文
|
||||
*/
|
||||
export function getParentContextByName(parentName: string): ParentContext | null {
|
||||
const instance = getCurrentInstance();
|
||||
return instance ? getParentContext(parentName, instance) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 热更新清理函数
|
||||
*/
|
||||
export function cleanupComponentRelations(): void {
|
||||
logger.log('Cleaning up component relations for hot reload');
|
||||
}
|
||||
|
||||
// 热更新处理
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(() => {
|
||||
logger.log('Hot reload detected, relations will be automatically reconnected');
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
useParent,
|
||||
useChildren,
|
||||
hasParent,
|
||||
getParentContextByName,
|
||||
cleanupComponentRelations
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
export function useDebounce(delay: number = 500) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
// 防抖函数
|
||||
function debounce(callback: () => void, debounceTime?: number) {
|
||||
debounceTime = debounceTime || delay;
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
callback();
|
||||
}, debounceTime);
|
||||
}
|
||||
|
||||
return {
|
||||
debounce
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { type ComponentInternalInstance, getCurrentInstance } from 'vue';
|
||||
|
||||
/**
|
||||
* 将事件名转换为驼峰格式
|
||||
* @param str 需要转换的字符串
|
||||
* @description 例如:on-form-change -> onFormChange
|
||||
* @returns
|
||||
*/
|
||||
function formatToCamelCase(str: string): string {
|
||||
return str.replace(/-([a-z])/g, function (g) {
|
||||
return g[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function useEmitter(name: string) {
|
||||
const instance: ComponentInternalInstance | null | undefined = getCurrentInstance();
|
||||
|
||||
/** * 向上查找父组件并派发事件
|
||||
* @param instance 当前组件实例(setup中可用getCurrentInstance())
|
||||
* @param componentName 目标组件名
|
||||
* @param eventName 事件名
|
||||
* @param params 参数
|
||||
*/
|
||||
function dispatch(componentName: string, eventName: string, ...params: any[]) {
|
||||
let parent = instance && (instance.parent as ComponentInternalInstance | null | undefined);
|
||||
while (parent) {
|
||||
const name = (parent.type as any)?.name as string | undefined;
|
||||
if (name === componentName) {
|
||||
// 找到目标组件,派发事件
|
||||
// Vue3未解决,目标组件事件监听失效,待优化,暂时使用下面的方式解决,如果你有好的方式也可以告诉我或者提PR
|
||||
parent.emit && parent.emit(eventName, ...params);
|
||||
// 如果有对应的方法,执行方法
|
||||
// 这里可以考虑将 eventName 转换为驼峰格式
|
||||
// 例如:on-form-change -> onFormChange
|
||||
parent.exposed?.[formatToCamelCase(eventName)] &&
|
||||
parent.exposed[formatToCamelCase(eventName)](...params);
|
||||
break;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向下递归查找子组件并广播事件
|
||||
* @param instance 当前组件实例(setup中可用getCurrentInstance())
|
||||
* @param componentName 目标组件名
|
||||
* @param eventName 事件名
|
||||
* @param params 参数
|
||||
*/
|
||||
function broadcast(componentName: string, eventName: string, ...params: any[]) {
|
||||
if (!instance) return;
|
||||
const subTree = (instance.subTree as any)?.children || [];
|
||||
const children = Array.isArray(subTree) ? subTree : [subTree];
|
||||
children.forEach((vnode: any) => {
|
||||
const child = vnode.component as ComponentInternalInstance | undefined;
|
||||
|
||||
if (child) {
|
||||
const name = (child.type as any)?.name as string | undefined;
|
||||
if (name === componentName) {
|
||||
// 找到目标组件,广播事件
|
||||
// Vue3未解决,目标组件事件监听失效,待优化,暂时使用下面的方式解决,如果你有好的方式也可以告诉我或者提PR
|
||||
child.emit && child.emit(eventName, ...params);
|
||||
// 如果有对应的方法,执行方法
|
||||
// 这里可以考虑将 eventName 转换为驼峰格式
|
||||
// 例如:on-form-change -> onFormChange
|
||||
child.exposed?.[formatToCamelCase(eventName)] &&
|
||||
child.exposed[formatToCamelCase(eventName)](...params);
|
||||
} else {
|
||||
broadcast.call(child, componentName, eventName, ...params);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
dispatch,
|
||||
broadcast
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import configProvider from '../util/config-provider';
|
||||
|
||||
/**
|
||||
* 国际化 composable
|
||||
* 提供:
|
||||
* - currentLocale / locales 响应式引用
|
||||
* - t(key, replacements) 翻译方法
|
||||
* - setLocale(name) 切换语言
|
||||
* - initLocales(locales?, defaultName?) 初始化语言包
|
||||
*/
|
||||
export function useLocale(namespace?: string) {
|
||||
// 创建翻译函数,支持命名空间
|
||||
const createTranslateFunction = (ns?: string) => {
|
||||
return (key: string, replacements?: any, localeName?: string): string => {
|
||||
// 如果有命名空间,自动添加前缀
|
||||
const fullKey = ns ? `${ns}.${key}` : key;
|
||||
return configProvider.t(fullKey, replacements, localeName);
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 响应式引用
|
||||
currentLocale: configProvider.currentLocaleRef,
|
||||
locales: configProvider.localesRef,
|
||||
|
||||
// 方法
|
||||
t: createTranslateFunction(namespace),
|
||||
setLocale: (name: string) => configProvider.setLocale(name),
|
||||
getLocales: () => configProvider.getLocales(),
|
||||
getCurrentLocale: () => configProvider.getCurrentLocale(),
|
||||
initLocales: (locales?: any[], defaultLocaleName?: string, isForce?: boolean) =>
|
||||
configProvider.initLocales(locales, defaultLocaleName, isForce)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import {
|
||||
U_MODAL_EVENT_SHOW,
|
||||
U_MODAL_EVENT_HIDE,
|
||||
U_MODAL_EVENT_CLEAR_LOADING,
|
||||
U_MODAL_GLOBAL_EVENT_SHOW,
|
||||
U_MODAL_GLOBAL_EVENT_HIDE,
|
||||
U_MODAL_GLOBAL_EVENT_CLEAR_LOADING,
|
||||
getEventWithCurrentPage,
|
||||
type ModalPayload
|
||||
} from '../../components/u-modal/service';
|
||||
|
||||
export type UseModalShowOptions = ModalPayload;
|
||||
|
||||
export type UseModal = {
|
||||
/**
|
||||
* 显示 modal
|
||||
* - show('标题')
|
||||
* - show({ title, content, showCancelButton, ... })
|
||||
*/
|
||||
show: (contentOrOptions: string | UseModalShowOptions) => void;
|
||||
/**
|
||||
* 显示 confirm 类型的 modal(带确认和取消按钮)
|
||||
* - confirm('标题')
|
||||
* - confirm({ title, content, onConfirm, onCancel })
|
||||
*/
|
||||
confirm: (contentOrOptions: string | UseModalShowOptions) => void;
|
||||
/** 关闭 modal */
|
||||
close: () => void;
|
||||
/** 清除 loading 状态 */
|
||||
clearLoading: () => void;
|
||||
};
|
||||
|
||||
export type UseModalOptions = {
|
||||
/** 是否使用全局根部 <u-modal global /> */
|
||||
global?: boolean;
|
||||
/** 是否使用页面级 <u-modal page /> 或某个页面的 <u-modal page="pageId" /> */
|
||||
page?: boolean | string;
|
||||
};
|
||||
|
||||
function normalize(contentOrOptions: string | UseModalShowOptions): UseModalShowOptions {
|
||||
if (typeof contentOrOptions === 'string') {
|
||||
// 如果是简单的字符串,可能是标题或内容
|
||||
return { content: contentOrOptions };
|
||||
}
|
||||
return contentOrOptions || {};
|
||||
}
|
||||
|
||||
function getPage(optionsOrGlobal: UseModalOptions | boolean): string {
|
||||
if (typeof optionsOrGlobal === 'boolean') {
|
||||
return '';
|
||||
}
|
||||
if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
|
||||
return optionsOrGlobal.page;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modal 函数式调用
|
||||
* @description 需要页面/应用中至少存在一个 <u-modal global /> 或 <u-modal page /> 实例用于承接事件;不影响原 ref 调用方式。
|
||||
*
|
||||
* 支持两种调用方式:
|
||||
* - 应用级 useModal() / useModal(true) / useModal({ global: true })
|
||||
* - 页面级 useModal(false) / useModal({ page: true }) / useModal({ page: 'pageId' }) (pageId 应和页面中 <u-modal page="pageId" /> 的 page 属性一致)
|
||||
*/
|
||||
export function useModal(optionsOrGlobal: UseModalOptions | boolean = true): UseModal {
|
||||
const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
|
||||
const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
|
||||
|
||||
const showEvent = isGlobal
|
||||
? U_MODAL_GLOBAL_EVENT_SHOW
|
||||
: isPage
|
||||
? getEventWithCurrentPage(U_MODAL_EVENT_SHOW, getPage(optionsOrGlobal))
|
||||
: '';
|
||||
const hideEvent = isGlobal
|
||||
? U_MODAL_GLOBAL_EVENT_HIDE
|
||||
: isPage
|
||||
? getEventWithCurrentPage(U_MODAL_EVENT_HIDE, getPage(optionsOrGlobal))
|
||||
: '';
|
||||
const clearLoadingEvent = isGlobal
|
||||
? U_MODAL_GLOBAL_EVENT_CLEAR_LOADING
|
||||
: isPage
|
||||
? getEventWithCurrentPage(U_MODAL_EVENT_CLEAR_LOADING, getPage(optionsOrGlobal))
|
||||
: '';
|
||||
|
||||
function emitShow(payload: UseModalShowOptions) {
|
||||
if (showEvent) {
|
||||
uni?.$emit && uni.$emit(showEvent, payload);
|
||||
}
|
||||
}
|
||||
|
||||
function emitHide() {
|
||||
if (hideEvent) {
|
||||
uni?.$emit && uni.$emit(hideEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function emitClearLoading() {
|
||||
if (clearLoadingEvent) {
|
||||
uni?.$emit && uni.$emit(clearLoadingEvent);
|
||||
}
|
||||
}
|
||||
|
||||
function show(contentOrOptions: string | UseModalShowOptions) {
|
||||
emitShow(normalize(contentOrOptions));
|
||||
}
|
||||
|
||||
function confirm(contentOrOptions: string | UseModalShowOptions) {
|
||||
const options = normalize(contentOrOptions);
|
||||
emitShow({
|
||||
...options,
|
||||
showCancelButton: options.showCancelButton ?? true,
|
||||
showConfirmButton: options.showConfirmButton ?? true
|
||||
});
|
||||
}
|
||||
|
||||
function clearLoading() {
|
||||
emitClearLoading();
|
||||
}
|
||||
|
||||
function close() {
|
||||
emitHide();
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
confirm,
|
||||
close,
|
||||
clearLoading
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { getCurrentInstance, nextTick, ref } from 'vue';
|
||||
|
||||
/**
|
||||
* useRect - 获取元素的位置信息(响应式,原生实现)
|
||||
* @param selector 选择器(如 #id 或 .class)
|
||||
* @param all 是否获取所有匹配元素
|
||||
* @returns rect 响应式的节点信息,refresh 主动刷新方法
|
||||
*/
|
||||
export function useRect(selector: string | null = null, all = false) {
|
||||
const rect = ref<any>(all ? [] : null);
|
||||
const instance = getCurrentInstance();
|
||||
|
||||
async function getRect(realSelector: string | null = null, delay = 0): Promise<any> {
|
||||
realSelector = realSelector || selector;
|
||||
if (!realSelector) return rect.value;
|
||||
await nextTick();
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
uni.createSelectorQuery()
|
||||
.in(instance?.proxy)
|
||||
[all ? 'selectAll' : 'select'](realSelector as string)
|
||||
.boundingClientRect((res: any) => {
|
||||
rect.value = res;
|
||||
resolve(res);
|
||||
})
|
||||
.exec();
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh(selector?: string | null, delay?: number): Promise<any> {
|
||||
return getRect(selector, delay);
|
||||
}
|
||||
|
||||
return {
|
||||
rect,
|
||||
getRect,
|
||||
refresh
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 路由跳转 hooks,基于 route.ts 的 Router 类实现
|
||||
* 提供 Vue Composition API 风格的路由跳转方法
|
||||
*/
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface RouterConfig {
|
||||
type?: string;
|
||||
url?: string;
|
||||
delta?: number;
|
||||
params?: Record<string, any>;
|
||||
animationType?: string;
|
||||
animationDuration?: number;
|
||||
intercept?: boolean;
|
||||
}
|
||||
|
||||
declare const uni: any;
|
||||
|
||||
const config: RouterConfig = {
|
||||
type: 'navigateTo',
|
||||
url: '',
|
||||
delta: 1,
|
||||
params: {},
|
||||
animationType: 'pop-in',
|
||||
animationDuration: 300,
|
||||
intercept: false
|
||||
};
|
||||
|
||||
// 响应式配置,支持动态修改
|
||||
const configRef = ref({ ...config });
|
||||
|
||||
/**
|
||||
* 判断 url 前面是否有 "/",如果没有则加上
|
||||
*/
|
||||
const addRootPath = (url: string): string => {
|
||||
return url[0] === '/' ? url : `/${url}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 整合路由参数
|
||||
*/
|
||||
const mixinParam = (url: string, params: Record<string, any>): string => {
|
||||
url = url && addRootPath(url);
|
||||
let query = '';
|
||||
if (/.*\/.*\?.*=.*/.test(url)) {
|
||||
query = uni.$u.queryParams(params, false);
|
||||
return url + '&' + query;
|
||||
} else {
|
||||
query = uni.$u.queryParams(params);
|
||||
return url + query;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 执行路由跳转
|
||||
*/
|
||||
const openPage = (cfg: RouterConfig): void => {
|
||||
const { url = '', type = '', delta = 1, animationDuration = 300 } = cfg;
|
||||
if (type === 'navigateTo' || type === 'to') {
|
||||
uni.navigateTo({ url, animationDuration });
|
||||
}
|
||||
if (type === 'redirectTo' || type === 'redirect') {
|
||||
uni.redirectTo({ url });
|
||||
}
|
||||
if (type === 'switchTab' || type === 'tab') {
|
||||
uni.switchTab({ url });
|
||||
}
|
||||
if (type === 'reLaunch' || type === 'launch') {
|
||||
uni.reLaunch({ url });
|
||||
}
|
||||
if (type === 'navigateBack' || type === 'back') {
|
||||
uni.navigateBack({ delta });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 路由跳转主方法
|
||||
*/
|
||||
const route = async (options: string | RouterConfig = {}, params: Record<string, any> = {}): Promise<void> => {
|
||||
let mergeConfig: RouterConfig = {};
|
||||
|
||||
if (typeof options === 'string') {
|
||||
mergeConfig.url = mixinParam(options, params);
|
||||
mergeConfig.type = 'navigateTo';
|
||||
} else {
|
||||
mergeConfig = uni.$u.deepMerge(configRef.value, options);
|
||||
mergeConfig.url = mixinParam(options.url || '', options.params || {});
|
||||
}
|
||||
|
||||
if (params.intercept !== undefined) {
|
||||
configRef.value.intercept = params.intercept;
|
||||
}
|
||||
|
||||
mergeConfig.params = params;
|
||||
mergeConfig = uni.$u.deepMerge(configRef.value, mergeConfig);
|
||||
|
||||
if (uni.$u.routeIntercept && typeof uni.$u.routeIntercept === 'function') {
|
||||
const isNext = await new Promise<boolean>(resolve => {
|
||||
uni.$u.routeIntercept(mergeConfig, resolve);
|
||||
});
|
||||
isNext && openPage(mergeConfig);
|
||||
} else {
|
||||
openPage(mergeConfig);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 跳转到指定页面
|
||||
*/
|
||||
const to = (url: string, params?: Record<string, any>): Promise<void> => {
|
||||
return route({ url, type: 'navigateTo' }, params || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭当前页面,跳转到指定页面
|
||||
*/
|
||||
const redirect = (url: string, params?: Record<string, any>): Promise<void> => {
|
||||
return route({ url, type: 'redirectTo' }, params || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 跳转到 tabBar 页面
|
||||
*/
|
||||
const tab = (url: string, params?: Record<string, any>): Promise<void> => {
|
||||
return route({ url, type: 'switchTab' }, params || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭所有页面,跳转到指定页面
|
||||
*/
|
||||
const reLaunch = (url: string, params?: Record<string, any>): Promise<void> => {
|
||||
return route({ url, type: 'reLaunch' }, params || {});
|
||||
};
|
||||
|
||||
/**
|
||||
* 返回上一页
|
||||
*/
|
||||
const back = (delta: number = 1): Promise<void> => {
|
||||
return route({ type: 'navigateBack', delta });
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置默认路由配置
|
||||
*/
|
||||
const setConfig = (newConfig: Partial<RouterConfig>): void => {
|
||||
configRef.value = uni.$u.deepMerge(configRef.value, newConfig);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前路由配置
|
||||
*/
|
||||
const getConfig = (): RouterConfig => {
|
||||
return { ...configRef.value };
|
||||
};
|
||||
|
||||
export function useRouter() {
|
||||
return {
|
||||
// 核心跳转方法
|
||||
route,
|
||||
// 便捷方法
|
||||
to,
|
||||
redirect,
|
||||
tab,
|
||||
reLaunch,
|
||||
back,
|
||||
// 配置方法
|
||||
setConfig,
|
||||
getConfig
|
||||
};
|
||||
}
|
||||
|
||||
export default useRouter;
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 主题管理 composable
|
||||
* 提供主题切换、持久化、CSS 变量注入、暗黑模式等功能
|
||||
*
|
||||
* 使用方式:
|
||||
* const { currentTheme, themes, setTheme, getDarkMode, setDarkMode, isInDarkMode, getAvailableThemes, initTheme } = useTheme()
|
||||
*/
|
||||
|
||||
import type { DarkMode, Theme } from '../../types/global';
|
||||
import configProvider, { type DefaultThemeConfig } from '../util/config-provider';
|
||||
import { defaultThemes } from '../config/theme-tokens';
|
||||
|
||||
const THEME_STORAGE_KEY = 'uview-pro-theme';
|
||||
const DARK_MODE_STORAGE_KEY = 'uview-pro-dark-mode';
|
||||
const themesRef = configProvider.themesRef;
|
||||
const currentTheme = configProvider.currentThemeRef;
|
||||
const darkModeRef = configProvider.darkModeRef;
|
||||
|
||||
/**
|
||||
* 保存主题到 Storage
|
||||
*/
|
||||
function saveThemeToStorage(themeName: string) {
|
||||
try {
|
||||
uni.setStorageSync(THEME_STORAGE_KEY, themeName);
|
||||
} catch (e) {
|
||||
console.warn('[useTheme] failed to write storage', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存暗黑模式设置到 Storage
|
||||
*/
|
||||
function saveDarkModeToStorage(mode: DarkMode) {
|
||||
try {
|
||||
uni.setStorageSync(DARK_MODE_STORAGE_KEY, mode);
|
||||
} catch (e) {
|
||||
console.warn('[useTheme] failed to write storage', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
*/
|
||||
function setTheme(themeName: string) {
|
||||
configProvider.setTheme(themeName);
|
||||
currentTheme.value = configProvider.getCurrentTheme();
|
||||
saveThemeToStorage(themeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前主题
|
||||
*/
|
||||
function getCurrentTheme(): Theme | null {
|
||||
return currentTheme.value || configProvider.getCurrentTheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用主题
|
||||
*/
|
||||
function getAvailableThemes() {
|
||||
return configProvider.getThemes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化主题系统
|
||||
* @param themes 可选的主题列表,如果未提供则尝试从 uni.$u.themes 读取
|
||||
* @param defaultConfig 可选的默认主题配置,支持字符串(默认主题名)或对象({ defaultTheme?, defaultDarkMode? })
|
||||
*/
|
||||
export function initTheme(themes?: Theme[], defaultConfig?: string | DefaultThemeConfig, isForce?: boolean) {
|
||||
// 如果有传入主题列表,使用传入的
|
||||
if (Array.isArray(themes) && themes.length > 0) {
|
||||
configProvider.initTheme(themes, defaultConfig, isForce);
|
||||
return;
|
||||
}
|
||||
|
||||
// // 若已通过插件或其他方式完成初始化,则不再覆盖,最多按需切换默认主题
|
||||
// const existingThemes = configProvider.getThemes();
|
||||
// if (existingThemes.length > 0) {
|
||||
// if (typeof defaultConfig === 'string') {
|
||||
// configProvider.setTheme(defaultConfig);
|
||||
// } else if (defaultConfig && typeof defaultConfig === 'object' && (defaultConfig as any).defaultTheme) {
|
||||
// configProvider.setTheme(defaultConfig.defaultTheme);
|
||||
// } else if (!configProvider.getCurrentTheme()) {
|
||||
// configProvider.setTheme(existingThemes[0].name);
|
||||
// } else {
|
||||
// // 触发一次 apply,便于初始化 CSS 变量
|
||||
// configProvider.setTheme(configProvider.getCurrentTheme()!.name);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // 初始化 configProvider(如果运行时提供了内置主题)
|
||||
// try {
|
||||
// const builtin = (typeof uni !== 'undefined' && (uni as any).$u && (uni as any).$u.themes) || [];
|
||||
// if (Array.isArray(builtin) && builtin.length > 0) {
|
||||
// configProvider.initTheme(builtin as Theme[], defaultConfig);
|
||||
// return;
|
||||
// }
|
||||
// } catch (e) {
|
||||
// // ignore
|
||||
// }
|
||||
|
||||
// 回退到内置默认主题
|
||||
configProvider.initTheme(defaultThemes as Theme[], defaultConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化暗黑模式
|
||||
* @param darkMode 暗黑模式设置
|
||||
* @param isForce 是否强制初始化
|
||||
*/
|
||||
function initDarkMode(darkMode?: DarkMode, isForce?: boolean) {
|
||||
configProvider.initDarkMode(darkMode, isForce);
|
||||
}
|
||||
/**
|
||||
* 获取当前暗黑模式设置
|
||||
*/
|
||||
function getDarkMode(): DarkMode {
|
||||
return configProvider.getDarkMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置暗黑模式
|
||||
* @param mode 'auto' (跟随系统) | 'light' (强制亮色) | 'dark' (强制暗黑)
|
||||
*/
|
||||
function setDarkMode(mode: DarkMode) {
|
||||
configProvider.setDarkMode(mode);
|
||||
darkModeRef.value = mode;
|
||||
saveDarkModeToStorage(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前是否处于暗黑模式
|
||||
*/
|
||||
function isInDarkMode(): boolean {
|
||||
return configProvider.isInDarkMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换暗黑模式(在当前模式的基础上切换)
|
||||
*/
|
||||
function toggleDarkMode() {
|
||||
const current = getDarkMode();
|
||||
const nextMode = current === 'dark' ? 'light' : 'dark';
|
||||
setDarkMode(nextMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用主题的 composable
|
||||
* 返回所有主题相关的响应式引用和方法
|
||||
*/
|
||||
export function useTheme() {
|
||||
return {
|
||||
// 响应式引用
|
||||
currentTheme,
|
||||
themes: themesRef,
|
||||
darkMode: darkModeRef,
|
||||
cssVars: configProvider.cssVarsRef,
|
||||
|
||||
// 主题相关方法
|
||||
initTheme,
|
||||
setTheme,
|
||||
getCurrentTheme,
|
||||
getAvailableThemes,
|
||||
|
||||
// 暗黑模式相关方法
|
||||
initDarkMode,
|
||||
getDarkMode,
|
||||
setDarkMode,
|
||||
isInDarkMode,
|
||||
toggleDarkMode
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export function useThrottle(delay: number = 500) {
|
||||
let previous: number = 0;
|
||||
// 节流函数
|
||||
function throttle(callback: () => void, throttleTime?: number) {
|
||||
throttleTime = throttleTime || delay;
|
||||
let now = Date.now();
|
||||
if (now - previous > throttleTime) {
|
||||
callback();
|
||||
previous = now;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
throttle
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
U_TOAST_EVENT_HIDE,
|
||||
U_TOAST_EVENT_SHOW,
|
||||
U_TOAST_GLOBAL_EVENT_HIDE,
|
||||
U_TOAST_GLOBAL_EVENT_SHOW,
|
||||
getEventWithCurrentPage,
|
||||
type ToastPayload
|
||||
} from '../../components/u-toast/service';
|
||||
import type { ThemeType } from '../../types/global';
|
||||
|
||||
export type UseToastShowOptions = ToastPayload;
|
||||
|
||||
export type UseToast = {
|
||||
/**
|
||||
* 显示 toast
|
||||
* - show('文本')
|
||||
* - show({ title, type, duration, ... })
|
||||
*/
|
||||
show: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
/** 关闭 toast */
|
||||
close: () => void;
|
||||
/** 成功 */
|
||||
success: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
/** 错误 */
|
||||
error: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
/** 警告 */
|
||||
warning: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
/** 信息 */
|
||||
info: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
/** 加载中(默认常驻,需 close 关闭) */
|
||||
loading: (titleOrOptions: string | UseToastShowOptions) => void;
|
||||
};
|
||||
|
||||
export type UseToastOptions = {
|
||||
/** 是否使用全局根部 <u-toast global />,默认 true;为 false 时走页面级 <u-toast /> */
|
||||
global?: boolean;
|
||||
/** 是否使用页面级 <u-toast page /> 或某个页面的 <u-toast page="pageId" /> */
|
||||
page?: boolean | string;
|
||||
};
|
||||
|
||||
function normalize(titleOrOptions: string | UseToastShowOptions): UseToastShowOptions {
|
||||
if (typeof titleOrOptions === 'string') return { title: titleOrOptions };
|
||||
return titleOrOptions || {};
|
||||
}
|
||||
|
||||
function getPage(optionsOrGlobal: UseToastOptions | boolean): string {
|
||||
if (typeof optionsOrGlobal === 'boolean') {
|
||||
return '';
|
||||
}
|
||||
if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
|
||||
return optionsOrGlobal.page;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Toast 函数式调用
|
||||
* @description 需要页面/应用中至少存在一个 <u-toast global /> 或 <u-toast page /> 实例用于承接事件;不影响原 ref 调用方式。
|
||||
*
|
||||
* 支持两种调用方式:
|
||||
* - 应用级 useToast() / useToast(true) / useToast({ global: true })
|
||||
* - 页面级 useToast(false) / useToast({ page: true }) / useToast({ page: 'pageId' }) (pageId 应和页面中 <u-toast page="pageId" /> 的 page 属性一致)
|
||||
*/
|
||||
export function useToast(optionsOrGlobal: UseToastOptions | boolean = true): UseToast {
|
||||
const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
|
||||
const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
|
||||
const showEvent = isGlobal
|
||||
? U_TOAST_GLOBAL_EVENT_SHOW
|
||||
: isPage
|
||||
? getEventWithCurrentPage(U_TOAST_EVENT_SHOW, getPage(optionsOrGlobal))
|
||||
: '';
|
||||
const hideEvent = isGlobal
|
||||
? U_TOAST_GLOBAL_EVENT_HIDE
|
||||
: isPage
|
||||
? getEventWithCurrentPage(U_TOAST_EVENT_HIDE, getPage(optionsOrGlobal))
|
||||
: '';
|
||||
|
||||
function emitShow(payload: UseToastShowOptions) {
|
||||
if (showEvent) {
|
||||
uni?.$emit && uni.$emit(showEvent, payload);
|
||||
}
|
||||
}
|
||||
|
||||
function emitHide() {
|
||||
if (hideEvent) {
|
||||
uni?.$emit && uni.$emit(hideEvent);
|
||||
}
|
||||
}
|
||||
function show(titleOrOptions: string | UseToastShowOptions) {
|
||||
emitShow(normalize(titleOrOptions));
|
||||
}
|
||||
|
||||
function close() {
|
||||
emitHide();
|
||||
}
|
||||
|
||||
function withType(type: ThemeType, titleOrOptions: string | UseToastShowOptions) {
|
||||
const options = normalize(titleOrOptions);
|
||||
emitShow({ ...options, type });
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
close,
|
||||
success: (v: any) => withType('success', v),
|
||||
error: (v: any) => withType('error', v),
|
||||
warning: (v: any) => withType('warning', v),
|
||||
info: (v: any) => withType('info', v),
|
||||
loading: (v: any) => {
|
||||
const options = normalize(v);
|
||||
// loading 通常需要常驻,除非用户显式传 duration
|
||||
emitShow({ ...options, loading: true, duration: options.duration ?? 0 });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user