一步步教你,为WordPress网站添加访客行为热力图与录屏分析,通过WordPress程序的代码二次开发实现常用互联网小工具功能
引言:为什么你的WordPress网站需要行为分析工具?
在当今竞争激烈的数字环境中,仅仅拥有一个美观的WordPress网站已经远远不够。了解访客如何与你的网站互动,哪些内容吸引他们,哪些元素导致他们离开,这些洞察对于优化用户体验、提高转化率至关重要。传统的数据分析工具如Google Analytics提供了页面浏览量和用户数量的宏观数据,但却无法回答一些关键问题:用户究竟在哪里点击?他们是否看到了重要的行动号召按钮?他们在离开前在页面上做了什么?
这就是热力图和录屏分析的价值所在。热力图通过色彩可视化展示用户在页面上的点击、滚动和注意力分布,而录屏分析则像“数字监控摄像头”一样,记录真实用户在网站上的完整操作过程。本文将详细指导你如何通过WordPress代码二次开发,为你的网站集成这两项强大的分析工具,无需依赖昂贵的第三方服务。
第一部分:理解热力图与录屏分析的技术原理
1.1 热力图的三种类型及其实现原理
热力图主要分为三种类型:点击热力图、滚动热力图和注意力热力图。点击热力图记录用户在页面上的所有点击位置,包括链接、按钮甚至非链接区域。实现原理是通过JavaScript事件监听器捕获点击事件,记录点击元素的坐标、尺寸和页面URL,然后将这些数据发送到服务器进行处理和可视化。
滚动热力图显示用户在不同页面深度的停留比例,揭示有多少用户滚动到页面特定位置。这通过监听滚动事件,记录视窗位置和页面高度比例来实现。注意力热力图则基于视线追踪研究,假设用户的注意力与鼠标移动轨迹相关,通过跟踪鼠标移动和停留时间来模拟实现。
1.2 录屏分析的技术实现方式
录屏分析(也称为会话回放)的实现比热力图更为复杂。现代实现通常采用以下技术组合:
- DOM序列化:记录页面初始的DOM状态,然后监听所有可能改变DOM的事件(如点击、输入、滚动等)
- 突变观察器(MutationObserver):监控DOM元素的变化
- 事件监听:捕获用户交互事件
- 时间戳同步:确保所有事件按正确顺序和时序记录
- 数据压缩:将记录的数据高效压缩后发送到服务器
录屏数据通常以增量方式记录,而不是完整视频,这大大减少了数据量。回放时,通过重建DOM状态和应用记录的事件来“重现”用户会话。
1.3 数据收集与隐私保护的平衡
实现这些工具时,必须考虑隐私保护。敏感数据(如密码字段、个人身份信息)应在客户端就被过滤或遮蔽。GDPR、CCPA等法规要求对用户数据进行适当处理,因此你的实现应包含:
- 明确的隐私政策和使用告知
- 敏感数据自动遮蔽功能
- 用户选择退出机制
- 数据匿名化处理
第二部分:准备工作与开发环境搭建
2.1 开发环境配置
在开始编码前,确保你已准备好以下环境:
- 本地WordPress开发环境:可以使用Local by Flywheel、XAMPP或Docker配置
- 代码编辑器:VS Code、Sublime Text或PHPStorm
- 浏览器开发者工具:熟悉Chrome DevTools或Firefox开发者工具
- 版本控制系统:Git初始化你的插件目录
- 测试网站:不要在正式网站直接开发,使用测试网站或本地环境
2.2 创建自定义WordPress插件框架
我们将创建一个独立的WordPress插件来实现功能,而不是直接修改主题文件。这样做的好处是:
- 功能与主题分离,更换主题不影响分析工具
- 便于更新和维护
- 可以轻松在其他网站上复用
创建插件基本结构:
wp-content/plugins/visitor-analytics-tool/
├── visitor-analytics-tool.php
├── includes/
│ ├── class-data-collector.php
│ ├── class-heatmap-generator.php
│ ├── class-session-recorder.php
│ └── class-data-viewer.php
├── assets/
│ ├── js/
│ │ ├── frontend.js
│ │ └── admin.js
│ └── css/
│ ├── frontend.css
│ └── admin.css
├── admin/
│ └── admin-pages.php
└── uninstall.php
2.3 插件主文件基本结构
在visitor-analytics-tool.php中,设置插件基本信息:
<?php
/**
* Plugin Name: Visitor Analytics Tool
* Plugin URI: https://yourwebsite.com/visitor-analytics
* Description: 添加访客行为热力图与录屏分析功能
* Version: 1.0.0
* Author: Your Name
* License: GPL v2 or later
* Text Domain: visitor-analytics
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('VAT_VERSION', '1.0.0');
define('VAT_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('VAT_PLUGIN_URL', plugin_dir_url(__FILE__));
// 包含必要文件
require_once VAT_PLUGIN_DIR . 'includes/class-data-collector.php';
require_once VAT_PLUGIN_DIR . 'includes/class-heatmap-generator.php';
require_once VAT_PLUGIN_DIR . 'includes/class-session-recorder.php';
require_once VAT_PLUGIN_DIR . 'includes/class-data-viewer.php';
// 初始化插件
function vat_initialize_plugin() {
// 检查用户权限
if (current_user_can('manage_options')) {
// 初始化各个组件
$data_collector = new VAT_Data_Collector();
$heatmap_generator = new VAT_Heatmap_Generator();
$session_recorder = new VAT_Session_Recorder();
$data_viewer = new VAT_Data_Viewer();
// 注册激活/停用钩子
register_activation_hook(__FILE__, array($data_collector, 'activate'));
register_deactivation_hook(__FILE__, array($data_collector, 'deactivate'));
}
}
add_action('plugins_loaded', 'vat_initialize_plugin');
第三部分:实现前端数据收集系统
3.1 设计高效的数据收集JavaScript
创建assets/js/frontend.js文件,负责收集用户行为数据:
(function() {
'use strict';
// 配置参数
const config = {
sampleRate: 0.3, // 30%的用户被记录
heatmap: {
enabled: true,
click: true,
scroll: true,
move: true
},
sessionRecord: {
enabled: false, // 默认关闭,需要时开启
maxDuration: 300000, // 5分钟
events: ['click', 'input', 'scroll', 'resize']
},
privacy: {
maskText: true,
excludeFields: ['password', 'credit-card', 'ssn']
}
};
// 检查是否应该记录当前会话
function shouldRecordSession() {
if (!Math.floor(Math.random() * 100) < (config.sampleRate * 100)) {
return false;
}
// 检查排除条件(如管理员、特定页面等)
const excludedPaths = ['/wp-admin', '/wp-login'];
const currentPath = window.location.pathname;
return !excludedPaths.some(path => currentPath.startsWith(path));
}
// 数据收集器类
class DataCollector {
constructor() {
this.sessionId = this.generateSessionId();
this.pageId = window.location.pathname + window.location.search;
this.startTime = Date.now();
this.events = [];
this.isRecording = false;
// 初始化
this.init();
}
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
init() {
if (!shouldRecordSession()) return;
// 设置数据收集
if (config.heatmap.enabled) {
this.setupHeatmapCollection();
}
if (config.sessionRecord.enabled) {
this.setupSessionRecording();
}
// 发送初始数据
this.sendInitialData();
this.isRecording = true;
}
setupHeatmapCollection() {
// 点击事件收集
if (config.heatmap.click) {
document.addEventListener('click', (e) => {
this.recordClick(e);
}, { capture: true });
}
// 滚动事件收集
if (config.heatmap.scroll) {
let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
this.recordScroll();
}, 100);
});
}
// 鼠标移动收集(用于注意力热力图)
if (config.heatmap.move) {
let moveTimeout;
document.addEventListener('mousemove', (e) => {
clearTimeout(moveTimeout);
moveTimeout = setTimeout(() => {
this.recordMousePosition(e);
}, 50);
});
}
}
recordClick(event) {
const target = event.target;
const rect = target.getBoundingClientRect();
const clickData = {
type: 'click',
x: event.clientX,
y: event.clientY,
element: {
tag: target.tagName,
id: target.id,
classes: target.className,
text: this.maskText(target.textContent.trim().substring(0, 100))
},
pageX: event.pageX,
pageY: event.pageY,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
timestamp: Date.now() - this.startTime,
url: window.location.href
};
this.events.push(clickData);
this.sendDataIfNeeded();
}
recordScroll() {
const scrollData = {
type: 'scroll',
position: {
x: window.scrollX,
y: window.scrollY
},
maxScroll: {
x: document.documentElement.scrollWidth - window.innerWidth,
y: document.documentElement.scrollHeight - window.innerHeight
},
percentage: (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100,
timestamp: Date.now() - this.startTime,
url: window.location.href
};
this.events.push(scrollData);
this.sendDataIfNeeded();
}
recordMousePosition(event) {
const moveData = {
type: 'move',
x: event.clientX,
y: event.clientY,
timestamp: Date.now() - this.startTime,
url: window.location.href
};
this.events.push(moveData);
// 鼠标移动数据较多,单独处理发送逻辑
this.sendMoveData();
}
maskText(text) {
if (!config.privacy.maskText) return text;
// 简单文本遮蔽,实际应用中需要更复杂的逻辑
if (text.length > 5) {
return text.substring(0, 2) + '***' + text.substring(text.length - 2);
}
return '***';
}
sendDataIfNeeded() {
// 每10个事件或每5秒发送一次数据
if (this.events.length >= 10) {
this.sendBatchData();
}
}
sendMoveData() {
// 鼠标移动数据单独处理,避免数据量过大
if (this.moveEvents && this.moveEvents.length >= 20) {
const moveBatch = {
sessionId: this.sessionId,
type: 'move_batch',
events: this.moveEvents,
url: window.location.href
};
this.sendToServer(moveBatch);
this.moveEvents = [];
}
}
sendBatchData() {
if (this.events.length === 0) return;
const batch = {
sessionId: this.sessionId,
events: [...this.events],
url: window.location.href,
timestamp: Date.now()
};
this.sendToServer(batch);
this.events = [];
}
sendToServer(data) {
// 使用navigator.sendBeacon确保页面卸载时也能发送数据
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
if (navigator.sendBeacon) {
navigator.sendBeacon(vat_ajax.ajax_url + '?action=vat_track', blob);
} else {
// 回退方案
const xhr = new XMLHttpRequest();
xhr.open('POST', vat_ajax.ajax_url, false);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
}
}
sendInitialData() {
const initialData = {
sessionId: this.sessionId,
type: 'session_start',
url: window.location.href,
referrer: document.referrer,
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
device: {
userAgent: navigator.userAgent,
platform: navigator.platform
},
timestamp: Date.now()
};
this.sendToServer(initialData);
}
setupSessionRecording() {
// 录屏功能需要更复杂的实现
console.log('Session recording setup would go here');
// 实际实现需要DOM序列化、突变观察器等
}
}
// 初始化数据收集器
document.addEventListener('DOMContentLoaded', () => {
window.vatCollector = new DataCollector();
});
// 页面卸载前发送剩余数据
window.addEventListener('beforeunload', () => {
if (window.vatCollector && window.vatCollector.isRecording) {
window.vatCollector.sendBatchData();
}
});
})();
3.2 创建PHP后端数据处理类
在includes/class-data-collector.php中创建数据处理后端:
<?php
class VAT_Data_Collector {
private $table_events;
private $table_sessions;
public function __construct() {
global $wpdb;
$this->table_events = $wpdb->prefix . 'vat_events';
$this->table_sessions = $wpdb->prefix . 'vat_sessions';
// 注册AJAX处理
add_action('wp_ajax_vat_track', array($this, 'handle_tracking_data'));
add_action('wp_ajax_nopriv_vat_track', array($this, 'handle_tracking_data'));
// 注册前端脚本
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts'));
}
public function activate() {
$this->create_tables();
$this->set_default_options();
}
private function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 创建事件表
$sql_events = "CREATE TABLE IF NOT EXISTS {$this->table_events} (
id bigint(20) NOT NULL AUTO_INCREMENT,
session_id varchar(100) NOT NULL,
event_type varchar(50) NOT NULL,
event_data longtext NOT NULL,
page_url varchar(500) NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id),
KEY session_id (session_id),
KEY event_type (event_type),
KEY page_url (page_url(200)),
KEY created_at (created_at)
) $charset_collate;";
// 创建会话表
$sql_sessions = "CREATE TABLE IF NOT EXISTS {$this->table_sessions} (
session_id varchar(100) NOT NULL,
start_time datetime NOT NULL,
end_time datetime,
page_views int(11) DEFAULT 1,
duration int(11),
referrer varchar(500),
device_info text,
user_id bigint(20),
PRIMARY KEY (session_id),
KEY start_time (start_time),
KEY user_id (user_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql_events);
dbDelta($sql_sessions);
}
private function set_default_options() {
if (!get_option('vat_settings')) {
$default_settings = array(
'sample_rate' => 0.3,
'heatmap_enabled' => true,
'session_record_enabled' => false,
'data_retention_days' => 30,
'exclude_roles' => array('administrator'),
'privacy_mask' => true
);
update_option('vat_settings', $default_settings);
}
}
public function enqueue_frontend_scripts() {
// 只有在前端页面加载
if (is_admin()) return;
// 获取设置
$settings = get_option('vat_settings', array());
// 检查是否应该加载跟踪脚本
if (!$this->should_track_current_user()) return;
// 计算采样率
$sample_rate = isset($settings['sample_rate']) ? floatval($settings['sample_rate']) : 0.3;
// 只有部分用户加载跟踪脚本
if (mt_rand(1, 100) > ($sample_rate * 100)) return;
wp_enqueue_script(
'vat-frontend',
VAT_PLUGIN_URL . 'assets/js/frontend.js',
array(),
VAT_VERSION,
true
);
// 传递AJAX URL给前端脚本
wp_localize_script('vat-frontend', 'vat_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'sample_rate' => $sample_rate
));
}
第三部分:实现前端数据收集系统(续)
3.2 创建PHP后端数据处理类(续)
private function should_track_current_user() {
$settings = get_option('vat_settings', array());
$exclude_roles = isset($settings['exclude_roles']) ? $settings['exclude_roles'] : array();
// 如果用户已登录,检查其角色
if (is_user_logged_in()) {
$user = wp_get_current_user();
$user_roles = $user->roles;
// 检查用户角色是否在排除列表中
foreach ($user_roles as $role) {
if (in_array($role, $exclude_roles)) {
return false;
}
}
}
// 排除特定页面
$excluded_pages = array('/wp-admin', '/wp-login.php', '/cart', '/checkout');
$current_path = $_SERVER['REQUEST_URI'];
foreach ($excluded_pages as $page) {
if (strpos($current_path, $page) === 0) {
return false;
}
}
return true;
}
public function handle_tracking_data() {
// 验证请求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
wp_die('Invalid request method', 400);
}
// 获取原始POST数据
$raw_data = file_get_contents('php://input');
$data = json_decode($raw_data, true);
if (!$data || !isset($data['sessionId'])) {
wp_die('Invalid data format', 400);
}
// 处理不同类型的数据
if (isset($data['type']) && $data['type'] === 'session_start') {
$this->record_session_start($data);
} elseif (isset($data['type']) && $data['type'] === 'move_batch') {
$this->record_move_batch($data);
} elseif (isset($data['events'])) {
$this->record_events($data);
}
// 返回成功响应
wp_send_json_success(array('message' => 'Data recorded'));
}
private function record_session_start($data) {
global $wpdb;
$session_data = array(
'session_id' => sanitize_text_field($data['sessionId']),
'start_time' => current_time('mysql'),
'referrer' => isset($data['referrer']) ? sanitize_text_field($data['referrer']) : '',
'device_info' => isset($data['device']) ? json_encode($data['device']) : '',
'user_id' => is_user_logged_in() ? get_current_user_id() : 0
);
$wpdb->insert($this->table_sessions, $session_data);
}
private function record_events($data) {
global $wpdb;
$session_id = sanitize_text_field($data['sessionId']);
$events = $data['events'];
foreach ($events as $event) {
$event_type = isset($event['type']) ? sanitize_text_field($event['type']) : 'unknown';
$event_data = array(
'session_id' => $session_id,
'event_type' => $event_type,
'event_data' => json_encode($event),
'page_url' => isset($event['url']) ? sanitize_text_field($event['url']) : ''
);
$wpdb->insert($this->table_events, $event_data);
}
// 更新会话的最后活动时间
$this->update_session_activity($session_id);
}
private function record_move_batch($data) {
global $wpdb;
$session_id = sanitize_text_field($data['sessionId']);
$events = $data['events'];
// 鼠标移动数据较多,进行聚合处理
$aggregated_data = array(
'session_id' => $session_id,
'event_type' => 'move_aggregated',
'event_data' => json_encode(array(
'count' => count($events),
'positions' => array_map(function($e) {
return array($e['x'], $e['y']);
}, $events)
)),
'page_url' => isset($data['url']) ? sanitize_text_field($data['url']) : ''
);
$wpdb->insert($this->table_events, $aggregated_data);
}
private function update_session_activity($session_id) {
global $wpdb;
$wpdb->update(
$this->table_sessions,
array('end_time' => current_time('mysql')),
array('session_id' => $session_id)
);
}
public function cleanup_old_data() {
global $wpdb;
$settings = get_option('vat_settings', array());
$retention_days = isset($settings['data_retention_days']) ? intval($settings['data_retention_days']) : 30;
$cutoff_date = date('Y-m-d H:i:s', strtotime("-$retention_days days"));
// 删除旧事件数据
$wpdb->query($wpdb->prepare(
"DELETE FROM {$this->table_events} WHERE created_at < %s",
$cutoff_date
));
// 删除旧会话数据
$wpdb->query($wpdb->prepare(
"DELETE FROM {$this->table_sessions} WHERE start_time < %s",
$cutoff_date
));
}
}
第四部分:构建热力图生成与可视化系统
4.1 创建热力图数据处理类
在includes/class-heatmap-generator.php中:
<?php
class VAT_Heatmap_Generator {
private $data_collector;
public function __construct() {
global $wpdb;
$this->table_events = $wpdb->prefix . 'vat_events';
// 注册管理页面
add_action('admin_menu', array($this, 'add_admin_menu'));
// 注册AJAX端点
add_action('wp_ajax_vat_get_heatmap_data', array($this, 'get_heatmap_data'));
}
public function add_admin_menu() {
add_menu_page(
'访客行为分析',
'行为分析',
'manage_options',
'vat-analytics',
array($this, 'render_admin_page'),
'dashicons-chart-area',
30
);
add_submenu_page(
'vat-analytics',
'热力图分析',
'热力图',
'manage_options',
'vat-heatmaps',
array($this, 'render_heatmap_page')
);
}
public function render_admin_page() {
?>
<div class="wrap">
<h1>访客行为分析仪表板</h1>
<div class="vat-dashboard">
<div class="vat-stats-grid">
<div class="vat-stat-card">
<h3>今日会话</h3>
<p class="vat-stat-number"><?php echo $this->get_today_sessions(); ?></p>
</div>
<div class="vat-stat-card">
<h3>平均点击次数</h3>
<p class="vat-stat-number"><?php echo $this->get_average_clicks(); ?></p>
</div>
<div class="vat-stat-card">
<h3>热门页面</h3>
<p class="vat-stat-number"><?php echo $this->get_top_page(); ?></p>
</div>
</div>
</div>
</div>
<?php
}
public function render_heatmap_page() {
$pages = $this->get_pages_with_data();
?>
<div class="wrap">
<h1>热力图分析</h1>
<div class="vat-heatmap-controls">
<div class="vat-control-group">
<label for="vat-page-select">选择页面:</label>
<select id="vat-page-select" class="vat-select">
<option value="">选择页面...</option>
<?php foreach ($pages as $page): ?>
<option value="<?php echo esc_attr($page->page_url); ?>">
<?php echo esc_html($this->truncate_url($page->page_url)); ?>
(<?php echo $page->event_count; ?> 事件)
</option>
<?php endforeach; ?>
</select>
</div>
<div class="vat-control-group">
<label for="vat-date-range">日期范围:</label>
<select id="vat-date-range" class="vat-select">
<option value="today">今天</option>
<option value="yesterday">昨天</option>
<option value="7days" selected>最近7天</option>
<option value="30days">最近30天</option>
<option value="custom">自定义</option>
</select>
<div id="vat-custom-dates" style="display: none; margin-top: 10px;">
<input type="date" id="vat-date-from" class="vat-date-input">
<span>至</span>
<input type="date" id="vat-date-to" class="vat-date-input">
</div>
</div>
<div class="vat-control-group">
<label for="vat-heatmap-type">热力图类型:</label>
<select id="vat-heatmap-type" class="vat-select">
<option value="click">点击热力图</option>
<option value="scroll">滚动热力图</option>
<option value="attention">注意力热力图</option>
</select>
</div>
<button id="vat-generate-heatmap" class="button button-primary">生成热力图</button>
</div>
<div class="vat-heatmap-container">
<div id="vat-page-preview" style="border: 1px solid #ddd; margin: 20px 0; position: relative; overflow: hidden;">
<!-- 页面预览将在这里显示 -->
</div>
<div id="vat-heatmap-canvas" style="position: absolute; top: 0; left: 0; pointer-events: none;">
<!-- 热力图将在这里绘制 -->
</div>
<div class="vat-heatmap-legend">
<div class="vat-legend-title">热力强度</div>
<div class="vat-legend-gradient">
<span>低</span>
<div class="vat-gradient-bar"></div>
<span>高</span>
</div>
</div>
</div>
<div class="vat-heatmap-stats">
<h3>页面分析数据</h3>
<div id="vat-page-stats">
<!-- 统计数据将动态加载 -->
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// 初始化热力图页面
vatHeatmap.init();
});
</script>
<?php
}
private function get_pages_with_data() {
global $wpdb;
return $wpdb->get_results($wpdb->prepare(
"SELECT page_url, COUNT(*) as event_count
FROM {$this->table_events}
WHERE event_type IN ('click', 'scroll', 'move_aggregated')
AND created_at >= %s
GROUP BY page_url
ORDER BY event_count DESC
LIMIT 50",
date('Y-m-d H:i:s', strtotime('-30 days'))
));
}
public function get_heatmap_data() {
// 验证权限
if (!current_user_can('manage_options')) {
wp_die('权限不足', 403);
}
$page_url = isset($_GET['page_url']) ? urldecode($_GET['page_url']) : '';
$date_range = isset($_GET['date_range']) ? $_GET['date_range'] : '7days';
$heatmap_type = isset($_GET['heatmap_type']) ? $_GET['heatmap_type'] : 'click';
if (empty($page_url)) {
wp_send_json_error('请选择页面');
}
// 计算日期范围
$date_conditions = $this->get_date_condition($date_range);
// 根据热力图类型获取数据
switch ($heatmap_type) {
case 'click':
$data = $this->get_click_data($page_url, $date_conditions);
break;
case 'scroll':
$data = $this->get_scroll_data($page_url, $date_conditions);
break;
case 'attention':
$data = $this->get_attention_data($page_url, $date_conditions);
break;
default:
$data = array();
}
// 获取页面统计数据
$stats = $this->get_page_stats($page_url, $date_conditions);
wp_send_json_success(array(
'data' => $data,
'stats' => $stats,
'page_url' => $page_url
));
}
private function get_click_data($page_url, $date_conditions) {
global $wpdb;
$events = $wpdb->get_results($wpdb->prepare(
"SELECT event_data
FROM {$this->table_events}
WHERE page_url = %s
AND event_type = 'click'
AND created_at >= %s
ORDER BY created_at DESC
LIMIT 1000",
$page_url,
$date_conditions['start_date']
));
$clicks = array();
foreach ($events as $event) {
$data = json_decode($event->event_data, true);
if (isset($data['x']) && isset($data['y'])) {
$clicks[] = array(
'x' => intval($data['x']),
'y' => intval($data['y']),
'viewport_width' => isset($data['viewport']['width']) ? intval($data['viewport']['width']) : 1920,
'viewport_height' => isset($data['viewport']['height']) ? intval($data['viewport']['height']) : 1080
);
}
}
return $clicks;
}
private function get_scroll_data($page_url, $date_conditions) {
global $wpdb;
$events = $wpdb->get_results($wpdb->prepare(
"SELECT event_data
FROM {$this->table_events}
WHERE page_url = %s
AND event_type = 'scroll'
AND created_at >= %s
ORDER BY created_at DESC
LIMIT 5000",
$page_url,
$date_conditions['start_date']
));
$scroll_depths = array();
foreach ($events as $event) {
$data = json_decode($event->event_data, true);
if (isset($data['percentage'])) {
$scroll_depths[] = floatval($data['percentage']);
}
}
// 计算滚动深度分布
$distribution = array_fill(0, 10, 0); // 10个区间:0-10%, 10-20%, ..., 90-100%
foreach ($scroll_depths as $depth) {
$index = min(floor($depth / 10), 9);
$distribution[$index]++;
}
return $distribution;
}
private function get_attention_data($page_url, $date_conditions) {
global $wpdb;
$events = $wpdb->get_results($wpdb->prepare(
"SELECT event_data
FROM {$this->table_events}
WHERE page_url = %s
AND event_type = 'move_aggregated'
AND created_at >= %s
ORDER BY created_at DESC
LIMIT 100",
$page_url,
$date_conditions['start_date']
));
$positions = array();
foreach ($events as $event) {
$data = json_decode($event->event_data, true);
if (isset($data['positions'])) {
foreach ($data['positions'] as $pos) {
$positions[] = array(
'x' => $pos[0],
'y' => $pos[1]
);
}
}
}
return $positions;
}
private function get_page_stats($page_url, $date_conditions) {
global $wpdb;
$stats = array();
// 获取总点击次数
$stats['total_clicks'] = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*)
FROM {$this->table_events}
WHERE page_url = %s
AND event_type = 'click'
AND created_at >= %s",
$page_url,
$date_conditions['start_date']
));
// 获取独立访客数
$stats['unique_visitors'] = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT session_id)
FROM {$this->table_events}
WHERE page_url = %s
AND created_at >= %s",
$page_url,
$date_conditions['start_date']
));
// 获取平均滚动深度
$scroll_data = $wpdb->get_results($wpdb->prepare(
"SELECT event_data
FROM {$this->table_events}
WHERE page_url = %s
AND event_type = 'scroll'
AND created_at >= %s",
$page_url,
$date_conditions['start_date']
));
$total_depth = 0;
$count = 0;
foreach ($scroll_data as $event) {
$data = json_decode($event->event_data, true);
if (isset($data['percentage'])) {
$total_depth += floatval($data['percentage']);
$count++;
}
}
