前言

相比 51la 等统计工具,Umami更轻量、隐私友好,并且支持自托管,非常适合个人博客使用。

美化之前

在进行博客美化时,通常需要修改网站源文件并添加样式或JavaScript,因此需要一定的基础知识,可以参考:

千万别忘了

在添加完js、css后,一定要记得在**_config.butterfly.ymlinject**里引用

参考链接

部署Umami

首先访问 Umami GitHub仓库,fork仓库后

打开Vercel,点击Add New Projectimport刚刚fork的仓库,修改项目名称后点击Deploy。第一次部署通常会失败,因为此时还没有配置数据库,这是正常现象。

回到Vercel仪表盘,进入刚刚创建的Umami项目,点击Storage ->Create Database -> Neon

配置你的数据库的地区,我习惯用新加坡的

然后就是配置Resource Name,创建好后可以直接管理项目

回到项目,Setting ->Environment Variables,确认环境变量已添加,然后回到Deployments,点击刚刚失败的项目,重新部署Redeploy

部署完后点击分配给你的域名(也可以去Damains配置你自己的域名)进入后台,第一次登陆的时候,默认为admin/umami,登陆完记得改密码

配置Umami

添加需要统计的网站

复制下面的script,添加到_config.butterfly.yml ->inject,可以添加下面的data-domains,避免本地访问产生统计脏数据

1
<script defer src="https://XXX/script.js" data-website-id="XXX" data-domains="你的网站域名"></script>

关于页面改造

在我原先的关于页面基础上修改

获取token

输入:https://你的地址/api/auth/login,获取底下返回的token

1
2
3
4
{
"username": "用户名",
"password": "密码"
}

修改about.pug

由于原先是用51la的,所以要把原先那部分代码改造一下(注:上面的统计信息那块自己改一下就好,这里主要是改底下的script脚本)

修改[blogRoot]\themes\butterfly\layout\includes\page\about.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
script.
var initAboutPage = () => {
const aboutPage = document.querySelector('#about-page');
const statisticEl = document.getElementById('statistic');
if (!aboutPage || !statisticEl) return;

// --- 1. 配置信息 ---
const config = {
token: "刚刚获取的token".replace(/[\r\n\s]/g, ""),
id: "你的data-website-id",
api: "https://umami.june-pj.cn/api/websites/你的data-website-id/stats"
};

// --- 2. 静态结构初始化 ---
statisticEl.innerHTML = `
<div><span>今日人数</span><span id="uvToday">0</span></div>
<div><span>今日访问</span><span id="pvToday">0</span></div>
<div><span>昨日人数</span><span id="uvYesterday">0</span></div>
<div><span>昨日访问</span><span id="pvYesterday">0</span></div>
<div><span>本月访问</span><span id="pvMonth">0</span></div>
<div><span>总访问量</span><span id="pvTotal">0</span></div>
`;

// --- 3. 渲染函数 ---
const renderVal = (id, val) => {
const el = document.getElementById(id);
if (!el) return;
const num = val || 0;
if (window.CountUp) {
new CountUp(id, 0, num, 0, 1.5).start();
} else {
el.innerText = num;
}
};

// --- 4. 时间计算 ---
const now = Date.now();
const todayStart = new Date().setHours(0, 0, 0, 0);
const yesterdayStart = todayStart - 86400000;
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime();

// --- 5. 获取数据 ---
const fetchStats = (start, end) =>
fetch(`${config.api}?startAt=${start}&endAt=${end}`, {
headers: {'Authorization': `Bearer ${config.token}`, 'Content-Type': 'application/json'}
}).then(res => res.json());

// 使用 Promise.all 并行请求所有数据
Promise.all([
fetchStats(0, now), // Total
fetchStats(todayStart, now), // Today
fetchStats(yesterdayStart, todayStart - 1), // Yesterday
fetchStats(monthStart, now) // Month
]).then(([total, today, yesterday, month]) => {
renderVal('pvTotal', total.pageviews);
renderVal('uvToday', today.visitors);
renderVal('pvToday', today.pageviews);
renderVal('uvYesterday', yesterday.visitors);
renderVal('pvYesterday', yesterday.pageviews);
renderVal('pvMonth', month.pageviews);
}).catch(err => console.error('Umami Stats Error:', err));

// --- 6. 轮播逻辑优化 ---
if (window.pursuitInterval) clearInterval(window.pursuitInterval);
window.pursuitInterval = setInterval(() => {
const show = document.querySelector('span[data-show]');
if (!show) return;
const next = show.nextElementSibling || document.querySelector('.first-tips');
show.removeAttribute('data-show');
show.setAttribute('data-up', '');
next.setAttribute('data-show', '');
setTimeout(() => {
const up = document.querySelector('span[data-up]');
if (up) up.removeAttribute('data-up');
}, 400); // 延迟清理状态,匹配 CSS 动画时间
}, 2000);
};

// 绑定初始化
const startInit = () => {
if (typeof btf !== 'undefined') {
initAboutPage();
if (btf.addGlobalFn) {
btf.addGlobalFn('pjaxComplete', initAboutPage, 'initAboutPage');
} else {
document.addEventListener('pjax:complete', initAboutPage);
}
} else {
setTimeout(startInit, 100);
}
};

startInit();