feat: 添加 Umami 分析支持,更新配置文档

This commit is contained in:
2025-12-07 14:27:42 +08:00
parent d7b419f401
commit 361999a8b1
9 changed files with 118 additions and 70 deletions

View File

@@ -3,6 +3,10 @@
<p class="muted" v-if="showHitokoto && quote">
{{ quote }}<span v-if="from" class="from"> {{ from }}</span>
</p>
<p class="muted stats" v-if="showStats && !statsError">
👁 {{ visitors }} visitors · 📊 {{ pageviews }} pageviews
</p>
<!-- <p class="muted stats" v-if="showStats && statsError">🔒 由于启用了隐私保护拓展禁用状态统计</p> -->
<p class="muted beian" v-if="contact.beian">
<a :href="contact.beianLink || 'https://beian.miit.gov.cn/'" target="_blank" rel="noreferrer">
{{ contact.beian }}
@@ -14,14 +18,19 @@
<script setup>
import { onMounted, ref } from "vue";
import siteConfig from "../config/siteConfig";
const props = defineProps({ contact: Object });
const quote = ref("");
const from = ref("");
const showHitokoto = props.contact?.showHitokoto !== false;
const pageviews = ref(0);
const visitors = ref(0);
const statsError = ref(true);
const showHitokoto = siteConfig.footer?.hitokoto?.enable;
const showStats = ref(siteConfig.umami?.enable);
const buildHitokotoUrl = () => {
const type = siteConfig.footer?.hitokoto?.type;
const url = new URL("https://v1.hitokoto.cn/");
const type = props.contact?.hitokotoType;
if (Array.isArray(type)) {
type.filter(Boolean).forEach((t) => url.searchParams.append("c", t));
} else if (typeof type === "string") {
@@ -44,8 +53,52 @@ const fetchHitokoto = async () => {
}
};
const fetchStats = async () => {
try {
if (!siteConfig.umami?.apiBase || !siteConfig.umami?.websiteId) {
return;
}
const apiBase = siteConfig.umami.apiBase;
const websiteId = siteConfig.umami.websiteId;
const apiKey = import.meta.env.VITE_UMAMI_API_KEY;
if (!apiKey) return;
// 获取统计数据
const endAt = Date.now();
const startAt = new Date(siteConfig.siteMeta.startDate).getTime();
const resp = await fetch(`${apiBase}/v1/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}`, {
headers: {
Authorization: `Bearer ${apiKey}`,
},
});
if (!resp.ok) {
console.warn(`Stats API returned ${resp.status}`);
statsError.value = true;
return;
}
const data = await resp.json();
if (data) {
statsError.value = false;
pageviews.value = data.pageviews;
visitors.value = data.visitors;
}
if (pageviews.value === 0 && visitors.value === 0) {
showStats.value = false;
}
} catch (e) {
statsError.value = true;
console.debug("Stats fetch failed (this is normal if blocked by ad blocker):", e.message);
}
};
onMounted(() => {
if (showHitokoto) fetchHitokoto();
if (showStats.value) fetchStats();
});
</script>
@@ -54,14 +107,17 @@ onMounted(() => {
text-align: center;
margin-top: 8px;
}
.from {
margin-left: 6px;
}
.beian {
font-size: 12px;
margin: 6px 0;
letter-spacing: 0.2px;
}
.beian a {
color: inherit;
opacity: 0.85;
@@ -69,12 +125,20 @@ onMounted(() => {
border-radius: 8px;
transition: color 0.2s ease, background 0.2s ease, opacity 0.2s ease;
}
.beian a:hover {
color: var(--accent, #7cc1ff);
background: rgba(124, 193, 255, 0.1);
opacity: 1;
}
.custom-html {
margin-top: 6px;
}
.stats {
font-size: 12px;
margin: 6px 0;
letter-spacing: 0.2px;
}
</style>