@uxda/appkit 4.2.93 → 4.2.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * 埋点点击指令
3
+ * 支持自定义点击内容埋点
4
+ */
5
+
6
+ import type { App, Directive } from 'vue'
7
+ import { trackingSDK } from '../tracking-sdk'
8
+
9
+ // 指令值类型
10
+ interface TrackClickValue {
11
+ event?: string // 事件名称
12
+ data?: Record<string, unknown> // 自定义数据
13
+ elementId?: string // 元素ID
14
+ elementText?: string // 元素文本
15
+ preventDefault?: boolean // 是否阻止默认行为
16
+ }
17
+
18
+ /**
19
+ * 获取元素信息
20
+ */
21
+ function getElementInfo(el: HTMLElement): { id?: string; className?: string; text?: string } {
22
+ return {
23
+ id: el.id,
24
+ className: el.className,
25
+ text: (el.textContent || el.innerText || '').trim(),
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 获取点击位置
31
+ */
32
+ function getClickPosition(event: Event): { x: number; y: number } {
33
+ const e = event as MouseEvent | TouchEvent
34
+ if ('touches' in e && e.touches.length > 0) {
35
+ return {
36
+ x: e.touches[0].clientX,
37
+ y: e.touches[0].clientY,
38
+ }
39
+ } else if ('clientX' in e) {
40
+ return {
41
+ x: e.clientX,
42
+ y: e.clientY,
43
+ }
44
+ }
45
+ return { x: 0, y: 0 }
46
+ }
47
+
48
+ /**
49
+ * 绑定点击事件
50
+ */
51
+ function bindClickEvent(el: HTMLElement, binding: any) {
52
+ // 先移除旧的事件监听器(必须使用相同的参数)
53
+ const oldHandler = (el as any).__trackClickHandler
54
+ const oldOptions = (el as any).__trackClickOptions
55
+ if (oldHandler) {
56
+ el.removeEventListener('click', oldHandler, oldOptions)
57
+ delete (el as any).__trackClickHandler
58
+ delete (el as any).__trackClickOptions
59
+ }
60
+
61
+ const value = binding.value as TrackClickValue | string
62
+
63
+ // 处理字符串类型的值
64
+ const config: TrackClickValue = typeof value === 'string' ? { event: value } : value || {}
65
+
66
+ const { data = {}, elementId, elementText, preventDefault = false } = config
67
+
68
+ // 获取元素信息
69
+ const elementInfo = getElementInfo(el)
70
+ const finalElementId = elementId || elementInfo.id
71
+ const finalElementClass = elementInfo.className
72
+ const finalElementText = elementText || elementInfo.text
73
+
74
+ // 点击事件处理
75
+ const handleClick = (clickEvent: Event) => {
76
+ if (preventDefault) {
77
+ clickEvent.preventDefault()
78
+ }
79
+
80
+ // 获取点击位置
81
+ const position = getClickPosition(clickEvent)
82
+
83
+ // 发送点击埋点
84
+ trackingSDK.trackClick(finalElementId || finalElementClass, finalElementText)
85
+
86
+ // 发送自定义事件埋点
87
+ if (config.event && config.event !== 'click') {
88
+ trackingSDK.trackCustom(config.event, {
89
+ elementId: finalElementId || finalElementClass,
90
+ elementText: finalElementText,
91
+ position,
92
+ ...data,
93
+ })
94
+ }
95
+ }
96
+
97
+ // 事件监听器选项
98
+ const eventOptions = { capture: true, passive: !preventDefault }
99
+
100
+ // 绑定事件(使用捕获阶段,确保能捕获到事件)
101
+ el.addEventListener('click', handleClick, eventOptions)
102
+
103
+ // 存储事件处理函数和选项,用于卸载时移除
104
+ ;(el as any).__trackClickHandler = handleClick
105
+ ;(el as any).__trackClickOptions = eventOptions
106
+ }
107
+
108
+ /**
109
+ * 埋点点击指令实现
110
+ */
111
+ const trackClickDirective: Directive = {
112
+ mounted(el: HTMLElement, binding) {
113
+ // 使用 nextTick 确保 DOM 完全渲染后再绑定
114
+ setTimeout(() => {
115
+ bindClickEvent(el, binding)
116
+ }, 0)
117
+ },
118
+
119
+ updated(el: HTMLElement, binding) {
120
+ bindClickEvent(el, binding)
121
+ },
122
+
123
+ unmounted(el: HTMLElement) {
124
+ // 移除事件监听器
125
+ const handler = (el as any).__trackClickHandler
126
+ const options = (el as any).__trackClickOptions
127
+ if (handler) {
128
+ el.removeEventListener('click', handler, options)
129
+ delete (el as any).__trackClickHandler
130
+ delete (el as any).__trackClickOptions
131
+ }
132
+ },
133
+ }
134
+
135
+ /**
136
+ * 安装埋点点击指令
137
+ */
138
+ export function installTrackClickDirective(app: App) {
139
+ app.directive('track-click', trackClickDirective)
140
+ }
141
+
142
+ export default trackClickDirective
@@ -0,0 +1,50 @@
1
+ /**
2
+ * 埋点页面访问指令
3
+ * 支持页面访问埋点
4
+ */
5
+
6
+ import type { App, Directive } from 'vue'
7
+ import { trackingSDK } from '../tracking-sdk'
8
+
9
+ // 指令值类型
10
+ interface TrackPageValue {
11
+ pageTitle?: string // 页面标题
12
+ customData?: Record<string, unknown> // 自定义数据
13
+ trackStay?: boolean // 是否跟踪停留时间
14
+ }
15
+
16
+ /**
17
+ * 埋点页面访问指令实现
18
+ */
19
+ const trackPageDirective: Directive = {
20
+ mounted(el: HTMLElement, binding) {
21
+ const value = binding.value as TrackPageValue | string
22
+
23
+ // 处理字符串类型的值
24
+ const config: TrackPageValue = typeof value === 'string' ? { pageTitle: value } : value || {}
25
+
26
+ const { pageTitle, customData = {}, trackStay = true } = config
27
+
28
+ // 记录页面访问
29
+ trackingSDK.trackPageView(pageTitle)
30
+
31
+ // 如果有自定义数据,发送自定义事件
32
+ if (Object.keys(customData).length > 0) {
33
+ trackingSDK.trackCustom('page_view_custom', customData)
34
+ }
35
+
36
+ // 如果启用停留时间跟踪,记录开始时间
37
+ if (trackStay) {
38
+ ;(el as any).__trackPageStartTime = Date.now()
39
+ }
40
+ },
41
+ }
42
+
43
+ /**
44
+ * 安装埋点页面访问指令
45
+ */
46
+ export function installTrackPageDirective(app: App) {
47
+ app.directive('track-page', trackPageDirective)
48
+ }
49
+
50
+ export default trackPageDirective
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 埋点滚动指令
3
+ * 支持滚动事件埋点
4
+ */
5
+
6
+ import type { App, Directive } from 'vue'
7
+ import { trackingSDK } from '../tracking-sdk'
8
+
9
+ // 指令值类型
10
+ interface TrackScrollValue {
11
+ threshold?: number // 滚动阈值(0-1)
12
+ data?: Record<string, unknown> // 自定义数据
13
+ trackDirection?: boolean // 是否跟踪滚动方向
14
+ }
15
+
16
+ /**
17
+ * 埋点滚动指令实现
18
+ */
19
+ const trackScrollDirective: Directive = {
20
+ mounted(el: HTMLElement, binding) {
21
+ const value = binding.value as TrackScrollValue | boolean
22
+
23
+ // 处理布尔值或配置对象
24
+ const config: TrackScrollValue =
25
+ typeof value === 'boolean' ? { threshold: 0.5, trackDirection: value } : value || {}
26
+
27
+ const { threshold = 0.5, data = {}, trackDirection = false } = config
28
+
29
+ let hasTracked = false
30
+ let lastScrollTop = 0
31
+
32
+ const handleScroll = () => {
33
+ if (hasTracked) return
34
+
35
+ const rect = el.getBoundingClientRect()
36
+ const windowHeight = window.innerHeight
37
+ const elementTop = rect.top
38
+ const elementHeight = rect.height
39
+
40
+ // 计算元素可见比例
41
+ const visibleHeight = Math.min(elementHeight, windowHeight - Math.max(0, elementTop))
42
+ const visibleRatio = visibleHeight / elementHeight
43
+
44
+ if (visibleRatio >= threshold) {
45
+ hasTracked = true
46
+
47
+ // 获取滚动方向
48
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop
49
+ const scrollDirection = scrollTop > lastScrollTop ? 'down' : 'up'
50
+ lastScrollTop = scrollTop
51
+
52
+ trackingSDK.trackCustom('scroll_into_view', {
53
+ elementId: el.id,
54
+ visibleRatio,
55
+ scrollDirection: trackDirection ? scrollDirection : undefined,
56
+ scrollTop,
57
+ ...data,
58
+ })
59
+ }
60
+ }
61
+
62
+ // 使用 Intersection Observer 或 scroll 事件
63
+ if ('IntersectionObserver' in window) {
64
+ const observer = new IntersectionObserver(
65
+ (entries) => {
66
+ entries.forEach((entry) => {
67
+ if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
68
+ hasTracked = true
69
+ trackingSDK.trackCustom('scroll_into_view', {
70
+ elementId: el.id,
71
+ intersectionRatio: entry.intersectionRatio,
72
+ ...data,
73
+ })
74
+ observer.unobserve(el)
75
+ }
76
+ })
77
+ },
78
+ { threshold }
79
+ )
80
+
81
+ observer.observe(el)
82
+ ;(el as any).__scrollObserver = observer
83
+ } else {
84
+ // 降级到 scroll 事件
85
+ if (typeof window !== 'undefined') {
86
+ window.addEventListener('scroll', handleScroll, { passive: true })
87
+ ;(el as any).__scrollHandler = handleScroll
88
+ }
89
+ }
90
+ },
91
+
92
+ unmounted(el: HTMLElement) {
93
+ // 清理 Intersection Observer
94
+ const observer = (el as any).__scrollObserver
95
+ if (observer) {
96
+ observer.disconnect()
97
+ delete (el as any).__scrollObserver
98
+ }
99
+
100
+ // 清理 scroll 事件
101
+ const handler = (el as any).__scrollHandler
102
+ if (handler && typeof window !== 'undefined') {
103
+ window.removeEventListener('scroll', handler)
104
+ delete (el as any).__scrollHandler
105
+ }
106
+ },
107
+ }
108
+
109
+ /**
110
+ * 安装埋点滚动指令
111
+ */
112
+ export function installTrackScrollDirective(app: App) {
113
+ app.directive('track-scroll', trackScrollDirective)
114
+ }
115
+
116
+ export default trackScrollDirective
@@ -0,0 +1,179 @@
1
+ /**
2
+ * 埋点搜索指令
3
+ * 支持搜索行为埋点
4
+ */
5
+
6
+ import type { App, Directive } from 'vue'
7
+ import { trackingSDK } from '../tracking-sdk'
8
+ import { debounce } from 'lodash-es'
9
+
10
+ // 指令值类型
11
+ interface TrackSearchValue {
12
+ event?: string // 事件名称
13
+ data?: Record<string, unknown> // 自定义数据
14
+ elementId?: string // 元素ID
15
+ trackInput?: boolean // 是否跟踪输入事件
16
+ trackSubmit?: boolean // 是否跟踪提交事件
17
+ debounceTime?: number // 防抖时间(毫秒)
18
+ minLength?: number // 最小搜索长度
19
+ }
20
+
21
+ /**
22
+ * 获取搜索元素信息
23
+ */
24
+ function getSearchElementInfo(el: HTMLElement): {
25
+ id?: string
26
+ className?: string
27
+ placeholder?: string
28
+ } {
29
+ let placeholder
30
+ if ((el as HTMLInputElement).placeholder) {
31
+ placeholder = (el as HTMLInputElement).placeholder
32
+ } else if (el.querySelector('input')) {
33
+ placeholder = (el.querySelector('input') as HTMLInputElement).placeholder
34
+ }
35
+
36
+ return {
37
+ id: el.id,
38
+ className: el.className,
39
+ placeholder: placeholder || '',
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 埋点搜索指令实现
45
+ */
46
+ const trackSearchDirective: Directive = {
47
+ mounted(el: HTMLElement, binding) {
48
+ const value = binding.value as TrackSearchValue | string
49
+
50
+ // 处理字符串类型的值
51
+ const config: TrackSearchValue = typeof value === 'string' ? { event: value } : value || {}
52
+
53
+ const {
54
+ event = 'search',
55
+ data = {},
56
+ elementId,
57
+ trackInput = true,
58
+ trackSubmit = true,
59
+ debounceTime = 500,
60
+ minLength = 1,
61
+ } = config
62
+
63
+ // 获取元素信息
64
+ const elementInfo = getSearchElementInfo(el)
65
+ const finalElementId = elementId || elementInfo.id
66
+ const finalElementClass = elementInfo.className
67
+
68
+ // 输入事件处理
69
+ const handleInput = (event: Event) => {
70
+ const target = event.target as HTMLInputElement
71
+ const searchValue = target.value.trim()
72
+
73
+ // 检查最小长度
74
+ if (searchValue.length < minLength) {
75
+ return
76
+ }
77
+
78
+ // 发送搜索输入埋点
79
+ trackingSDK.trackCustom('search_input', {
80
+ elementId: finalElementId || finalElementClass,
81
+ searchValue,
82
+ searchLength: searchValue.length,
83
+ placeholder: elementInfo.placeholder,
84
+ ...data,
85
+ })
86
+ }
87
+
88
+ // 提交事件处理
89
+ const handleSubmit = (event: Event) => {
90
+ const target = event.target as HTMLInputElement
91
+ const searchValue = target.value.trim()
92
+
93
+ // 检查最小长度
94
+ if (searchValue.length < minLength) {
95
+ return
96
+ }
97
+
98
+ // 发送搜索提交埋点
99
+ trackingSDK.trackCustom('search_submit', {
100
+ elementId: finalElementId || finalElementClass,
101
+ searchValue,
102
+ searchLength: searchValue.length,
103
+ placeholder: elementInfo.placeholder,
104
+ ...data,
105
+ })
106
+ }
107
+
108
+ // 创建防抖的输入处理函数
109
+ const debouncedHandleInput = debounce(handleInput, debounceTime || 500)
110
+
111
+ // 绑定事件
112
+ if (trackInput) {
113
+ el.addEventListener('input', debouncedHandleInput, { passive: true })
114
+ ;(el as any).__trackSearchInputHandler = debouncedHandleInput
115
+ }
116
+
117
+ if (trackSubmit) {
118
+ el.addEventListener('keydown', (event: KeyboardEvent) => {
119
+ if (event.key === 'Enter') {
120
+ handleSubmit(event)
121
+ }
122
+ })
123
+ ;(el as any).__trackSearchSubmitHandler = handleSubmit
124
+ }
125
+
126
+ // 如果是表单元素,也监听表单提交
127
+ const form = el.closest('form')
128
+ if (form && trackSubmit) {
129
+ form.addEventListener('submit', handleSubmit)
130
+ ;(el as any).__trackSearchFormHandler = handleSubmit
131
+ }
132
+ },
133
+
134
+ updated(el: HTMLElement, binding) {
135
+ // 更新时重新绑定
136
+ const inputHandler = (el as any).__trackSearchInputHandler
137
+
138
+ if (inputHandler) {
139
+ el.removeEventListener('input', inputHandler)
140
+ delete (el as any).__trackSearchInputHandler
141
+
142
+ trackSearchDirective.mounted?.(el, binding)
143
+ }
144
+ },
145
+
146
+ unmounted(el: HTMLElement) {
147
+ // 移除事件监听器
148
+ const inputHandler = (el as any).__trackSearchInputHandler
149
+ const submitHandler = (el as any).__trackSearchSubmitHandler
150
+ const formHandler = (el as any).__trackSearchFormHandler
151
+
152
+ if (inputHandler) {
153
+ el.removeEventListener('input', inputHandler)
154
+ delete (el as any).__trackSearchInputHandler
155
+ }
156
+
157
+ if (submitHandler) {
158
+ el.removeEventListener('submit', submitHandler)
159
+ delete (el as any).__trackSearchSubmitHandler
160
+ }
161
+
162
+ if (formHandler) {
163
+ const form = el.closest('form')
164
+ if (form) {
165
+ form.removeEventListener('submit', formHandler)
166
+ }
167
+ delete (el as any).__trackSearchFormHandler
168
+ }
169
+ },
170
+ }
171
+
172
+ /**
173
+ * 安装埋点搜索指令
174
+ */
175
+ export function installTrackSearchDirective(app: App) {
176
+ app.directive('track-search', trackSearchDirective)
177
+ }
178
+
179
+ export default trackSearchDirective