新增功能说明
数据库与性能优化
使用方法
<?php
/*
Plugin Name: 增强访问统计
Plugin URI: https://example.com/
Description: 功能完善的WordPress访问统计工具,支持来源分析、设备统计和数据导出
Version: 2.0
Author: Your Name
Author URI: https://example.com/
License: GPL2
*/
if (!defined('ABSPATH')) {
exit;
}
// 插件常量
define('WPSA_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('WPSA_PLUGIN_URL', plugin_dir_url(__FILE__));
define('WPSA_DB_VERSION', '2.0');
// 激活插件时创建/更新数据库表
register_activation_hook(__FILE__, 'wpsa_activate');
function wpsa_activate() {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$charset_collate = $wpdb->get_charset_collate();
// 新增visit_duration和source_type字段
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
visit_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip_address varchar(45) NOT NULL,
user_agent text NOT NULL,
page_url text NOT NULL,
referrer text,
referrer_domain varchar(255),
source_type enum('direct', 'search', 'social', 'external', 'internal') NOT NULL DEFAULT 'direct',
browser varchar(100),
os varchar(100),
device_type enum('desktop', 'mobile', 'tablet', 'other') NOT NULL DEFAULT 'other',
visit_duration int(11) DEFAULT 0,
is_bot tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
KEY visit_date (visit_time),
KEY source_type (source_type),
KEY device_type (device_type)
);";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 更新数据库版本
update_option('wpsa_db_version', WPSA_DB_VERSION);
// 添加新设置项
$current_settings = get_option('wpsa_settings');
if (!$current_settings) {
$current_settings = array(
'track_admins' => false,
'keep_data_days' => 30,
'ignore_bots' => true,
'track_duration' => true, // 新增:是否追踪停留时间
'internal_domains' => home_url() // 新增:内部域名列表
);
add_option('wpsa_settings', $current_settings);
} else {
// 合并新设置项
$new_settings = array(
'track_duration' => isset($current_settings['track_duration']) ? $current_settings['track_duration'] : true,
'internal_domains' => isset($current_settings['internal_domains']) ? $current_settings['internal_domains'] : home_url()
);
$merged_settings = array_merge($current_settings, $new_settings);
update_option('wpsa_settings', $merged_settings);
}
}
// 检查数据库更新
add_action('plugins_loaded', 'wpsa_check_db_update');
function wpsa_check_db_update() {
if (get_option('wpsa_db_version') != WPSA_DB_VERSION) {
wpsa_activate();
}
}
// 记录访问数据
add_action('template_redirect', 'wpsa_track_visit');
function wpsa_track_visit() {
// 检查是否需要跟踪管理员
$settings = get_option('wpsa_settings');
if (!$settings['track_admins'] && current_user_can('manage_options')) {
return;
}
// 获取访问信息
$ip_address = wpsa_get_ip_address();
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$page_url = home_url($_SERVER['REQUEST_URI'] ?? '');
$referrer = $_SERVER['HTTP_REFERER'] ?? '';
// 分析来源信息
$source_info = wpsa_analyze_source($page_url, $referrer, $settings['internal_domains']);
// 分析用户代理(浏览器、系统、设备)
$user_agent_info = wpsa_analyze_user_agent($user_agent);
// 检测是否为机器人
$is_bot = wpsa_is_bot($user_agent) ? 1 : 0;
// 如果忽略机器人且检测到是机器人,则不记录
if ($settings['ignore_bots'] && $is_bot) {
return;
}
// 保存到数据库
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$visit_id = $wpdb->insert(
$table_name,
array(
'ip_address' => $ip_address,
'user_agent' => $user_agent,
'page_url' => $page_url,
'referrer' => $referrer,
'referrer_domain' => $source_info['domain'],
'source_type' => $source_info['type'],
'browser' => $user_agent_info['browser'],
'os' => $user_agent_info['os'],
'device_type' => $user_agent_info['device_type'],
'is_bot' => $is_bot
)
);
// 获取插入的ID,用于跟踪停留时间
$visit_id = $wpdb->insert_id;
// 如果启用了停留时间跟踪,添加前端脚本
if ($settings['track_duration'] && $visit_id) {
add_action('wp_footer', function() use ($visit_id) {
?>
<script>
(function() {
var startTime = new Date().getTime();
var visitId = <?php echo intval($visit_id); ?>;
// 监听页面离开事件
window.addEventListener('beforeunload', function() {
var endTime = new Date().getTime();
var duration = Math.floor((endTime - startTime) / 1000); // 转换为秒
// 发送停留时间数据
var xhr = new XMLHttpRequest();
xhr.open('POST', '<?php echo admin_url('admin-ajax.php'); ?>', false);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('action=wpsa_track_duration&visit_id=' + visitId + '&duration=' + duration + '&nonce=<?php echo wp_create_nonce('wpsa-duration-nonce'); ?>');
});
})();
</script>
<?php
});
}
// 定期清理旧数据
if (rand(1, 100) <= 5) { // 5%的概率执行清理
wpsa_cleanup_old_data();
}
}
// 处理停留时间更新
add_action('wp_ajax_nopriv_wpsa_track_duration', 'wpsa_update_visit_duration');
add_action('wp_ajax_wpsa_track_duration', 'wpsa_update_visit_duration');
function wpsa_update_visit_duration() {
check_ajax_referer('wpsa-duration-nonce', 'nonce');
$visit_id = isset($_POST['visit_id']) ? intval($_POST['visit_id']) : 0;
$duration = isset($_POST['duration']) ? intval($_POST['duration']) : 0;
// 限制最大停留时间为2小时(防止异常值)
if ($duration > 7200) {
$duration = 7200;
}
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$wpdb->update(
$table_name,
array('visit_duration' => $duration),
array('id' => $visit_id)
);
wp_die();
}
// 分析访问来源
function wpsa_analyze_source($current_url, $referrer, $internal_domains) {
$result = array(
'type' => 'direct',
'domain' => ''
);
// 无来源视为直接访问
if (empty($referrer)) {
return $result;
}
// 解析来源域名
$parsed_referrer = parse_url($referrer);
if (!isset($parsed_referrer['host'])) {
return $result;
}
$referrer_domain = $parsed_referrer['host'];
$result['domain'] = $referrer_domain;
// 解析当前域名
$parsed_current = parse_url($current_url);
$current_domain = isset($parsed_current['host']) ? $parsed_current['host'] : '';
// 检查是否为内部链接(同一域名或设置的内部域名)
$internal_domains = explode("\n", str_replace("\r", "", $internal_domains));
$is_internal = false;
if ($referrer_domain == $current_domain) {
$is_internal = true;
} else {
foreach ($internal_domains as $domain) {
$domain = trim($domain);
if (!empty($domain) && strpos($referrer_domain, $domain) !== false) {
$is_internal = true;
break;
}
}
}
if ($is_internal) {
$result['type'] = 'internal';
return $result;
}
// 检查是否为搜索引擎
$search_engines = array(
'google', 'bing', 'yahoo', 'baidu', 'yandex',
'ask', 'duckduckgo', 'sogou', 'so', '360'
);
foreach ($search_engines as $engine) {
if (stripos($referrer_domain, $engine) !== false) {
$result['type'] = 'search';
return $result;
}
}
// 检查是否为社交媒体
$social_sites = array(
'facebook', 'twitter', 'instagram', 'linkedin',
'pinterest', 'reddit', 'tiktok', 'youtube', 'weibo', 'wechat'
);
foreach ($social_sites as $site) {
if (stripos($referrer_domain, $site) !== false) {
$result['type'] = 'social';
return $result;
}
}
// 其他外部来源
$result['type'] = 'external';
return $result;
}
// 分析用户代理(浏览器、系统、设备)
function wpsa_analyze_user_agent($user_agent) {
$result = array(
'browser' => 'Unknown',
'os' => 'Unknown',
'device_type' => 'other'
);
// 检测浏览器
if (stripos($user_agent, 'Chrome') !== false && stripos($user_agent, 'Edg') === false) {
$result['browser'] = 'Chrome';
} elseif (stripos($user_agent, 'Firefox') !== false) {
$result['browser'] = 'Firefox';
} elseif (stripos($user_agent, 'Safari') !== false && stripos($user_agent, 'Chrome') === false) {
$result['browser'] = 'Safari';
} elseif (stripos($user_agent, 'Edge') !== false || stripos($user_agent, 'Edg') !== false) {
$result['browser'] = 'Edge';
} elseif (stripos($user_agent, 'Opera') !== false || stripos($user_agent, 'OPR') !== false) {
$result['browser'] = 'Opera';
} elseif (stripos($user_agent, 'MSIE') !== false || stripos($user_agent, 'Trident') !== false) {
$result['browser'] = 'Internet Explorer';
}
// 检测操作系统
if (stripos($user_agent, 'Windows') !== false) {
$result['os'] = 'Windows';
} elseif (stripos($user_agent, 'Macintosh') !== false) {
$result['os'] = 'macOS';
} elseif (stripos($user_agent, 'iPhone') !== false) {
$result['os'] = 'iOS';
} elseif (stripos($user_agent, 'iPad') !== false) {
$result['os'] = 'iOS';
} elseif (stripos($user_agent, 'Android') !== false) {
$result['os'] = 'Android';
} elseif (stripos($user_agent, 'Linux') !== false) {
$result['os'] = 'Linux';
}
// 检测设备类型
if (stripos($user_agent, 'Mobile') !== false && stripos($user_agent, 'iPad') === false) {
$result['device_type'] = 'mobile';
} elseif (stripos($user_agent, 'iPad') !== false || (stripos($user_agent, 'Tablet') !== false)) {
$result['device_type'] = 'tablet';
} elseif (stripos($user_agent, 'Windows') !== false || stripos($user_agent, 'Macintosh') !== false || stripos($user_agent, 'Linux') !== false) {
$result['device_type'] = 'desktop';
}
return $result;
}
// 获取用户IP地址
function wpsa_get_ip_address() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return sanitize_text_field($_SERVER['HTTP_CLIENT_IP']);
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return sanitize_text_field(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
} else {
return sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? 'unknown');
}
}
// 检测是否为机器人
function wpsa_is_bot($user_agent) {
$bot_patterns = array(
'bot', 'crawl', 'spider', 'slurp', 'search',
'bingbot', 'googlebot', 'yandexbot', 'baiduspider',
'robot', 'curl', 'wget', 'python', 'java', 'scraper'
);
foreach ($bot_patterns as $pattern) {
if (stripos($user_agent, $pattern) !== false) {
return true;
}
}
return false;
}
// 清理旧数据
function wpsa_cleanup_old_data() {
$settings = get_option('wpsa_settings');
$days = intval($settings['keep_data_days']);
if ($days <= 0) {
$days = 30; // 默认保留30天数据
}
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$date = date('Y-m-d H:i:s', strtotime("-$days days"));
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $table_name WHERE visit_time < %s",
$date
)
);
}
// 添加管理菜单
add_action('admin_menu', 'wpsa_add_admin_menu');
function wpsa_add_admin_menu() {
add_menu_page(
'访问统计',
'访问统计',
'manage_options',
'wp-simple-analytics',
'wpsa_render_dashboard',
'dashicons-chart-bar',
6
);
add_submenu_page(
'wp-simple-analytics',
'统计设置',
'设置',
'manage_options',
'wpsa-settings',
'wpsa_render_settings'
);
}
// 注册设置
add_action('admin_init', 'wpsa_register_settings');
function wpsa_register_settings() {
register_setting('wpsa_settings_group', 'wpsa_settings', 'wpsa_sanitize_settings');
add_settings_section(
'wpsa_general_section',
'基本设置',
'wpsa_general_section_desc',
'wpsa-settings'
);
add_settings_field(
'track_admins',
'跟踪管理员访问',
'wpsa_track_admins_field',
'wpsa-settings',
'wpsa_general_section'
);
add_settings_field(
'ignore_bots',
'忽略机器人访问',
'wpsa_ignore_bots_field',
'wpsa-settings',
'wpsa_general_section'
);
add_settings_field(
'track_duration',
'跟踪页面停留时间',
'wpsa_track_duration_field',
'wpsa-settings',
'wpsa_general_section'
);
add_settings_field(
'keep_data_days',
'数据保留天数',
'wpsa_keep_data_days_field',
'wpsa-settings',
'wpsa_general_section'
);
add_settings_section(
'wpsa_source_section',
'来源识别设置',
'wpsa_source_section_desc',
'wpsa-settings'
);
add_settings_field(
'internal_domains',
'内部域名列表',
'wpsa_internal_domains_field',
'wpsa-settings',
'wpsa_source_section'
);
}
// 验证设置
function wpsa_sanitize_settings($input) {
$sanitized = array();
$sanitized['track_admins'] = isset($input['track_admins']) ? (bool)$input['track_admins'] : false;
$sanitized['ignore_bots'] = isset($input['ignore_bots']) ? (bool)$input['ignore_bots'] : true;
$sanitized['track_duration'] = isset($input['track_duration']) ? (bool)$input['track_duration'] : true;
$sanitized['keep_data_days'] = isset($input['keep_data_days']) ? max(7, intval($input['keep_data_days'])) : 30;
$sanitized['internal_domains'] = isset($input['internal_domains']) ? sanitize_textarea_field($input['internal_domains']) : home_url();
return $sanitized;
}
// 设置字段渲染函数
function wpsa_general_section_desc() {
echo '<p>配置访问统计的基本选项</p>';
}
function wpsa_track_admins_field() {
$settings = get_option('wpsa_settings');
$checked = $settings['track_admins'] ? 'checked' : '';
echo '<input type="checkbox" name="wpsa_settings[track_admins]" ' . $checked . '>';
echo '<p class="description">是否统计管理员账号的访问记录</p>';
}
function wpsa_ignore_bots_field() {
$settings = get_option('wpsa_settings');
$checked = $settings['ignore_bots'] ? 'checked' : '';
echo '<input type="checkbox" name="wpsa_settings[ignore_bots]" ' . $checked . '>';
echo '<p class="description">是否忽略搜索引擎爬虫等机器人访问</p>';
}
function wpsa_track_duration_field() {
$settings = get_option('wpsa_settings');
$checked = $settings['track_duration'] ? 'checked' : '';
echo '<input type="checkbox" name="wpsa_settings[track_duration]" ' . $checked . '>';
echo '<p class="description">是否跟踪用户在页面上的停留时间</p>';
}
function wpsa_keep_data_days_field() {
$settings = get_option('wpsa_settings');
$value = $settings['keep_data_days'];
echo '<input type="number" name="wpsa_settings[keep_data_days]" value="' . esc_attr($value) . '" min="7" max="365"> 天';
echo '<p class="description">统计数据保留的天数,超过此天数的数据将被自动清理</p>';
}
function wpsa_source_section_desc() {
echo '<p>配置访问来源识别的相关选项</p>';
}
function wpsa_internal_domains_field() {
$settings = get_option('wpsa_settings');
$value = $settings['internal_domains'];
echo '<textarea name="wpsa_settings[internal_domains]" rows="4" cols="50" class="large-text">' . esc_textarea($value) . '</textarea>';
echo '<p class="description">属于您网站的域名列表(每行一个),用于识别内部访问来源</p>';
}
// 渲染设置页面
function wpsa_render_settings() {
?>
<div class="wrap">
<h1>访问统计设置</h1>
<form action="options.php" method="post">
<?php
settings_fields('wpsa_settings_group');
do_settings_sections('wpsa-settings');
submit_button('保存设置');
?>
</form>
</div>
<?php
}
// 处理数据导出
add_action('admin_post_wpsa_export_data', 'wpsa_export_data');
function wpsa_export_data() {
if (!current_user_can('manage_options')) {
wp_die('没有权限执行此操作');
}
check_admin_referrer('wpsa_export_nonce');
// 获取日期范围
$start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : '';
$end_date = isset($_POST['end_date']) ? sanitize_text_field($_POST['end_date']) : '';
$export_type = isset($_POST['export_type']) ? sanitize_text_field($_POST['export_type']) : 'visits';
// 验证日期
if (empty($start_date) || empty($end_date) || strtotime($start_date) === false || strtotime($end_date) === false) {
wp_die('日期格式不正确');
}
// 格式化日期
$start_date = date('Y-m-d 00:00:00', strtotime($start_date));
$end_date = date('Y-m-d 23:59:59', strtotime($end_date));
// 导出数据
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="wpsa-export-' . $export_type . '-' . date('Ymd') . '.csv"');
$output = fopen('php://output', 'w');
fprintf($output, chr(0xEF) . chr(0xBB) . chr(0xBF)); // UTF-8 BOM
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
switch ($export_type) {
case 'visits':
// 导出访问记录
fputcsv($output, array('ID', '访问时间', 'IP地址', '页面URL', '来源类型', '来源域名', '浏览器', '操作系统', '设备类型', '停留时间(秒)'));
$visits = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $table_name WHERE visit_time BETWEEN %s AND %s ORDER BY visit_time DESC",
$start_date,
$end_date
)
);
foreach ($visits as $visit) {
fputcsv($output, array(
$visit->id,
$visit->visit_time,
$visit->ip_address,
$visit->page_url,
wpsa_get_source_type_label($visit->source_type),
$visit->referrer_domain,
$visit->browser,
$visit->os,
ucfirst($visit->device_type),
$visit->visit_duration
));
}
break;
case 'pages':
// 导出页面统计
fputcsv($output, array('页面URL', '页面标题', '访问量', '平均停留时间(秒)'));
$pages = $wpdb->get_results(
$wpdb->prepare(
"SELECT page_url, COUNT(*) as visit_count, AVG(visit_duration) as avg_duration
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY page_url
ORDER BY visit_count DESC",
$start_date,
$end_date
)
);
foreach ($pages as $page) {
fputcsv($output, array(
$page->page_url,
wpsa_get_page_title($page->page_url),
$page->visit_count,
round($page->avg_duration, 1)
));
}
break;
case 'sources':
// 导出来源统计
fputcsv($output, array('来源类型', '来源域名', '访问量'));
$sources = $wpdb->get_results(
$wpdb->prepare(
"SELECT source_type, referrer_domain, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY source_type, referrer_domain
ORDER BY visit_count DESC",
$start_date,
$end_date
)
);
foreach ($sources as $source) {
fputcsv($output, array(
wpsa_get_source_type_label($source->source_type),
$source->referrer_domain ?: '无',
$source->visit_count
));
}
break;
}
fclose($output);
exit;
}
// 获取来源类型标签
function wpsa_get_source_type_label($type) {
$labels = array(
'direct' => '直接访问',
'search' => '搜索引擎',
'social' => '社交媒体',
'external' => '外部网站',
'internal' => '内部链接'
);
return isset($labels[$type]) ? $labels[$type] : $type;
}
// 渲染统计仪表盘
function wpsa_render_dashboard() {
// 获取日期范围(默认最近30天)
$default_start = date('Y-m-d', strtotime('-30 days'));
$default_end = date('Y-m-d');
$start_date = isset($_POST['start_date']) ? sanitize_text_field($_POST['start_date']) : $default_start;
$end_date = isset($_POST['end_date']) ? sanitize_text_field($_POST['end_date']) : $default_end;
// 格式化日期用于查询
$start_datetime = date('Y-m-d 00:00:00', strtotime($start_date));
$end_datetime = date('Y-m-d 23:59:59', strtotime($end_date));
// 获取统计数据
$period_stats = wpsa_get_stats_by_period($start_datetime, $end_datetime);
$top_pages = wpsa_get_top_pages(10, $start_datetime, $end_datetime);
$daily_trend = wpsa_get_daily_trend($start_datetime, $end_datetime);
$source_stats = wpsa_get_source_stats($start_datetime, $end_datetime);
$top_referrers = wpsa_get_top_referrers(10, $start_datetime, $end_datetime);
$browser_stats = wpsa_get_browser_stats($start_datetime, $end_datetime);
$device_stats = wpsa_get_device_stats($start_datetime, $end_datetime);
$os_stats = wpsa_get_os_stats($start_datetime, $end_datetime);
?>
<div class="wrap">
<h1>访问统计仪表盘</h1>
<!-- 日期范围选择器 -->
<div class="wp-card mb-4">
<div class="wp-card-body">
<form method="post" action="">
<?php wp_nonce_field('wpsa_date_range_nonce'); ?>
<div class="date-range-selector">
<label for="start_date">开始日期:</label>
<input type="date" id="start_date" name="start_date" value="<?php echo esc_attr($start_date); ?>" required>
<label for="end_date" class="ml-4">结束日期:</label>
<input type="date" id="end_date" name="end_date" value="<?php echo esc_attr($end_date); ?>" required>
<button type="submit" class="button button-primary ml-4">更新统计</button>
<!-- 导出按钮 -->
<button type="button" class="button button-secondary ml-4" id="exportToggle">导出数据</button>
</div>
<!-- 导出选项(默认隐藏) -->
<div id="exportOptions" class="export-options mt-4 hidden">
<h4>导出数据</h4>
<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
<?php wp_nonce_field('wpsa_export_nonce'); ?>
<input type="hidden" name="action" value="wpsa_export_data">
<input type="hidden" name="start_date" value="<?php echo esc_attr($start_date); ?>">
<input type="hidden" name="end_date" value="<?php echo esc_attr($end_date); ?>">
<label>
<input type="radio" name="export_type" value="visits" checked> 访问记录
</label>
<label class="ml-4">
<input type="radio" name="export_type" value="pages"> 页面统计
</label>
<label class="ml-4">
<input type="radio" name="export_type" value="sources"> 来源统计
</label>
<button type="submit" class="button button-primary ml-4">导出CSV</button>
</form>
</div>
</form>
</div>
</div>
<!-- 统计概览卡片 -->
<div class="wp-card-grid">
<div class="wp-card">
<div class="wp-card-header">总访问量</div>
<div class="wp-card-body">
<div class="stat-number"><?php echo $period_stats['visits']; ?></div>
<div class="stat-label"><?php echo $start_date; ?> 至 <?php echo $end_date; ?></div>
</div>
<div class="wp-card-footer">
<span class="stat-detail">平均每日: <?php echo $period_stats['avg_daily_visits']; ?></span>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">独立访客</div>
<div class="wp-card-body">
<div class="stat-number"><?php echo $period_stats['unique_visitors']; ?></div>
<div class="stat-label">按IP地址统计</div>
</div>
<div class="wp-card-footer">
<span class="stat-detail">访客率: <?php echo $period_stats['visitor_rate']; ?>%</span>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">平均停留时间</div>
<div class="wp-card-body">
<div class="stat-number"><?php echo $period_stats['avg_duration']; ?></div>
<div class="stat-label">秒/次访问</div>
</div>
<div class="wp-card-footer">
<span class="stat-detail">总停留时间: <?php echo $period_stats['total_duration']; ?></span>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">平均访问页数</div>
<div class="wp-card-body">
<div class="stat-number"><?php echo $period_stats['avg_pages_per_visit']; ?></div>
<div class="stat-label">页/次访问</div>
</div>
<div class="wp-card-footer">
<span class="stat-detail">总浏览页数: <?php echo $period_stats['total_pages']; ?></span>
</div>
</div>
</div>
<!-- 访问趋势图表 -->
<div class="wp-card">
<div class="wp-card-header">访问趋势</div>
<div class="wp-card-body">
<canvas id="visitsChart" height="300"></canvas>
</div>
</div>
<!-- 来源分析 -->
<div class="wp-card-grid two-columns">
<div class="wp-card">
<div class="wp-card-header">访问来源分布</div>
<div class="wp-card-body">
<canvas id="sourcesChart" height="300"></canvas>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">主要来源网站 (Top 10)</div>
<div class="wp-card-body">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>来源</th>
<th>类型</th>
<th>访问量</th>
</tr>
</thead>
<tbody>
<?php if (!empty($top_referrers)) : ?>
<?php foreach ($top_referrers as $referrer) : ?>
<tr>
<td>
<?php echo $referrer->referrer_domain ? esc_html($referrer->referrer_domain) : '直接访问'; ?>
</td>
<td><?php echo wpsa_get_source_type_label($referrer->source_type); ?></td>
<td><?php echo $referrer->visit_count; ?></td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td colspan="3" class="text-center">暂无数据</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- 设备和浏览器统计 -->
<div class="wp-card-grid three-columns">
<div class="wp-card">
<div class="wp-card-header">设备类型分布</div>
<div class="wp-card-body">
<canvas id="devicesChart" height="250"></canvas>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">浏览器分布</div>
<div class="wp-card-body">
<canvas id="browsersChart" height="250"></canvas>
</div>
</div>
<div class="wp-card">
<div class="wp-card-header">操作系统分布</div>
<div class="wp-card-body">
<canvas id="osChart" height="250"></canvas>
</div>
</div>
</div>
<!-- 热门页面 -->
<div class="wp-card">
<div class="wp-card-header">热门页面 (Top 10)</div>
<div class="wp-card-body">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>排名</th>
<th>页面</th>
<th>访问量</th>
<th>平均停留时间(秒)</th>
</tr>
</thead>
<tbody>
<?php if (!empty($top_pages)) : ?>
<?php $i = 1; foreach ($top_pages as $page) : ?>
<tr>
<td><?php echo $i++; ?></td>
<td>
<a href="<?php echo esc_url($page->page_url); ?>" target="_blank">
<?php echo esc_html(wpsa_get_page_title($page->page_url)); ?>
</a>
<div class="row-actions">
<a href="<?php echo esc_url($page->page_url); ?>" target="_blank">查看</a>
</div>
</td>
<td><?php echo $page->visit_count; ?></td>
<td><?php echo round($page->avg_duration, 1); ?></td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td colspan="4" class="text-center">暂无数据</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- 图表和样式 -->
<style>
.wp-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.two-columns {
grid-template-columns: repeat(2, 1fr);
}
.three-columns {
grid-template-columns: repeat(3, 1fr);
}
.wp-card {
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.13);
margin-bottom: 20px;
}
.wp-card-header {
padding: 15px 20px;
border-bottom: 1px solid #eee;
font-weight: 600;
}
.wp-card-body {
padding: 20px;
}
.wp-card-footer {
padding: 10px 20px;
border-top: 1px solid #eee;
color: #666;
font-size: 13px;
}
.stat-number {
font-size: 32px;
font-weight: 700;
color: #1d2327;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 14px;
}
.stat-detail {
font-size: 13px;
}
.date-range-selector {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.export-options {
padding: 15px;
border: 1px solid #eee;
border-radius: 4px;
}
.hidden {
display: none;
}
.text-center {
text-align: center;
}
.ml-4 {
margin-left: 16px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 导出选项切换
document.getElementById('exportToggle').addEventListener('click', function() {
const exportOptions = document.getElementById('exportOptions');
exportOptions.classList.toggle('hidden');
});
// 访问趋势图表
document.addEventListener('DOMContentLoaded', function() {
// 访问趋势图
const visitsCtx = document.getElementById('visitsChart').getContext('2d');
const labels = <?php echo json_encode(array_column($daily_trend, 'date')); ?>;
const visitsData = <?php echo json_encode(array_column($daily_trend, 'visits')); ?>;
const uniqueData = <?php echo json_encode(array_column($daily_trend, 'unique_visitors')); ?>;
new Chart(visitsCtx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '总访问量',
data: visitsData,
borderColor: '#2271b1',
backgroundColor: 'rgba(34, 113, 177, 0.1)',
tension: 0.3,
fill: true
},
{
label: '独立访客',
data: uniqueData,
borderColor: '#00a32a',
backgroundColor: 'rgba(0, 163, 42, 0.1)',
tension: 0.3,
fill: true
}
]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top',
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
// 来源分布图
const sourcesCtx = document.getElementById('sourcesChart').getContext('2d');
const sourceLabels = <?php echo json_encode(array_column($source_stats, 'label')); ?>;
const sourceData = <?php echo json_encode(array_column($source_stats, 'value')); ?>;
const sourceColors = [
'#2271b1', // 直接访问 - 蓝色
'#00a32a', // 搜索引擎 - 绿色
'#dba617', // 社交媒体 - 黄色
'#d63638', // 外部网站 - 红色
'#9370db' // 内部链接 - 紫色
];
new Chart(sourcesCtx, {
type: 'doughnut',
data: {
labels: sourceLabels,
datasets: [{
data: sourceData,
backgroundColor: sourceColors,
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'right',
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = Math.round((value / total) * 100);
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
// 设备类型分布图
const devicesCtx = document.getElementById('devicesChart').getContext('2d');
const deviceLabels = <?php echo json_encode(array_column($device_stats, 'label')); ?>;
const deviceData = <?php echo json_encode(array_column($device_stats, 'value')); ?>;
new Chart(devicesCtx, {
type: 'pie',
data: {
labels: deviceLabels,
datasets: [{
data: deviceData,
backgroundColor: ['#2271b1', '#00a32a', '#dba617', '#9370db'],
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom',
}
}
}
});
// 浏览器分布图
const browsersCtx = document.getElementById('browsersChart').getContext('2d');
const browserLabels = <?php echo json_encode(array_column($browser_stats, 'label')); ?>;
const browserData = <?php echo json_encode(array_column($browser_stats, 'value')); ?>;
new Chart(browsersCtx, {
type: 'bar',
data: {
labels: browserLabels,
datasets: [{
label: '访问量',
data: browserData,
backgroundColor: 'rgba(34, 113, 177, 0.7)',
borderColor: 'rgba(34, 113, 177, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
// 操作系统分布图
const osCtx = document.getElementById('osChart').getContext('2d');
const osLabels = <?php echo json_encode(array_column($os_stats, 'label')); ?>;
const osData = <?php echo json_encode(array_column($os_stats, 'value')); ?>;
new Chart(osCtx, {
type: 'bar',
data: {
labels: osLabels,
datasets: [{
label: '访问量',
data: osData,
backgroundColor: 'rgba(0, 163, 42, 0.7)',
borderColor: 'rgba(0, 163, 42, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
precision: 0
}
}
}
}
});
});
</script>
<?php
}
// 获取指定时间段的统计数据
function wpsa_get_stats_by_period($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
// 总访问量
$visits = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE visit_time BETWEEN %s AND %s",
$start_datetime,
$end_datetime
)
);
// 独立访客数(按IP)
$unique_visitors = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT ip_address) FROM $table_name WHERE visit_time BETWEEN %s AND %s",
$start_datetime,
$end_datetime
)
);
// 总停留时间(秒)
$total_duration = $wpdb->get_var(
$wpdb->prepare(
"SELECT SUM(visit_duration) FROM $table_name WHERE visit_time BETWEEN %s AND %s",
$start_datetime,
$end_datetime
)
);
// 总浏览页数(即总访问量,每个访问对应一个页面)
$total_pages = $visits;
// 计算天数
$start = new DateTime($start_datetime);
$end = new DateTime($end_datetime);
$days = $end->diff($start)->days + 1; // 包含起止日期
// 计算衍生指标
$avg_daily_visits = $days > 0 ? round($visits / $days, 1) : 0;
$visitor_rate = $visits > 0 ? round(($unique_visitors / $visits) * 100, 1) : 0;
$avg_duration = $visits > 0 ? round($total_duration / $visits, 1) : 0;
$avg_pages_per_visit = $unique_visitors > 0 ? round($visits / $unique_visitors, 1) : 0;
// 格式化总停留时间
$total_duration_formatted = wpsa_format_seconds($total_duration);
return array(
'visits' => intval($visits),
'unique_visitors' => intval($unique_visitors),
'total_duration' => $total_duration_formatted,
'total_pages' => intval($total_pages),
'avg_daily_visits' => $avg_daily_visits,
'visitor_rate' => $visitor_rate,
'avg_duration' => $avg_duration,
'avg_pages_per_visit' => $avg_pages_per_visit
);
}
// 格式化秒数为易读格式
function wpsa_format_seconds($seconds) {
$seconds = intval($seconds);
if ($seconds < 60) {
return $seconds . ' 秒';
} elseif ($seconds < 3600) {
return floor($seconds / 60) . ' 分 ' . ($seconds % 60) . ' 秒';
} else {
return floor($seconds / 3600) . ' 时 ' . floor(($seconds % 3600) / 60) . ' 分';
}
}
// 获取热门页面
function wpsa_get_top_pages($limit = 10, $start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
return $wpdb->get_results(
$wpdb->prepare(
"SELECT page_url, COUNT(*) as visit_count, AVG(visit_duration) as avg_duration
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY page_url
ORDER BY visit_count DESC
LIMIT %d",
$start_datetime,
$end_datetime,
$limit
)
);
}
// 获取每日趋势数据
function wpsa_get_daily_trend($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT
DATE(visit_time) as date,
COUNT(*) as visits,
COUNT(DISTINCT ip_address) as unique_visitors
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY DATE(visit_time)
ORDER BY date ASC",
$start_datetime,
$end_datetime
)
);
// 确保包含所有日期(即使某天没有数据)
$trend = array();
$start = new DateTime($start_datetime);
$end = new DateTime($end_datetime);
$current = clone $start;
while ($current <= $end) {
$date = $current->format('Y-m-d');
$found = false;
foreach ($results as $result) {
if ($result->date == $date) {
$trend[] = array(
'date' => $current->format('m-d'),
'visits' => intval($result->visits),
'unique_visitors' => intval($result->unique_visitors)
);
$found = true;
break;
}
}
if (!$found) {
$trend[] = array(
'date' => $current->format('m-d'),
'visits' => 0,
'unique_visitors' => 0
);
}
$current->modify('+1 day');
}
return $trend;
}
// 获取来源统计
function wpsa_get_source_stats($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT source_type, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY source_type
ORDER BY visit_count DESC",
$start_datetime,
$end_datetime
)
);
$source_types = array('direct', 'search', 'social', 'external', 'internal');
$stats = array();
foreach ($source_types as $type) {
$found = false;
foreach ($results as $result) {
if ($result->source_type == $type) {
$stats[] = array(
'label' => wpsa_get_source_type_label($type),
'value' => intval($result->visit_count)
);
$found = true;
break;
}
}
if (!$found) {
$stats[] = array(
'label' => wpsa_get_source_type_label($type),
'value' => 0
);
}
}
return $stats;
}
// 获取主要来源网站
function wpsa_get_top_referrers($limit = 10, $start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
return $wpdb->get_results(
$wpdb->prepare(
"SELECT referrer_domain, source_type, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY referrer_domain, source_type
ORDER BY visit_count DESC
LIMIT %d",
$start_datetime,
$end_datetime,
$limit
)
);
}
// 获取浏览器统计
function wpsa_get_browser_stats($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT browser, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY browser
ORDER BY visit_count DESC
LIMIT 5", // 只取前5种主要浏览器
$start_datetime,
$end_datetime
)
);
// 合并"其他"类别
$stats = array();
$other_count = 0;
$total = 0;
foreach ($results as $result) {
$total += $result->visit_count;
}
// 取前4种,其余合并为"其他"
for ($i = 0; $i < count($results); $i++) {
if ($i < 4) {
$stats[] = array(
'label' => $results[$i]->browser,
'value' => intval($results[$i]->visit_count)
);
} else {
$other_count += $results[$i]->visit_count;
}
}
if ($other_count > 0) {
$stats[] = array(
'label' => '其他',
'value' => $other_count
);
}
return $stats;
}
// 获取设备类型统计
function wpsa_get_device_stats($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT device_type, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY device_type
ORDER BY visit_count DESC",
$start_datetime,
$end_datetime
)
);
$device_types = array('desktop', 'mobile', 'tablet', 'other');
$stats = array();
foreach ($device_types as $type) {
$found = false;
foreach ($results as $result) {
if ($result->device_type == $type) {
$stats[] = array(
'label' => ucfirst($type),
'value' => intval($result->visit_count)
);
$found = true;
break;
}
}
if (!$found) {
$stats[] = array(
'label' => ucfirst($type),
'value' => 0
);
}
}
return $stats;
}
// 获取操作系统统计
function wpsa_get_os_stats($start_datetime, $end_datetime) {
global $wpdb;
$table_name = $wpdb->prefix . 'wpsa_visits';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT os, COUNT(*) as visit_count
FROM $table_name
WHERE visit_time BETWEEN %s AND %s
GROUP BY os
ORDER BY visit_count DESC
LIMIT 5", // 只取前5种主要操作系统
$start_datetime,
$end_datetime
)
);
// 合并"其他"类别
$stats = array();
$other_count = 0;
// 取前4种,其余合并为"其他"
for ($i = 0; $i < count($results); $i++) {
if ($i < 4) {
$stats[] = array(
'label' => $results[$i]->os,
'value' => intval($results[$i]->visit_count)
);
} else {
$other_count += $results[$i]->visit_count;
}
}
if ($other_count > 0) {
$stats[] = array(
'label' => '其他',
'value' => $other_count
);
}
return $stats;
}
// 根据URL获取页面标题
function wpsa_get_page_title($url) {
// 提取路径部分
$home_url = home_url();
$path = str_replace($home_url, '', $url);
// 首页特殊处理
if ($path == '' || $path == '/') {
return '首页';
}
// 尝试通过路径获取页面
$posts = get_posts(array(
'name' => trim($path, '/'),
'post_type' => array('post', 'page'),
'numberposts' => 1
));
if (!empty($posts)) {
return $posts[0]->post_title;
}
// 尝试匹配分类
$categories = get_terms(array(
'slug' => trim($path, '/'),
'taxonomy' => 'category',
'number' => 1
));
if (!is_wp_error($categories) && !empty($categories)) {
return '分类: ' . $categories[0]->name;
}
// 无法识别的页面,返回URL的最后部分
$parts = explode('/', trim($path, '/'));
return end($parts) ?: $url;
}
没有回复内容