本模板是在子比文章归档页面模板【二次开发版】的基础上利用AI进行开发,代码有点臃肿,请见谅!
搭建步骤可以参考上面的文章,模板步骤的话,等我有空了补上。
感谢初一提供的思路,其实做了很久了,没空发,后续还要再优化代码和界面!!!
示例网址:文章归档-聪明才智亦如你
参考页面图:
我使用的完整代码(后续还会再次优化),结合AI写的代码,有点臃肿,请见谅:
<?php
/**
* Template name: Grace-文章归档新版
* Description: A modern archives page with article statistics and visualizations
*
* 设计理念:数据接口使用缓存,前端图表使用 Chart.js,整体视觉采用白色/浅灰背景搭配柔和蓝绿主色调,
* 突出高级感与艺术气息,适合长期运营、海量文章的网站。
*/
/* ============================= 数据统计及缓存函数 ============================= */
// 获取分类统计信息并缓存,只显示文章数量前26的分类
function get_category_statistics() {
return get_cached_data('category_stats_top26', function() {
$categories = get_categories([
'hide_empty' => false,
'orderby' => 'count',
'order' => 'DESC',
'number' => 26
]);
$data = [];
foreach ($categories as $category) {
$data[] = ['value' => $category->count, 'name' => $category->name];
}
return json_encode($data);
});
}
// 获取文章热力图数据(按天统计)
function get_heatmap_data() {
return get_cached_data('heatmap_data', function() {
global $wpdb;
$query = "
SELECT DATE(post_date) AS date, COUNT(*) AS count
FROM {$wpdb->posts}
WHERE post_type = 'post' AND post_status = 'publish'
GROUP BY DATE(post_date)
ORDER BY date ASC
";
$results = $wpdb->get_results($query, ARRAY_A);
if (!empty($results)) {
$start_date = $results[0]['date'];
$end_date = end($results)['date'];
$current_date = strtotime($start_date);
$heatmap_data = [];
while (date('Y-m-d', $current_date) <= $end_date) {
$formatted_date = date('Y-m-d', $current_date);
$count = 0;
foreach ($results as $result) {
if ($result['date'] === $formatted_date) {
$count = $result['count'];
break;
}
}
$heatmap_data[] = [$formatted_date, $count];
$current_date = strtotime('+1 day', $current_date);
}
return json_encode($heatmap_data);
} else {
return json_encode([]);
}
});
}
// 获取月度文章发布统计数据
function get_monthly_post_data() {
return get_cached_data('monthly_posts', function() {
global $wpdb;
$query = "
SELECT DATE_FORMAT(post_date, '%Y-%m') AS month, COUNT(*) AS count
FROM {$wpdb->posts}
WHERE post_type = 'post' AND post_status = 'publish'
GROUP BY DATE_FORMAT(post_date, '%Y-%m')
ORDER BY month ASC
";
$results = $wpdb->get_results($query, ARRAY_A);
$data = [];
if (!empty($results)) {
$months = [];
$counts = [];
foreach ($results as $result) {
$months[] = $result['month'];
$counts[] = (int)$result['count'];
}
$data = ['months' => $months, 'counts' => $counts];
}
return json_encode($data);
});
}
// 获取用户VIP统计数据
function get_user_vip_statistics() {
return get_cached_data('user_vip_stats', function() {
$users = get_users(['fields' => ['ID']]);
$user_counts = [
'normal' => 0,
'vip_1' => 0,
'vip_2' => 0
];
foreach ($users as $user) {
$vip_level = zib_get_user_vip_level($user->ID);
switch ($vip_level) {
case 1:
$user_counts['vip_1']++;
break;
case 2:
$user_counts['vip_2']++;
break;
default:
$user_counts['normal']++;
break;
}
}
$chart_data = [
['value' => $user_counts['normal'], 'name' => '普通用户'],
['value' => $user_counts['vip_1'], 'name' => _pz('pay_user_vip_1_name')],
['value' => $user_counts['vip_2'], 'name' => _pz('pay_user_vip_2_name')]
];
return json_encode($chart_data);
});
}
// 缓存数据函数(使用 WordPress 瞬态 API)
function get_cached_data($transient_key, $fetch_callback) {
if (false === ($data = get_transient($transient_key))) {
$data = call_user_func($fetch_callback);
set_transient($transient_key, $data, DAY_IN_SECONDS);
}
return $data;
}
// 访问统计:总访问量
function get_total_visit_count() {
global $wpdb;
$count = 0;
$views = $wpdb->get_results("SELECT meta_value FROM $wpdb->postmeta WHERE meta_key='views'");
if (!empty($views)) {
foreach ($views as $view) {
if (trim($view->meta_value) !== '') {
$count += (int)$view->meta_value;
}
}
}
return $count;
}
// 今日统计数据
function get_today_new_users() {
global $wpdb;
$today = current_time('Y-m-d');
$count = $wpdb->get_var("SELECT COUNT(ID) FROM {$wpdb->users} WHERE DATE(user_registered) = '$today'");
return $count ? intval($count) : 0;
}
function get_today_new_posts() {
global $wpdb;
$today = current_time('Y-m-d');
$count = $wpdb->get_var("SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type='post' AND post_status='publish' AND DATE(post_date) = '$today'");
return $count ? intval($count) : 0;
}
function get_today_new_comments() {
global $wpdb;
$today = current_time('Y-m-d');
$count = $wpdb->get_var("SELECT COUNT(comment_ID) FROM {$wpdb->comments} WHERE DATE(comment_date) = '$today' AND comment_approved = '1'");
return $count ? intval($count) : 0;
}
// 获取按年份分组的文章列表
function get_posts_by_year() {
global $wpdb;
$years_query = $wpdb->get_results(
"SELECT YEAR(post_date) as year
FROM $wpdb->posts
WHERE post_type = 'post' AND post_status = 'publish'
GROUP BY YEAR(post_date)
ORDER BY year DESC"
);
$years = [];
foreach ($years_query as $year_item) {
$year = $year_item->year;
$posts_in_year = get_posts([
'numberposts' => -1,
'post_type' => 'post',
'post_status' => 'publish',
'date_query' => [['year' => $year]],
'orderby' => 'date',
'order' => 'DESC'
]);
$posts_by_month = [];
foreach ($posts_in_year as $post) {
$month = mysql2date('m', $post->post_date);
$month_name = mysql2date('F', $post->post_date);
if (!isset($posts_by_month[$month])) {
$posts_by_month[$month] = [
'name' => $month_name,
'posts' => []
];
}
$posts_by_month[$month]['posts'][] = [
'id' => $post->ID,
'title' => $post->post_title,
'date' => get_the_date('Y-m-d', $post),
'permalink' => get_permalink($post)
];
}
ksort($posts_by_month);
$years[$year] = [
'months' => $posts_by_month,
'count' => count($posts_in_year)
];
}
return $years;
}
/* ============================= 脚本加载与数据传递 ============================= */
function grace_archives_new_scripts() {
wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', [], null, true);
wp_enqueue_script('charts-utils', 'https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns', ['chart-js'], null, true);
// 将数据传递给 JS
wp_enqueue_script('archives-modern-js', get_theme_file_uri('/assets/js/archives-modern.js'), ['chart-js', 'jquery'], null, true);
$data_to_js = [
'categoryData' => json_decode(get_category_statistics(), true),
'heatmapData' => json_decode(get_heatmap_data(), true),
'monthlyData' => json_decode(get_monthly_post_data(), true),
'userVipData' => json_decode(get_user_vip_statistics(), true)
];
wp_localize_script('archives-modern-js', 'archivesData', $data_to_js);
}
add_action('wp_enqueue_scripts', 'grace_archives_new_scripts');
get_header();
$post_id = get_queried_object_id();
$header_style = zib_get_page_header_style($post_id);
$post_count = wp_count_posts()->publish;
$comment_data = get_comment_count();
$total_comments = $comment_data['approved'] + $comment_data['moderated'] + $comment_data['spam'] + $comment_data['trash'];
$user_count = count_users()['total_users'];
$posts_by_year = get_posts_by_year();
?>
<!-- 内嵌样式:高级感、炫酷且富有艺术气息 -->
<style>
:root {
--card-bg: #ffffff;
--text-main: #333333;
--text-secondary: #777777;
--border-color: rgba(0,0,0,0.1);
--hover-bg: rgba(0,0,0,0.03);
--accent-color: #3498db;
--accent-soft: rgba(52,152,219,0.1);
--chart-grid: rgba(0,0,0,0.05);
--shadow-soft: 0 2px 8px rgba(0,0,0,0.05);
--shadow-medium: 0 4px 12px rgba(0,0,0,0.1);
--rounded-sm: 4px;
--rounded-md: 8px;
--rounded-lg: 12px;
--anim-standard: all 0.3s ease;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
background: #f7f7f7;
color: var(--text-main);
line-height: 1.6;
}
body.dark {
/* 高级暗色风:淡灰蓝背景 */
--card-bg: #f1f3f5;
--text-main: #2c3e50;
--text-secondary: #6c757d;
--border-color: rgba(0,0,0,0.1);
--hover-bg: rgba(0,0,0,0.03);
--chart-grid: rgba(0,0,0,0.05);
--shadow-soft: 0 2px 8px rgba(0,0,0,0.05);
--shadow-medium: 0 4px 12px rgba(0,0,0,0.1);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.stats-overview,
.dashboard-grid,
.archive-section {
margin-bottom: 32px;
}
/* Stat Cards */
.stat-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--rounded-md);
padding: 20px;
box-shadow: var(--shadow-soft);
transition: var(--anim-standard);
display: flex;
align-items: center;
position: relative;
}
.stat-card:hover {
box-shadow: var(--shadow-medium);
transform: translateY(-2px);
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
flex-shrink: 0;
}
.stat-content { flex-grow: 1; }
.stat-title { color: var(--text-secondary); font-size: 14px; margin-bottom: 6px; }
.stat-value { color: var(--text-main); font-size: 22px; font-weight: 600; }
.stat-note { color: var(--text-secondary); font-size: 12px; }
/* Chart Cards */
.chart-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--rounded-md);
padding: 20px;
box-shadow: var(--shadow-soft);
margin-bottom: 24px;
position: relative;
}
.chart-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.chart-title { color: var(--text-main); font-size: 20px; font-weight: bold; }
.chart-container { position: relative; height: 300px; width: 100%; }
/* Archive Section */
.archive-section {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--rounded-md);
padding: 24px;
box-shadow: var(--shadow-soft);
margin-bottom: 24px;
}
.archive-title {
color: var(--text-main);
font-size: 22px;
font-weight: bold;
margin-bottom: 20px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.archive-year { margin-bottom: 24px; }
.year-header, .month-header {
background: var(--accent-soft);
color: var(--accent-color);
padding: 10px 16px;
border-radius: var(--rounded-sm);
cursor: pointer;
margin-bottom: 12px;
transition: var(--anim-standard);
}
.year-header:hover, .month-header:hover { background: var(--hover-bg); }
.year-title, .month-header { font-size: 18px; }
.year-count {
font-size: 14px;
background: var(--accent-color);
color: #fff;
border-radius: 20px;
padding: 2px 10px;
}
.month-section { margin-left: 12px; position: relative; }
.post-list { margin-left: 20px; display: none; }
.post-item {
display: flex;
align-items: center;
padding: 8px 10px;
border-bottom: 1px dashed var(--border-color);
transition: var(--anim-standard);
}
.post-item:last-child { border-bottom: none; }
.post-date { color: var(--text-secondary); font-size: 13px; width: 95px; margin-right: 12px; }
.post-title a {
color: var(--text-main);
text-decoration: none;
transition: var(--anim-standard);
}
.post-title a:hover { color: var(--accent-color); }
/* Utility classes */
.mb-lg { margin-bottom: 32px; }
.expand-all-button {
background: var(--accent-color);
color: #fff;
border: none;
border-radius: var(--rounded-sm);
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
transition: var(--anim-standard);
}
.expand-all-button:hover { opacity: 0.9; }
.toggle-icon { transition: transform 0.3s ease; }
.toggle-icon.open { transform: rotate(180deg); }
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 32px;
}
@media (max-width: 900px) {
.dashboard-grid { grid-template-columns: 1fr; }
.chart-container { height: 250px; }
}
@media (max-width: 480px) {
.post-date { width: 85px; }
}
/* ========== 3D动画效果样式 ========== */
.archives-3d-header {
position: relative;
width: 100%;
height: 60vh;
min-height: 400px;
max-height: 800px;
overflow: hidden;
margin-bottom: 40px;
background-color: #000;
color: #fff;
}
.archives-3d-header * {
box-sizing: border-box;
}
/* square 仅用于鼠标跟踪效果 */
#square {
position: fixed;
left: 50vw;
top: -100px;
background-color: #fff;
width: 100px;
height: 100px;
transform: translate(-50%, -50%);
pointer-events: none;
mix-blend-mode: difference;
z-index: 10;
}
/* 修改 .boxes 容器:占满父容器并使用 flex 居中 */
.boxes {
--boxWidth: 300px;
--boxTranslate: calc(var(--boxWidth) / 2 + 2px);
--pers: calc(var(--boxWidth) * 3);
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 0 !important;
}
.boxes a {
text-decoration: unset;
color: #fff;
}
/* 取消鼠标悬停时 outline 效果 */
.boxes a.target .boxes__box__face:not(.boxes__box__face--inside),
.boxes a:hover .boxes__box__face:not(.boxes__box__face--inside) {
/* outline: #fff solid 5px; */
}
.boxes p {
color: #000;
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #fff;
font-size: 4rem;
line-height: 1.15;
width: min-content;
text-transform: uppercase;
}
/* 调整子盒子定位:使用百分比单位 */
.boxes__back,
.boxes__scene {
position: absolute;
transform: translate(-50%, -50%);
width: var(--boxWidth);
height: var(--boxWidth);
perspective: var(--pers);
}
.boxes__back--1,
.boxes__scene--1 {
left: 30%;
top: 40%;
animation: move-1 18s infinite ease-in-out;
}
.boxes__back--1 .boxes__box,
.boxes__scene--1 .boxes__box {
animation: float-1 20s infinite ease-in-out;
}
.boxes__back--2,
.boxes__scene--2 {
left: 45%;
top: 70%;
animation: move-2 18s infinite ease-in-out;
}
.boxes__back--2 .boxes__box,
.boxes__scene--2 .boxes__box {
animation: float-2 20s infinite ease-in-out;
}
.boxes__back--3,
.boxes__scene--3 {
left: 50%;
top: 30%;
animation: move-3 18s infinite ease-in-out;
}
.boxes__back--3 .boxes__box,
.boxes__scene--3 .boxes__box {
animation: float-3 20s infinite ease-in-out;
}
/* 盒子各面 */
.boxes__box {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(-100px);
transform-style: preserve-3d;
}
.boxes__box__face {
position: absolute;
width: var(--boxWidth);
height: var(--boxWidth);
display: flex;
justify-content: center;
align-items: center;
backface-visibility: visible;
border-radius: 0.5px;
border: 1px solid #fff;
}
/* 内部面取消边框 */
.boxes__box__face--inside {
border: none;
transform: rotateY(0) translateZ(0);
}
.boxes__box__face--front {
transform: rotateY(0) translateZ(var(--boxTranslate));
}
.boxes__box__face--right {
transform: rotateY(90deg) translateZ(var(--boxTranslate));
}
.boxes__box__face--back {
transform: rotateY(180deg) translateZ(var(--boxTranslate));
}
.boxes__box__face--left {
transform: rotateY(-90deg) translateZ(var(--boxTranslate));
}
.boxes__box__face--top {
transform: rotateX(90deg) translateZ(var(--boxTranslate));
}
.boxes__box__face--bottom {
transform: rotateX(-90deg) translateZ(var(--boxTranslate));
}
@keyframes float-1 {
0%, 100% {
transform: translateZ(-100px) rotateY(-20deg) rotateX(-15deg) rotateZ(10deg);
}
30% {
transform: translateZ(-100px) rotateY(-22deg) rotateX(-22deg) rotateZ(8deg);
}
50% {
transform: translateZ(-100px) rotateY(-15deg) rotateX(-20deg) rotateZ(15deg);
}
66% {
transform: translateZ(-100px) rotateY(-14deg) rotateX(-17deg) rotateZ(16deg);
}
}
@keyframes float-2 {
0%, 100% {
transform: translateZ(-100px) rotateY(20deg) rotateX(-15deg) rotateZ(-10deg);
}
40% {
transform: translateZ(-100px) rotateY(15deg) rotateX(-20deg) rotateZ(-15deg);
}
55% {
transform: translateZ(-100px) rotateY(14deg) rotateX(-17deg) rotateZ(-16deg);
}
70% {
transform: translateZ(-100px) rotateY(22deg) rotateX(-22deg) rotateZ(-8deg);
}
}
@keyframes float-3 {
0%, 100% {
transform: translateZ(-100px) rotateY(-20deg) rotateX(-15deg) rotateZ(10deg);
}
40% {
transform: translateZ(-100px) rotateY(-15deg) rotateX(-20deg) rotateZ(15deg);
}
55% {
transform: translateZ(-100px) rotateY(-14deg) rotateX(-17deg) rotateZ(16deg);
}
70% {
transform: translateZ(-100px) rotateY(-22deg) rotateX(-22deg) rotateZ(8deg);
}
}
/* 调整 move-1 与 move-2 保持原效果 */
@keyframes move-1 {
0%, 100% {
transform: translate(-60%, -70%);
}
25% {
transform: translate(-30%, -60%);
}
50% {
transform: translate(-20%, -70%);
}
75% {
transform: translate(-30%, -40%);
}
}
@keyframes move-2 {
0%, 100% {
transform: translate(-50%, -50%);
}
25% {
transform: translate(-40%, -50%);
}
50% {
transform: translate(-50%, -60%);
}
75% {
transform: translate(-50%, -40%);
}
}
/* 修改 move-3,降低第三个盒子动画时的偏移,确保其始终在容器内可见 */
@keyframes move-3 {
0%, 100% {
transform: translate(-5%, -30%);
}
25% {
transform: translate(0, -25%);
}
50% {
transform: translate(5%, -30%);
}
75% {
transform: translate(0, -35%);
}
}
/* 响应式调整 */
@media screen and (max-width: 1024px) {
.archives-3d-header {
height: 50vh;
}
.boxes {
--boxWidth: 250px;
}
.boxes p {
font-size: 3rem;
}
}
@media screen and (max-width: 768px) {
.archives-3d-header {
height: 40vh;
}
.boxes {
--boxWidth: 200px;
}
.boxes p {
font-size: 2.5rem;
-webkit-text-stroke-width: 1.5px;
}
}
@media screen and (max-width: 480px) {
.archives-3d-header {
height: 30vh;
min-height: 300px;
}
.boxes {
--boxWidth: 150px;
}
.boxes p {
font-size: 2rem;
-webkit-text-stroke-width: 1px;
}
}
/* ========== 3D动画效果样式 结束 ========== */
</style>
<main class="container">
<div class="content-wrap">
<div class="content-layout">
<?php if ($header_style != 1) { echo zib_get_page_header($post_id); } ?>
<div class="theme-box">
<?php if ($header_style == 1) { echo zib_get_page_header($post_id); } ?>
<!-- 3D动画效果 - 开始 -->
<div class="archives-3d-header">
<div class="archives-3d-container">
<div id="square"></div>
<div class="boxes">
<div class="boxes__back boxes__back--1">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
<div class="boxes__scene boxes__scene--1">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--inside">
<p>文章归档</p>
</div>
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
<div class="boxes__back boxes__back--2">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
<div class="boxes__scene boxes__scene--2">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--inside">
<p>博客</p>
</div>
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
<div class="boxes__back boxes__back--3">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
<div class="boxes__scene boxes__scene--3">
<div class="boxes__box">
<div class="boxes__box__face boxes__box__face--inside">
<p>时间轴</p>
</div>
<div class="boxes__box__face boxes__box__face--front"></div>
<div class="boxes__box__face boxes__box__face--back"></div>
<div class="boxes__box__face boxes__box__face--right"></div>
<div class="boxes__box__face boxes__box__face--left"></div>
<div class="boxes__box__face boxes__box__face--top"></div>
<div class="boxes__box__face boxes__box__face--bottom"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 3D动画效果 - 结束 -->
<article>
<!-- 统计概览卡片 -->
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon" style="background-color: rgba(52,152,219,0.1);">
<i class="fa fa-file-text-o" style="color: #3498db; font-size: 22px;"></i>
</div>
<div class="stat-content">
<div class="stat-title">文章总数</div>
<div class="stat-value"><?php echo esc_html($post_count); ?></div>
<span class="stat-note">今日新增 <?php echo esc_html(get_today_new_posts()); ?></span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background-color: rgba(46,204,113,0.1);">
<i class="fa fa-comments" style="color: #2ecc71; font-size: 22px;"></i>
</div>
<div class="stat-content">
<div class="stat-title">评论总数</div>
<div class="stat-value"><?php echo esc_html($total_comments); ?></div>
<span class="stat-note">今日新增 <?php echo esc_html(get_today_new_comments()); ?></span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background-color: rgba(241,196,15,0.1);">
<i class="fa fa-users" style="color: #f1c40f; font-size: 22px;"></i>
</div>
<div class="stat-content">
<div class="stat-title">用户总数</div>
<div class="stat-value"><?php echo esc_html($user_count); ?></div>
<span class="stat-note">今日新增 <?php echo esc_html(get_today_new_users()); ?></span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background-color: rgba(231,76,60,0.1);">
<i class="fa fa-eye" style="color: #e74c3c; font-size: 22px;"></i>
</div>
<div class="stat-content">
<div class="stat-title">访问总数</div>
<div class="stat-value"><?php echo esc_html(get_total_visit_count()); ?></div>
</div>
</div>
</div>
<!-- 图表仪表盘 -->
<div class="dashboard-grid">
<div class="chart-card">
<div class="chart-card-header">
<h3 class="chart-title">文章分类统计</h3>
</div>
<div class="chart-container">
<canvas id="category-chart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-card-header">
<h3 class="chart-title">用户类型分布</h3>
</div>
<div class="chart-container">
<canvas id="user-chart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-card-header">
<h3 class="chart-title">月度文章发布量</h3>
</div>
<div class="chart-container">
<canvas id="monthly-chart"></canvas>
</div>
</div>
<div class="chart-card">
<div class="chart-card-header">
<h3 class="chart-title">文章发布热力图(<?php echo date('Y'); ?>)</h3>
</div>
<div class="chart-container">
<canvas id="heatmap-chart"></canvas>
</div>
</div>
</div>
<!-- 归档列表 -->
<div class="archive-section">
<div class="archive-title">
<span>文章归档</span>
<button class="expand-all-button" id="expand-all-btn">展开全部</button>
</div>
<div id="archives-container">
<?php
if (!empty($posts_by_year)):
foreach ($posts_by_year as $year => $year_data):
$months = $year_data['months'];
?>
<div class="archive-year" data-year="<?php echo $year; ?>">
<div class="year-header" onclick="toggleYear(<?php echo $year; ?>)">
<span class="year-title"><?php echo $year; ?>年</span>
<span class="year-count"><?php echo $year_data['count']; ?>篇</span>
</div>
<div class="year-content" id="year-<?php echo $year; ?>" style="display: none;">
<?php foreach ($months as $month => $month_data): ?>
<div class="month-section">
<div class="month-header" onclick="toggleMonth('<?php echo $year.'-'.$month; ?>')">
<?php echo $month_data['name']; ?> (<?php echo count($month_data['posts']); ?>篇)
<i class="fa fa-angle-down toggle-icon"></i>
</div>
<div class="post-list" id="month-<?php echo $year.'-'.$month; ?>">
<?php foreach ($month_data['posts'] as $post): ?>
<div class="post-item">
<div class="post-date"><?php echo date('m-d', strtotime($post['date'])); ?></div>
<div class="post-title">
<a href="<?php echo esc_url($post['permalink']); ?>" title="<?php echo esc_attr($post['title']); ?>">
<?php echo esc_html($post['title']); ?>
</a>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
endforeach;
endif;
?>
</div>
</div>
</article>
</div>
<?php comments_template('/template/comments.php', true); ?>
</div>
</div>
</main>
<!-- 归档列表交互及主题切换监听 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 归档交互
window.toggleYear = function(year) {
const yearContent = document.getElementById('year-' + year);
yearContent.style.display = (yearContent.style.display === 'none' || yearContent.style.display === '') ? 'block' : 'none';
};
window.toggleMonth = function(monthId) {
const monthContent = document.getElementById('month-' + monthId);
const header = monthContent.previousElementSibling;
const icon = header.querySelector('.toggle-icon');
if (monthContent.style.display === 'block') {
monthContent.style.display = 'none';
icon.classList.remove('open');
} else {
monthContent.style.display = 'block';
icon.classList.add('open');
}
};
// 展开/收起所有归档
document.getElementById('expand-all-btn').addEventListener('click', function() {
const yearContents = document.querySelectorAll('.year-content');
const postLists = document.querySelectorAll('.post-list');
const icons = document.querySelectorAll('.toggle-icon');
const btn = this;
if (btn.getAttribute('data-expanded') === 'true') {
yearContents.forEach(el => el.style.display = 'none');
postLists.forEach(el => el.style.display = 'none');
icons.forEach(icon => icon.classList.remove('open'));
btn.textContent = '展开全部';
btn.setAttribute('data-expanded', 'false');
} else {
yearContents.forEach(el => el.style.display = 'block');
postLists.forEach(el => el.style.display = 'block');
icons.forEach(icon => icon.classList.add('open'));
btn.textContent = '收起全部';
btn.setAttribute('data-expanded', 'true');
}
});
// 防抖函数:延迟执行,避免频繁调用
function debounce(fn, delay) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(fn, delay);
}
}
// 防抖后的图表初始化
const debouncedInitCharts = debounce(function() {
if (typeof initCharts === 'function') {
initCharts();
}
}, 200);
// 当前主题状态,仅在深色/浅色切换时触发图表更新
let currentTheme = document.body.classList.contains('dark') ? 'dark' : 'light';
// 3D动画效果鼠标跟踪 - 开始
const square = document.getElementById('square');
if (square) {
document.addEventListener('mousemove', function(e) {
square.style.left = e.clientX + 'px';
square.style.top = e.clientY + 'px';
});
}
// 3D动画效果鼠标跟踪 - 结束
});
</script>
<!-- 图表初始化与配置 -->
<script type="text/javascript">
function initCharts() {
const isDark = document.body.classList.contains('dark');
const textColor = isDark ? '#e0e0e0' : '#333333';
const gridColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.07)';
// 文章分类统计饼图
const categoryCtx = document.getElementById('category-chart').getContext('2d');
if (window.categoryChart) window.categoryChart.destroy();
window.categoryChart = new Chart(categoryCtx, {
type: 'doughnut',
data: {
labels: archivesData.categoryData.map(item => item.name),
datasets: [{
data: archivesData.categoryData.map(item => item.value),
backgroundColor: [
'#3498db', '#2ecc71', '#f1c40f', '#e74c3c', '#9b59b6',
'#1abc9c', '#e67e22', '#95a5a6', '#34495e', '#16a085',
'#2980b9', '#27ae60', '#f39c12', '#c0392b', '#8e44ad'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: {
color: textColor,
font: { size: 12 }
}
},
tooltip: {
backgroundColor: isDark ? '#333' : '#fff',
titleColor: isDark ? '#fff' : '#333',
bodyColor: isDark ? '#fff' : '#333'
}
}
}
});
// 用户类型分布饼图
const userCtx = document.getElementById('user-chart').getContext('2d');
if (window.userChart) window.userChart.destroy();
window.userChart = new Chart(userCtx, {
type: 'doughnut',
data: {
labels: archivesData.userVipData.map(item => item.name),
datasets: [{
data: archivesData.userVipData.map(item => item.value),
backgroundColor: ['#3498db', '#2ecc71', '#f1c40f']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: {
color: textColor,
font: { size: 12 }
}
},
tooltip: {
backgroundColor: isDark ? '#333' : '#fff',
titleColor: isDark ? '#fff' : '#333',
bodyColor: isDark ? '#fff' : '#333'
}
}
}
});
// 月度文章发布量折线图
const monthlyCtx = document.getElementById('monthly-chart').getContext('2d');
if (window.monthlyChart) window.monthlyChart.destroy();
window.monthlyChart = new Chart(monthlyCtx, {
type: 'line',
data: {
labels: archivesData.monthlyData.months,
datasets: [{
label: '文章发布量',
data: archivesData.monthlyData.counts,
borderColor: '#3498db',
backgroundColor: 'rgba(52,152,219,0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
color: textColor,
autoSkip: true,
maxTicksLimit: 12,
callback: function(value, index, values) {
return index % 2 === 0 ? this.getLabelForValue(value) : '';
},
minRotation: 45,
maxRotation: 45
},
grid: { color: gridColor }
},
y: {
ticks: { color: textColor },
grid: { color: gridColor }
}
},
plugins: {
legend: { labels: { color: textColor } },
tooltip: {
backgroundColor: isDark ? '#333' : '#fff',
titleColor: isDark ? '#fff' : '#333',
bodyColor: isDark ? '#fff' : '#333'
}
}
}
});
// 文章发布热力图柱状图
const heatmapLabels = archivesData.heatmapData.map(item => item[0]);
const heatmapCounts = archivesData.heatmapData.map(item => item[1]);
const heatmapCtx = document.getElementById('heatmap-chart').getContext('2d');
if (window.heatmapChart) window.heatmapChart.destroy();
window.heatmapChart = new Chart(heatmapCtx, {
type: 'bar',
data: {
labels: heatmapLabels,
datasets: [{
label: '每日文章数',
data: heatmapCounts,
backgroundColor: '#2ecc71'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
ticks: {
color: textColor,
autoSkip: true,
maxTicksLimit: 10,
minRotation: 45,
maxRotation: 45,
callback: function(value, index, values) {
return index % 2 === 0 ? this.getLabelForValue(value) : '';
}
},
grid: { color: gridColor }
},
y: {
ticks: { color: textColor },
grid: { color: gridColor }
}
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: isDark ? '#333' : '#fff',
titleColor: isDark ? '#fff' : '#333',
bodyColor: isDark ? '#fff' : '#333'
}
}
}
});
}
document.addEventListener('DOMContentLoaded', function() {
initCharts();
});
</script>
<?php
get_footer();
?>