前言
相比 51la 等统计工具,Umami更轻量、隐私友好,并且支持自托管,非常适合个人博客使用。
美化之前
在进行博客美化时,通常需要修改网站源文件并添加样式或JavaScript,因此需要一定的基础知识,可以参考:
引用站外地址
Hexo博客添加自定义css和js文件
Leonus
在添加完js、css后,一定要记得在**_config.butterfly.yml的inject**里引用
引用站外地址
Butterfly自用全局变量
June'sBlog
参考链接
引用站外地址
在 Vercel 上部署 Umami 轻松实现网站流量统计
Heyjude'sBlog
引用站外地址
使用Docker搭建Umami统计,显示近一年的pv、uv数据的API搭建
张洪Heo
部署Umami
首先访问 Umami GitHub仓库,fork仓库后
打开Vercel,点击Add New Project,import刚刚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
引用站外地址
在线HTTP接口测试 - HTTP GET/POST模拟请求测试工具
JSON模拟请求
输入: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();
|
Butterfly统计Umami及Vercel部署