chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
import { ref, onMounted, onUnmounted, Ref } from 'vue'
import { EventBindResponseActionEnum } from '@/types/enums/agent'
import { checkPathParams, fillPathParams, objectToQueryString } from '@/utils/common.uts'
import { EventBindConfig, ShowPagePreviewOptions } from '@/types/interfaces/agent'
import { API_BASE_URL } from '@/constants/config'
import { handleExternalLink } from '@/utils/system.uts'
import { t } from '@/utils/i18n'
// ============ 工具方法 ============
const showError = (msg: string): void => {
uni.showToast({
title: msg,
icon: 'none'
})
}
// ============ 防重复触发管理器 ============
/**
* 事件防重复触发管理器
* 用于防止短时间内重复触发相同的事件
*/
export class EventDedupManager {
private handledEvents: Set<string> = new Set()
private timeout: number = 1000
constructor(timeout?: number) {
if (timeout !== undefined) {
this.timeout = timeout
}
}
/**
* 检查事件是否已经被处理
* @param eventKey 事件键
* @returns 是否已处理true 表示已处理,应该跳过)
*/
isDuplicate(eventKey: string): boolean {
if (this.handledEvents.has(eventKey)) {
return true
}
this.handledEvents.add(eventKey)
setTimeout(() => {
this.handledEvents.delete(eventKey)
}, this.timeout)
return false
}
/**
* 清除所有记录
*/
clear(): void {
this.handledEvents.clear()
}
}
// ============ 导出的事件处理函数 ============
/**
* 处理消息事件点击
* 可以在任何地方独立调用此函数来处理事件
*
* @param eventType 事件类型标识
* @param dataStr 事件数据JSON 字符串)
* @param eventBindConfig 事件绑定配置
* @param showPagePreview 页面预览回调函数
* @param dedupManager 防重复触发管理器(可选)
*/
export const handleMessageEventClick = (
eventType: string,
dataStr: string,
eventBindConfig: EventBindConfig | undefined,
showPagePreview?: (opts: ShowPagePreviewOptions) => void,
dedupManager?: EventDedupManager
): void => {
const eventKey = `${eventType}-${dataStr}`
// 防重复触发
if (dedupManager && dedupManager.isDuplicate(eventKey)) {
return
}
// 解析数据
let parsedData: Record<string, any> = {}
try {
parsedData = JSON.parse(dataStr)
} catch (err) {
console.error('[Event Handler] 数据解析失败:', err)
return
}
// 查找事件配置
const eventConfig = eventBindConfig?.eventConfigs?.find(
(cfg) => cfg.identification === eventType
)
if (!eventConfig) {
console.warn(`[Event Handler] 未找到事件配置: ${eventType}`)
return
}
// === 根据类型执行 ===
switch (eventConfig.type) {
case EventBindResponseActionEnum.Page: {
if (!eventConfig.pageUrl) {
showError(t("Mobile.ButtonWrapper.pagePathConfigError"))
return
}
const pathParams: Record<string, any> = {}
const params: Record<string, any> = {}
eventConfig.args?.forEach((arg) => {
if (arg.inputType === 'Path' && arg.name)
pathParams[arg.name] = parsedData[arg.name] ?? arg.bindValue
if (arg.inputType === 'Query' && arg.name)
params[arg.name] = parsedData[arg.name] ?? arg.bindValue
})
if (checkPathParams(eventConfig.pageUrl, pathParams)) {
const pageUrl = fillPathParams(eventConfig.pageUrl, pathParams)
const fullUri = eventConfig.basePath? `${eventConfig.basePath}${pageUrl}`: pageUrl
// 构建查询字符串
const queryString = objectToQueryString(params)
showPagePreview?.({
uri: queryString ? `${fullUri}?${queryString}` : fullUri,
})
} else {
showError(t("Mobile.ButtonWrapper.pagePathParamConfigError"))
}
break
}
case EventBindResponseActionEnum.Link: {
handleExternalLink(eventConfig.url)
break
}
default:
console.warn(`[Event Handler] 未知的事件类型: ${eventConfig.type}`)
}
}
// ============ 主 Hook ============
export const useMessageEventDelegate = (
containerRef: Ref<HTMLElement | null>,
eventBindConfig?: EventBindConfig,
showPagePreview?: (opts: ShowPagePreviewOptions) => void
) => {
// 创建防重复触发管理器
const dedupManager = new EventDedupManager(1000)
// 👉 点击事件处理(内部封装)
const handleEventClick = (eventType: string, dataStr: string): void => {
handleMessageEventClick(
eventType,
dataStr,
eventBindConfig.value,
showPagePreview,
dedupManager
)
}
// 👉 监听容器点击事件仅H5平台
// #ifdef H5 || WEB
const handleClick = (e: MouseEvent): void => {
const target = e.target as HTMLElement
const eventElement = target.closest('.event') as HTMLElement | null
if (eventElement) {
e.preventDefault()
e.stopPropagation()
const eventType = eventElement.getAttribute('event-type')
const dataStr = eventElement.getAttribute('data')
if (eventType && dataStr) {
handleEventClick(eventType, dataStr)
} else {
console.warn('[Event Delegate] 缺少必要属性', { eventType, dataStr })
}
}
}
// 👉 生命周期挂载与清理
onMounted(() => {
const container = containerRef.value
if (container) {
container.addEventListener('click', handleClick)
}
})
onUnmounted(() => {
const container = containerRef.value
if (container) {
container.removeEventListener('click', handleClick)
}
})
// #endif
}

