增加报表
This commit is contained in:
1 parent
ca719ba1d4
commit
b8652acce0
25 files changed
+136
-44
No files matched your search
+1
-1
@@ -191,7 +191,7 @@ function onTimelineTogglePointerDown(e: PointerEvent) {
|
||||
window.removeEventListener('pointerup', finish)
|
||||
window.removeEventListener('pointercancel', finish)
|
||||
if (!dragging) return
|
||||
trackMatomoEvent('timeline.resize')
|
||||
trackMatomoEvent('时间轴', '调整数值', `时间轴高度=${timelineH.value}`)
|
||||
if (timelineH.value > 0) saveTimelineHeight()
|
||||
// 拖拽后的 click 与本次手势属于同一操作,不能再次切换折叠。
|
||||
ignoreTimelineToggleClick = true
|
||||
|
||||
+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)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="spine-editor">
|
||||
<div class="spine-editor" data-matomo-section="spine">
|
||||
<div class="editor-title">{{ $t('spine.title') }}</div>
|
||||
<label class="field wide"><span>{{ $t('spine.name') }}</span><input v-model="spineObject.name" /></label>
|
||||
<div class="grid three">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="gradient-editor">
|
||||
<div class="gradient-editor" data-matomo-section="gradient">
|
||||
<div
|
||||
ref="bar"
|
||||
class="gradient-bar"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ce">
|
||||
<div class="ce" data-matomo-section="curve">
|
||||
<div class="ce-editor">
|
||||
<svg ref="svg" class="ce-svg" @dblclick="onDbl">
|
||||
<!-- 网格线(0.25/0.5/0.75) -->
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="show" class="help-overlay" role="presentation" @click.self="close">
|
||||
<section class="help-dialog" role="dialog" aria-modal="true" aria-labelledby="help-title">
|
||||
<section class="help-dialog" data-matomo-section="help" role="dialog" aria-modal="true" aria-labelledby="help-title">
|
||||
<header class="help-head">
|
||||
<div>
|
||||
<h2 id="help-title">{{ $t('help.title') }}</h2>
|
||||
@@ -172,8 +172,8 @@
|
||||
<dl class="help-list">
|
||||
<div><dt>{{ $t('help.prerequisite') }}</dt><dd>{{ $t('help.prerequisiteDesc') }}</dd></div>
|
||||
<div><dt>{{ $t('help.download') }}</dt><dd>
|
||||
<a class="dl-link" href="/downloads/connector/SpineParticleConnector-windows-x64.exe" download>Windows<br><small>SpineParticleConnector-windows-x64.exe</small></a>
|
||||
<a class="dl-link" href="/downloads/connector/SpineParticleConnector-macos-universal.zip" download>macOS (Universal)<br><small>SpineParticleConnector-macos-universal.zip</small></a>
|
||||
<a class="dl-link" href="/downloads/connector/SpineParticleConnector-windows-x64.exe" data-matomo-category="使用说明" data-matomo-action="点击下载" data-matomo-name="下载 Windows 连接器" download>Windows<br><small>SpineParticleConnector-windows-x64.exe</small></a>
|
||||
<a class="dl-link" href="/downloads/connector/SpineParticleConnector-macos-universal.zip" data-matomo-category="使用说明" data-matomo-action="点击下载" data-matomo-name="下载 macOS 连接器" download>macOS (Universal)<br><small>SpineParticleConnector-macos-universal.zip</small></a>
|
||||
</dd></div>
|
||||
<div><dt>{{ $t('help.webSide') }}</dt><dd>{{ $t('help.webSideDesc') }}</dd></div>
|
||||
<div><dt>{{ $t('help.authorization') }}</dt><dd>{{ $t('help.authorizationDesc') }}</dd></div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="image-card">
|
||||
<div class="image-card" data-matomo-section="resources">
|
||||
<div class="imgrow">
|
||||
<button class="img-thumb" data-matomo-event="resources.pick-image" :title="supportsSequence && resource.imageMode === 'sequence' ? $t('image.chooseSequence') : $t('image.chooseLocal')" @click="pickImage">
|
||||
<img v-if="displayPreviewUrl" :src="displayPreviewUrl" :alt="resource.textureName" />
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="pivotFullscreen" class="pivot-modal-backdrop" @pointerdown.self="closePivotFullscreen(false)">
|
||||
<div class="pivot-modal" role="dialog" aria-modal="true" :aria-label="$t('image.pivotModal')">
|
||||
<div class="pivot-modal" data-matomo-section="resources" role="dialog" aria-modal="true" :aria-label="$t('image.pivotModal')">
|
||||
<div class="pivot-modal-head">
|
||||
<div class="pivot-modal-icon">◎</div>
|
||||
<div class="pivot-modal-info">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="pac">
|
||||
<div class="pac" data-matomo-section="attribute-control">
|
||||
<div class="pac-head">
|
||||
<span class="pac-label">{{ label }}</span>
|
||||
<div v-if="showModes" class="pac-modes">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="pivot-editor" :class="{ 'pivot-large': large }">
|
||||
<div class="pivot-editor" data-matomo-section="pivot" :class="{ 'pivot-large': large }">
|
||||
<div class="pivot-toolbar">
|
||||
<div class="pivot-hint">
|
||||
<span class="pivot-hint-icon">⊕</span>
|
||||
|
||||
@@ -39,7 +39,7 @@ function normalizeBoneDebugSizes() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="show" ref="panelRef" class="settings-panel" tabindex="-1" @focusout="onFocusOut">
|
||||
<div v-if="show" ref="panelRef" class="settings-panel" data-matomo-section="settings" tabindex="-1" @focusout="onFocusOut">
|
||||
<div class="sp-head">{{ t('settings.title') }} <button class="sp-close" @click="emit('close')">✕</button></div>
|
||||
<div class="sp-section-title">{{ t('settings.layout') }}</div>
|
||||
<label class="sp-switch-row">
|
||||
|
||||
@@ -68,7 +68,7 @@ function changeLanguage(event: Event) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-operation-stack">
|
||||
<div class="top-operation-stack" data-matomo-section="toolbar">
|
||||
<div class="topbar">
|
||||
<button class="settings-btn" @click="toggleSettings" :title="t('toolbar.settings')">{{ t('toolbar.settings') }}</button>
|
||||
<button class="undo-btn" :disabled="editorHistoryState.undoCount === 0" @click="undo" :title="t('toolbar.undo')">↶</button>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="timeline">
|
||||
<div class="timeline" data-matomo-section="timeline">
|
||||
<!-- 控制按钮 -->
|
||||
<Teleport defer to="#timeline-controls-host">
|
||||
<div class="tl-controls">
|
||||
<div class="tl-controls" data-matomo-section="timeline">
|
||||
<button class="tl-btn" data-matomo-event="timeline.play-toggle" :class="{ on: store.timeline.playing }" @click="store.togglePlay()" :title="store.timeline.playing ? $t('timeline.pause') : $t('timeline.play')">
|
||||
{{ store.timeline.playing ? '❚❚' : '▶' }}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 2. 发射模式 -->
|
||||
<div class="group" v-show="sectionVisible('emitMode')">
|
||||
<div class="group" data-matomo-section="emission" v-show="sectionVisible('emitMode')">
|
||||
<div class="group-head" @click="toggle('emitMode')">
|
||||
<span class="caret">{{ collapsed.emitMode ? '▸' : '▾' }}</span><span>{{ $t('emission.title') }}</span>
|
||||
<button class="group-reset" data-matomo-event="emission.reset" :title="$t('emission.reset')" @click.stop="resetModule('emitMode')">{{ $t('common.reset') }}</button>
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 3. 发射器形状 -->
|
||||
<div class="group" v-show="sectionVisible('shape')">
|
||||
<div class="group" data-matomo-section="shape" v-show="sectionVisible('shape')">
|
||||
<div class="group-head" @click="toggle('shape')">
|
||||
<span class="caret">{{ collapsed.shape ? '▸' : '▾' }}</span><span>{{ $t('emission.shapeTitle') }}</span>
|
||||
<button class="group-reset" data-matomo-event="shape.reset" :title="$t('emission.shapeReset')" @click.stop="resetModule('shape')">{{ $t('common.reset') }}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 7. 导出资源:导出参数与资源输出操作。 -->
|
||||
<div class="group">
|
||||
<div class="group" data-matomo-section="export">
|
||||
<div class="group-head" @click="toggle('export')">
|
||||
<span class="caret">{{ collapsed.export ? '▸' : '▾' }}</span><span>{{ $t('export.title') }}</span>
|
||||
<button class="group-reset" data-matomo-event="export.reset" :title="$t('export.reset')" @click.stop="resetModule('export')">{{ $t('common.reset') }}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 5. 外观与资源 -->
|
||||
<div class="group">
|
||||
<div class="group" data-matomo-section="appearance">
|
||||
<div class="group-head" @click="toggle('look')">
|
||||
<span class="caret">{{ collapsed.look ? '▸' : '▾' }}</span><span>{{ $t('appearance.title') }}</span>
|
||||
<button class="group-reset" data-matomo-event="appearance.reset" :title="$t('appearance.reset')" @click.stop="resetModule('look')">{{ $t('common.reset') }}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 4. 粒子属性 -->
|
||||
<div class="group">
|
||||
<div class="group" data-matomo-section="attributes">
|
||||
<div class="group-head" @click="toggle('attr')">
|
||||
<span class="caret">{{ collapsed.attr ? '▸' : '▾' }}</span><span>{{ $t('attributes.title') }}</span>
|
||||
<button class="group-reset" data-matomo-event="attributes.reset" :title="$t('attributes.reset')" @click.stop="resetModule('attr')">{{ $t('common.reset') }}</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 6. 修改器 -->
|
||||
<div class="group">
|
||||
<div class="group" data-matomo-section="modifiers">
|
||||
<div class="group-head" @click="toggle('mods')">
|
||||
<span class="caret">{{ collapsed.mods ? '▸' : '▾' }}</span><span>{{ $t('modifiers.title') }}</span>
|
||||
<button class="group-reset" data-matomo-event="modifiers.reset" :title="$t('modifiers.reset')" @click.stop="resetModule('mods')">{{ $t('common.reset') }}</button>
|
||||
@@ -83,7 +83,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<div class="modifier-hint">{{ $t('modifiers.forceHint') }}</div>
|
||||
<label class="switch-row"><input type="checkbox" v-model="sys.config.showForceFieldShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
<label class="switch-row" data-matomo-name="显示力场形状"><input type="checkbox" v-model="sys.config.showForceFieldShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modifier-item">
|
||||
@@ -104,7 +104,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<div class="modifier-hint">{{ $t('modifiers.vortexHint') }}</div>
|
||||
<label class="switch-row"><input type="checkbox" v-model="sys.config.showVortexShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
<label class="switch-row" data-matomo-name="显示漩涡形状"><input type="checkbox" v-model="sys.config.showVortexShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modifier-item">
|
||||
@@ -131,7 +131,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<div class="modifier-hint">{{ $t('modifiers.attractionHint') }}</div>
|
||||
<label class="switch-row"><input type="checkbox" v-model="sys.config.showAttractionShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
<label class="switch-row" data-matomo-name="显示吸附范围"><input type="checkbox" v-model="sys.config.showAttractionShape" /><span class="switch-ui"></span><span>{{ $t('modifiers.showShape') }}</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modifier-item">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="group config-management">
|
||||
<div class="group config-management" data-matomo-section="config">
|
||||
<div class="group-head" @click="toggle('config')">
|
||||
<span class="caret">{{ collapsed.config ? '▸' : '▾' }}</span><span>{{ $t('config.title') }}</span>
|
||||
<button type="button" class="group-download" data-matomo-event="config.download-current" @click.stop="downloadCurrentProject">{{ $t('config.local.downloadCurrent') }}</button>
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<div v-if="projectSaveOpen" class="project-save-backdrop" @pointerdown.self="closeProjectSaveDialog">
|
||||
<form class="project-save-dialog" @submit.prevent="confirmSaveConfig" @keydown.esc.prevent="closeProjectSaveDialog">
|
||||
<form class="project-save-dialog" data-matomo-section="config" @submit.prevent="confirmSaveConfig" @keydown.esc.prevent="closeProjectSaveDialog">
|
||||
<div class="project-save-head">
|
||||
<span>{{ $t('config.local.saveTitle') }}</span>
|
||||
<button type="button" :disabled="projectStorageBusy" @click="closeProjectSaveDialog">✕</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 1. 场景对象:创建入口始终可用,即使当前没有粒子系统。 -->
|
||||
<div class="group">
|
||||
<div class="group" data-matomo-section="scene">
|
||||
<div class="group-head scene-head" @click="toggle('scene')">
|
||||
<span class="caret">{{ collapsed.scene ? '▸' : '▾' }}</span><span>{{ $t('scene.title') }}</span>
|
||||
<div class="scene-actions">
|
||||
|
||||
Reference in new issue
Block a user