Files
qiming/qiming-mobile/subpackages/hooks/useMessageEventDelegate.uts

204 lines
5.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}