View File

@@ -0,0 +1,161 @@
/**
* 页面唤醒检测 Hook
*
* 用于检测页面从后台恢复到前台的场景,适配 H5 和小程序
*
* 使用场景:
* - H5页面切换到后台visibilitychange或切换标签页时触发
* - 小程序:需要配合 onShow/onHide 生命周期使用
*
* 使用方式:
* ```typescript
* const { isHidden, cleanup } = usePageResume({
* onResume: () => {
* console.log('页面恢复');
* // 执行恢复逻辑
* },
* onHide: () => {
* console.log('页面进入后台');
* }
* });
*
* // 在 onUnmounted 中调用 cleanup()
* ```
*/
import { ref, onMounted, onUnmounted } from 'vue';
export interface PageResumeOptions {
/** 页面恢复时的回调 */
onResume: () => void;
/** 页面进入后台时的回调(可选) */
onHide?: () => void;
/** 最小隐藏时间(毫秒),超过此时间才触发恢复回调,默认 3000ms */
minHiddenDuration?: number;
}
export interface PageResumeResult {
/** 页面是否处于隐藏状态 */
isHidden: Ref<boolean>;
/** 页面隐藏时间戳 */
hiddenTimestamp: Ref<number>;
/** 清理函数,用于移除事件监听 */
cleanup: () => void;
/** 手动触发恢复检测(用于小程序 onShow */
triggerResume: () => void;
/** 手动触发隐藏检测(用于小程序 onHide */
triggerHide: () => void;
}
/**
* 页面唤醒检测 Hook
*/
export function usePageResume(options: PageResumeOptions): PageResumeResult {
const { onResume, onHide, minHiddenDuration = 3000 } = options;
// 页面是否隐藏
const isHidden = ref<boolean>(false);
// 页面隐藏时间戳
const hiddenTimestamp = ref<number>(0);
// 是否已初始化
let isInitialized = false;
/**
* 处理页面隐藏
*/
const handleHide = (): void => {
console.log('[PageResume] 页面进入后台');
isHidden.value = true;
hiddenTimestamp.value = Date.now();
onHide?.();
};
/**
* 处理页面恢复
*/
const handleResume = (): void => {
if (!isHidden.value) {
return;
}
const now = Date.now();
const hiddenDuration = now - hiddenTimestamp.value;
console.log(`[PageResume] 页面恢复,隐藏时长: ${hiddenDuration}ms`);
// 只有隐藏时间超过阈值才触发恢复回调
if (hiddenDuration >= minHiddenDuration) {
console.log('[PageResume] 超过最小隐藏时长,触发恢复回调');
onResume();
}
// 重置状态
isHidden.value = false;
hiddenTimestamp.value = 0;
};
/**
* H5 visibilitychange 事件处理
*/
const handleVisibilityChange = (): void => {
// #ifdef H5 || WEB
if (document.hidden) {
handleHide();
} else {
handleResume();
}
// #endif
};
/**
* 初始化监听
*/
const init = (): void => {
if (isInitialized) {
return;
}
// #ifdef H5 || WEB
document.addEventListener('visibilitychange', handleVisibilityChange);
console.log('[PageResume] H5 visibilitychange 监听已初始化');
// #endif
isInitialized = true;
};
/**
* 清理监听
*/
const cleanup = (): void => {
// #ifdef H5 || WEB
if (isInitialized) {
document.removeEventListener('visibilitychange', handleVisibilityChange);
console.log('[PageResume] H5 visibilitychange 监听已移除');
}
// #endif
isInitialized = false;
isHidden.value = false;
hiddenTimestamp.value = 0;
};
// 在组件挂载时初始化
onMounted(() => {
init();
});
// 在组件卸载时清理
onUnmounted(() => {
cleanup();
});
return {
isHidden,
hiddenTimestamp,
cleanup,
// 手动触发(用于小程序)
triggerResume: handleResume,
triggerHide: handleHide,
};
}
export default usePageResume;