215 lines
8.8 KiB
PHP
215 lines
8.8 KiB
PHP
<?php
|
||
if (!defined('ABSPATH')) exit;
|
||
|
||
class Hardware_Tracker_UI {
|
||
public function __construct() {
|
||
add_action('admin_menu', [$this, 'add_menu_page']);
|
||
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
|
||
}
|
||
|
||
public function add_menu_page() {
|
||
add_menu_page(
|
||
'访客追踪',
|
||
'访客追踪',
|
||
'manage_options',
|
||
'hardware-tracker',
|
||
[$this, 'render_dashboard_page'],
|
||
'dashicons-visibility',
|
||
25
|
||
);
|
||
}
|
||
|
||
public function enqueue_assets($hook) {
|
||
if (isset($_GET['page']) && $_GET['page'] === 'hardware-tracker') {
|
||
wp_enqueue_script('hardware-tracker-script',
|
||
plugin_dir_url(__FILE__) . 'assets/script.js',
|
||
['jquery'], null, true
|
||
);
|
||
wp_enqueue_style('hardware-tracker-style',
|
||
plugin_dir_url(__FILE__) . 'assets/style.css'
|
||
);
|
||
}
|
||
}
|
||
|
||
public function render_dashboard_page() {
|
||
global $wpdb;
|
||
$table = $wpdb->prefix . 'hardware_visitors';
|
||
|
||
// 分页参数
|
||
$per_page = 20;
|
||
$current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
|
||
$offset = ($current_page - 1) * $per_page;
|
||
|
||
// 搜索逻辑
|
||
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
|
||
$where_clause = '1=1';
|
||
$query_params = [];
|
||
|
||
if (!empty($search)) {
|
||
$like = '%' . $wpdb->esc_like($search) . '%';
|
||
$where_clause .= $wpdb->prepare(" AND (ip LIKE %s OR os_name LIKE %s OR browser_name LIKE %s)", $like, $like, $like);
|
||
}
|
||
|
||
// 获取数据
|
||
$total_items = $wpdb->get_var("SELECT COUNT(*) FROM $table WHERE $where_clause");
|
||
$results = $wpdb->get_results($wpdb->prepare(
|
||
"SELECT * FROM $table
|
||
WHERE $where_clause
|
||
ORDER BY created_at DESC
|
||
LIMIT %d OFFSET %d",
|
||
$per_page, $offset
|
||
));
|
||
|
||
// 界面输出
|
||
echo '<div class="wrap"><h1>访客追踪</h1>';
|
||
|
||
// 搜索框
|
||
echo '<form method="get" class="search-form">
|
||
<input type="hidden" name="page" value="hardware-tracker">
|
||
<div class="search-box">
|
||
<input type="search" name="s" value="' . esc_attr($search) . '" placeholder="搜索 IP/浏览器/系统">
|
||
<button type="submit" class="button">搜索</button>
|
||
</div>
|
||
</form>';
|
||
|
||
// 数据表格
|
||
echo '<table class="widefat striped hardware-tracker-table">
|
||
<thead>
|
||
<tr>
|
||
<th>时间</th>
|
||
<th>操作系统</th>
|
||
<th>浏览器</th>
|
||
<th>IP地址</th>
|
||
<th>地理位置</th>
|
||
<th>硬件配置</th>
|
||
<th>用户代理</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>';
|
||
|
||
if ($results) {
|
||
foreach ($results as $row) {
|
||
// 服务器时间(上海时区)
|
||
$server_time = date('Y-m-d H:i:s', strtotime($row->created_at));
|
||
|
||
// ============== 用户时间(浏览器时区) ==============
|
||
$user_time = '未知';
|
||
$user_timezone = null;
|
||
if (!empty($row->timezone)) {
|
||
try {
|
||
$user_timezone = new DateTimeZone($row->timezone);
|
||
$user_time = (new DateTime($row->created_at, new DateTimeZone('Asia/Shanghai')))
|
||
->setTimezone($user_timezone)
|
||
->format('Y-m-d H:i:s');
|
||
} catch (Exception $e) {
|
||
$user_time = '时区无效';
|
||
}
|
||
}
|
||
|
||
// ============== IP地区时间(GeoIP时区) ==============
|
||
$ip_time = '未知';
|
||
$geo_timezone = $row->geo_timezone ?? 'unknown';
|
||
if (!empty($geo_timezone) && $geo_timezone !== 'unknown') {
|
||
try {
|
||
$ip_timezone = new DateTimeZone($geo_timezone);
|
||
$ip_time = (new DateTime($row->created_at, new DateTimeZone('Asia/Shanghai')))
|
||
->setTimezone($ip_timezone)
|
||
->format('Y-m-d H:i:s');
|
||
} catch (Exception $e) {
|
||
$ip_time = '时区无效: ' . esc_html($geo_timezone);
|
||
}
|
||
}
|
||
|
||
// ============== 时区不一致警告 ==============
|
||
$timezone_warning = '';
|
||
if ($user_timezone && $geo_timezone !== 'unknown') {
|
||
$time_diff = $this->calculate_timezone_diff($user_timezone, new DateTimeZone($geo_timezone));
|
||
if ($time_diff !== 0) {
|
||
$timezone_warning = '<div class="timezone-warning" style="color:#d63638;">
|
||
⚠️ 时区偏移: ' . $time_diff . ' 小时
|
||
</div>';
|
||
}
|
||
}
|
||
|
||
// 输出表格行
|
||
echo '<tr>';
|
||
echo '<td>
|
||
<div class="time-group">
|
||
<div class="time-server">🕒 服务器: ' . esc_html($server_time) . '</div>
|
||
<div class="time-user">👤 用户: ' . esc_html($user_time) . '</div>
|
||
<div class="time-ip">🌍 IP地区: ' . esc_html($ip_time) . '</div>
|
||
' . $timezone_warning . '
|
||
</div>
|
||
</td>';
|
||
echo '<td>' . esc_html("{$row->os_name} {$row->os_version}") . '</td>';
|
||
echo '<td>' . esc_html("{$row->browser_name} {$row->browser_version}") . '</td>';
|
||
echo '<td>' . esc_html($row->ip) . '</td>';
|
||
echo '<td>
|
||
<div class="geo-info">
|
||
<div class="geo-country">🏳️ ' . esc_html($row->country) . '</div>
|
||
<div class="geo-region">📍 ' . esc_html("{$row->region} · {$row->city}") . '</div>
|
||
<div class="geo-timezone">⏰ ' . esc_html($geo_timezone) . '</div>
|
||
<button class="button view-location"
|
||
data-lat="' . esc_attr($row->latitude) . '"
|
||
data-lng="' . esc_attr($row->longitude) . '">
|
||
查看经纬度
|
||
</button>
|
||
</div>
|
||
</td>';
|
||
echo '<td>
|
||
<div class="hardware-info">
|
||
<div class="cpu-info">💻 ' . esc_html("{$row->cpu_arch} · {$row->cpu_cores}核") . '</div>
|
||
<button class="button view-gpu"
|
||
data-gpu="' . esc_attr("{$row->gpu_vendor} - {$row->gpu_model}") . '">
|
||
GPU详情
|
||
</button>
|
||
</div>
|
||
</td>';
|
||
echo '<td>
|
||
<div class="ua-info">
|
||
<button class="button view-ua"
|
||
data-ua="' . esc_attr($row->user_agent) . '">
|
||
UA
|
||
</button>
|
||
<button class="button view-uach"
|
||
data-uach="' . esc_attr($row->ua_ch) . '">
|
||
UA-CH
|
||
</button>
|
||
</div>
|
||
</td>';
|
||
echo '</tr>';
|
||
}
|
||
} else {
|
||
echo '<tr><td colspan="7" class="no-data">😢 暂无追踪数据</td></tr>';
|
||
}
|
||
|
||
echo '</tbody></table>';
|
||
|
||
// 分页导航
|
||
if ($total_items > $per_page) {
|
||
echo '<div class="tablenav bottom">
|
||
<div class="tablenav-pages">
|
||
' . paginate_links([
|
||
'base' => add_query_arg('paged', '%#%'),
|
||
'format' => '',
|
||
'current' => $current_page,
|
||
'total' => ceil($total_items / $per_page)
|
||
]) . '
|
||
</div>
|
||
</div>';
|
||
}
|
||
|
||
echo '</div>'; // 结束 .wrap
|
||
}
|
||
|
||
/**
|
||
* 计算两个时区的小时差
|
||
*/
|
||
private function calculate_timezone_diff(DateTimeZone $tz1, DateTimeZone $tz2): int {
|
||
$date = new DateTime('now', $tz1);
|
||
$offset1 = $tz1->getOffset($date);
|
||
$offset2 = $tz2->getOffset($date);
|
||
return (int) round(($offset2 - $offset1) / 3600);
|
||
}
|
||
}
|