增加报表
This commit is contained in:
1 parent
ca719ba1d4
commit
b8652acce0
25 files changed
+136
-44
No files matched your search
+103
-11
@@ -1,15 +1,81 @@
|
||||
type MatomoWindow = Window & { _paq?: unknown[][] }
|
||||
import { i18n, SUPPORTED_LOCALES } from '../i18n'
|
||||
|
||||
export function trackMatomoEvent(eventName: string) {
|
||||
type MatomoWindow = Window & { _paq?: unknown[][] }
|
||||
type EventCategory = '时间轴' | '系统设置' | '属性面板' | '使用说明'
|
||||
type EventAction = '点击按钮' | '点击开关' | '调整数值' | '点击下载'
|
||||
type MessageTree = Record<string, unknown>
|
||||
|
||||
let chineseLabels: Map<string, string> | null = null
|
||||
|
||||
function flattenMessages(value: unknown, prefix = '', output: Record<string, string> = {}) {
|
||||
if (!value || typeof value !== 'object') return output
|
||||
for (const [key, child] of Object.entries(value as MessageTree)) {
|
||||
const path = prefix ? `${prefix}.${key}` : key
|
||||
if (typeof child === 'string') output[path] = child
|
||||
else flattenMessages(child, path, output)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
function localizedToChinese(value: string | null | undefined) {
|
||||
const label = value?.replace(/\s+/g, ' ').trim()
|
||||
if (!label) return ''
|
||||
if (!chineseLabels) {
|
||||
chineseLabels = new Map()
|
||||
const chinese = flattenMessages(i18n.global.getLocaleMessage('zh-CN'))
|
||||
for (const locale of SUPPORTED_LOCALES) {
|
||||
const messages = flattenMessages(i18n.global.getLocaleMessage(locale.code))
|
||||
for (const [key, text] of Object.entries(messages)) {
|
||||
const translated = chinese[key]
|
||||
if (translated) chineseLabels.set(text.replace(/\s+/g, ' ').trim(), translated)
|
||||
}
|
||||
}
|
||||
}
|
||||
return chineseLabels.get(label) || ''
|
||||
}
|
||||
|
||||
function categoryFor(element: Element): EventCategory {
|
||||
const section = element.closest<HTMLElement>('[data-matomo-section]')?.dataset.matomoSection
|
||||
if (section === 'timeline') return '时间轴'
|
||||
if (section === 'settings') return '系统设置'
|
||||
return '属性面板'
|
||||
}
|
||||
|
||||
function controlName(control: Element, fallback: string) {
|
||||
const candidates = [
|
||||
(control as HTMLElement).dataset.matomoName,
|
||||
control.closest<HTMLElement>('[data-matomo-name]')?.dataset.matomoName,
|
||||
control.getAttribute('aria-label'),
|
||||
control.getAttribute('title'),
|
||||
control.textContent,
|
||||
control.closest('label')?.textContent,
|
||||
control.parentElement?.querySelector('.ns-label, .pac-label')?.textContent,
|
||||
]
|
||||
for (const candidate of candidates) {
|
||||
const chinese = localizedToChinese(candidate)
|
||||
if (chinese) return chinese
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
function buttonName(button: HTMLButtonElement) {
|
||||
const section = button.closest('[data-matomo-section]') || document
|
||||
const className = [...button.classList]
|
||||
.filter((name) => !['active', 'collapsed', 'danger', 'ghost', 'off', 'on', 'primary', 'selected'].includes(name))
|
||||
.sort()
|
||||
.join('-') || button.type || 'button'
|
||||
const peers = [...section.querySelectorAll<HTMLButtonElement>('button')]
|
||||
.filter((peer) => ([...peer.classList]
|
||||
.filter((name) => !['active', 'collapsed', 'danger', 'ghost', 'off', 'on', 'primary', 'selected'].includes(name))
|
||||
.sort()
|
||||
.join('-') || peer.type || 'button') === className)
|
||||
return controlName(button, `按钮${Math.max(1, peers.indexOf(button) + 1)}`)
|
||||
}
|
||||
|
||||
export function trackMatomoEvent(category: EventCategory, action: EventAction, name: string) {
|
||||
if (!import.meta.env.PROD) return
|
||||
const separator = eventName.indexOf('.')
|
||||
if (separator <= 0 || separator === eventName.length - 1) return
|
||||
const target = window as MatomoWindow
|
||||
;(target._paq ||= []).push([
|
||||
'trackEvent',
|
||||
eventName.slice(0, separator),
|
||||
eventName.slice(separator + 1),
|
||||
])
|
||||
;(target._paq ||= []).push(['trackEvent', category, action, name])
|
||||
}
|
||||
|
||||
export function installMatomoEventTracking() {
|
||||
@@ -17,7 +83,33 @@ export function installMatomoEventTracking() {
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof Element)) return
|
||||
const tracked = target.closest<HTMLElement>('[data-matomo-event]')
|
||||
if (tracked?.dataset.matomoEvent) trackMatomoEvent(tracked.dataset.matomoEvent)
|
||||
const download = target.closest<HTMLAnchorElement>('a[data-matomo-category][data-matomo-action][data-matomo-name]')
|
||||
if (download) {
|
||||
trackMatomoEvent(
|
||||
download.dataset.matomoCategory as EventCategory,
|
||||
download.dataset.matomoAction as EventAction,
|
||||
download.dataset.matomoName || '',
|
||||
)
|
||||
return
|
||||
}
|
||||
const button = target.closest<HTMLButtonElement>('button')
|
||||
if (!button) return
|
||||
trackMatomoEvent(categoryFor(button), '点击按钮', buttonName(button))
|
||||
}, true)
|
||||
|
||||
document.addEventListener('change', (event) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof HTMLInputElement)) return
|
||||
if (target.type === 'checkbox') {
|
||||
const name = controlName(target, '开关')
|
||||
trackMatomoEvent(categoryFor(target), '点击开关', `${name}=${target.checked ? '开启' : '关闭'}`)
|
||||
return
|
||||
}
|
||||
if (target.type !== 'range' && target.type !== 'number') return
|
||||
const pairedRange = target.type === 'range'
|
||||
|| !!target.parentElement?.querySelector('input[type="range"]')
|
||||
if (!pairedRange) return
|
||||
const name = controlName(target, '数值')
|
||||
trackMatomoEvent(categoryFor(target), '调整数值', `${name}=${target.value}`)
|
||||
}, true)
|
||||
}
|
||||
Reference in new issue
Block a user