mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 01:40:03 -04:00
friends(new): 雪诺的小博客
2. feat(analytics): 加入 Google Analytics 3. fix(style): 修复 Umami 请求重复多次请求的问题
This commit is contained in:
@@ -24,7 +24,7 @@ let links: NavBarLink[] = navBarConfig.links.map(
|
|||||||
<div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation -->
|
<div class="absolute h-8 left-0 right-0 -top-8 bg-[var(--card-bg)] transition"></div> <!-- used for onload animation -->
|
||||||
<div class:list={[
|
<div class:list={[
|
||||||
className,
|
className,
|
||||||
"card-base !overflow-visible max-w-[var(--page-width)] h-[4.5rem] !rounded-t-none mx-auto flex items-center justify-between px-4 !bg-[var(--card-bg-transparent)] backdrop-blur-md"]}>
|
"card-base !overflow-visible max-w-[var(--page-width)] h-[4.5rem] !rounded-t-none mx-auto flex items-center justify-between px-4 !bg-[var(--card-bg-transparent)] backdrop-blur-lg"]}>
|
||||||
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
|
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
|
||||||
<div class="flex flex-row text-[var(--primary)] items-center text-md">
|
<div class="flex flex-row text-[var(--primary)] items-center text-md">
|
||||||
<Icon name="material-symbols:home-outline-rounded" class="text-[1.75rem] mb-1 mr-2" />
|
<Icon name="material-symbols:home-outline-rounded" class="text-[1.75rem] mb-1 mr-2" />
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { i18n } from "../i18n/translation";
|
|||||||
import { getDir } from "../utils/url-utils";
|
import { getDir } from "../utils/url-utils";
|
||||||
import ImageWrapper from "./misc/ImageWrapper.astro";
|
import ImageWrapper from "./misc/ImageWrapper.astro";
|
||||||
import PostMetadata from "./PostMeta.astro";
|
import PostMetadata from "./PostMeta.astro";
|
||||||
import { umamiConfig } from "../config";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -43,7 +42,7 @@ const coverWidth = "28%";
|
|||||||
|
|
||||||
const { remarkPluginFrontmatter } = await entry.render();
|
const { remarkPluginFrontmatter } = await entry.render();
|
||||||
---
|
---
|
||||||
<div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative hover:scale-[1.02] hover:shadow-xl transition-all duration-[300ms]", className]} style={style}>
|
<div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative border border-black/20 dark:border-white/20 hover:scale-[1.02] hover:shadow-xl transition-all duration-[300ms]", className]} style={style}>
|
||||||
<div class:list={["pl-6 md:pl-9 pr-6 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}>
|
<div class:list={["pl-6 md:pl-9 pr-6 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}>
|
||||||
<a href={url}
|
<a href={url}
|
||||||
class="transition group w-full block font-bold mb-3 text-3xl text-90
|
class="transition group w-full block font-bold mb-3 text-3xl text-90
|
||||||
@@ -111,56 +110,39 @@ const { remarkPluginFrontmatter } = await entry.render();
|
|||||||
|
|
||||||
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostCard.astro -->
|
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostCard.astro -->
|
||||||
|
|
||||||
<script define:vars={{ entry, umamiConfig }}>
|
<script define:vars={{ slug: entry.slug }}>
|
||||||
// 获取文章浏览量统计
|
// 获取文章浏览量统计
|
||||||
async function fetchPostCardViews(slug) {
|
async function fetchPostCardViews(slug) {
|
||||||
if (!umamiConfig.enable) {
|
const displayElement = document.getElementById(`page-views-${slug}`);
|
||||||
|
if (!displayElement || displayElement.dataset.umamiState === 'loading' || displayElement.dataset.umamiState === 'loaded') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayElement.dataset.umamiState = 'loading';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 第一步:获取网站ID和token
|
const umamiStore = window['__blogUmami'];
|
||||||
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
const statsData = await umamiStore?.getStats(`post:${slug}`, (websiteId) => {
|
||||||
if (!shareResponse.ok) {
|
|
||||||
throw new Error('获取分享信息失败');
|
|
||||||
}
|
|
||||||
const shareData = await shareResponse.json();
|
|
||||||
const { websiteId, token } = shareData;
|
|
||||||
|
|
||||||
// 第二步:获取统计数据
|
|
||||||
const currentTimestamp = Date.now();
|
const currentTimestamp = Date.now();
|
||||||
const statsUrl = `${umamiConfig.baseUrl}/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent(umamiConfig.timezone)}&url=%2Fposts%2F${slug}%2F&compare=false`;
|
return `https://umami.adclosenn.top/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent('Asia/Shanghai')}&url=%2Fposts%2F${slug}%2F&compare=false`;
|
||||||
|
|
||||||
const statsResponse = await fetch(statsUrl, {
|
|
||||||
headers: {
|
|
||||||
'x-umami-share-token': token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!statsResponse.ok) {
|
if (!statsData) {
|
||||||
throw new Error('获取统计数据失败');
|
throw new Error('统计功能未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsData = await statsResponse.json();
|
|
||||||
const pageViews = statsData.pageviews?.value || 0;
|
const pageViews = statsData.pageviews?.value || 0;
|
||||||
// const visits = statsData.visits?.value || 0;
|
|
||||||
|
|
||||||
const displayElement = document.getElementById(`page-views-${slug}`);
|
|
||||||
if (displayElement) {
|
|
||||||
displayElement.textContent = `浏览量 ${pageViews}`;
|
displayElement.textContent = `浏览量 ${pageViews}`;
|
||||||
}
|
displayElement.dataset.umamiState = 'loaded';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching page views for', slug, ':', error);
|
console.error('Error fetching page views for', slug, ':', error);
|
||||||
const displayElement = document.getElementById(`page-views-${slug}`);
|
|
||||||
if (displayElement) {
|
|
||||||
displayElement.textContent = '统计不可用';
|
displayElement.textContent = '统计不可用';
|
||||||
}
|
displayElement.dataset.umamiState = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载完成后获取统计数据
|
// 页面加载完成后获取统计数据
|
||||||
function initPostCardStats() {
|
function initPostCardStats() {
|
||||||
const slug = entry.slug;
|
|
||||||
if (slug) {
|
if (slug) {
|
||||||
fetchPostCardViews(slug);
|
fetchPostCardViews(slug);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import I18nKey from "../i18n/i18nKey";
|
|||||||
import { i18n } from "../i18n/translation";
|
import { i18n } from "../i18n/translation";
|
||||||
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
import { formatDateToYYYYMMDD } from "../utils/date-utils";
|
||||||
import { getCategoryUrl, getTagUrl, getDir, url } from "../utils/url-utils";
|
import { getCategoryUrl, getTagUrl, getDir, url } from "../utils/url-utils";
|
||||||
import { umamiConfig } from "../config";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
class: string;
|
class: string;
|
||||||
@@ -98,50 +97,36 @@ const className = Astro.props.class;
|
|||||||
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostMeta.astro -->
|
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostMeta.astro -->
|
||||||
|
|
||||||
{slug && (
|
{slug && (
|
||||||
<script define:vars={{ slug, umamiConfig }}>
|
<script define:vars={{ slug }}>
|
||||||
// 获取访问量统计
|
// 获取访问量统计
|
||||||
async function fetchPageViews() {
|
async function fetchPageViews() {
|
||||||
if (!umamiConfig.enable) {
|
const displayElement = document.getElementById('page-views-display');
|
||||||
|
if (!displayElement || displayElement.dataset.umamiState === 'loading' || displayElement.dataset.umamiState === 'loaded') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
displayElement.dataset.umamiState = 'loading';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 第一步:获取网站ID和token
|
const umamiStore = window['__blogUmami'];
|
||||||
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
const statsData = await umamiStore?.getStats(`post:${slug}`, (websiteId) => {
|
||||||
if (!shareResponse.ok) {
|
|
||||||
throw new Error('获取分享信息失败');
|
|
||||||
}
|
|
||||||
const shareData = await shareResponse.json();
|
|
||||||
const { websiteId, token } = shareData;
|
|
||||||
|
|
||||||
// 第二步:获取统计数据
|
|
||||||
const currentTimestamp = Date.now();
|
const currentTimestamp = Date.now();
|
||||||
const statsUrl = `${umamiConfig.baseUrl}/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent(umamiConfig.timezone)}&url=%2Fposts%2F${slug}%2F&compare=false`;
|
return `https://umami.adclosenn.top/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent('Asia/Shanghai')}&url=%2Fposts%2F${slug}%2F&compare=false`;
|
||||||
|
|
||||||
const statsResponse = await fetch(statsUrl, {
|
|
||||||
headers: {
|
|
||||||
'x-umami-share-token': token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!statsResponse.ok) {
|
if (!statsData) {
|
||||||
throw new Error('获取统计数据失败');
|
throw new Error('统计功能未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsData = await statsResponse.json();
|
|
||||||
const pageViews = statsData.pageviews?.value || 0;
|
const pageViews = statsData.pageviews?.value || 0;
|
||||||
const visits = statsData.visits?.value || 0;
|
const visits = statsData.visits?.value || 0;
|
||||||
|
|
||||||
const displayElement = document.getElementById('page-views-display');
|
|
||||||
if (displayElement) {
|
|
||||||
displayElement.textContent = `浏览量 ${pageViews} · 访问数 ${visits}`;
|
displayElement.textContent = `浏览量 ${pageViews} · 访问数 ${visits}`;
|
||||||
}
|
displayElement.dataset.umamiState = 'loaded';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching page views:', error);
|
console.error('Error fetching page views:', error);
|
||||||
const displayElement = document.getElementById('page-views-display');
|
|
||||||
if (displayElement) {
|
|
||||||
displayElement.textContent = '统计不可用';
|
displayElement.textContent = '统计不可用';
|
||||||
}
|
displayElement.dataset.umamiState = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { profileConfig, umamiConfig } from "../../config";
|
import { profileConfig } from "../../config";
|
||||||
import { url } from "../../utils/url-utils";
|
import { url } from "../../utils/url-utils";
|
||||||
import ImageWrapper from "../misc/ImageWrapper.astro";
|
import ImageWrapper from "../misc/ImageWrapper.astro";
|
||||||
|
|
||||||
@@ -85,55 +85,43 @@ fetch("https://v1.hitokoto.cn")
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script define:vars={{ umamiConfig }}>
|
<script>
|
||||||
// 获取全站访问量统计
|
// 获取全站访问量统计
|
||||||
async function loadSiteStats() {
|
async function loadSiteStats() {
|
||||||
if (!umamiConfig.enable) {
|
const statsElement = document.getElementById('site-stats');
|
||||||
|
if (!statsElement || statsElement.dataset.umamiState === 'loading' || statsElement.dataset.umamiState === 'loaded') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statsElement.dataset.umamiState = 'loading';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 第一步:获取网站ID和token
|
const umamiStore = window['__blogUmami'];
|
||||||
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
const statsData = await umamiStore?.getStats('site:all', (websiteId) => {
|
||||||
if (!shareResponse.ok) {
|
|
||||||
throw new Error('获取分享信息失败');
|
|
||||||
}
|
|
||||||
const shareData = await shareResponse.json();
|
|
||||||
const { websiteId, token } = shareData;
|
|
||||||
|
|
||||||
// 第二步:获取全站统计数据(不指定url参数获取全站数据)
|
|
||||||
const currentTimestamp = Date.now();
|
const currentTimestamp = Date.now();
|
||||||
const statsUrl = `${umamiConfig.baseUrl}/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent(umamiConfig.timezone)}&compare=false`;
|
return `https://umami.adclosenn.top/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent('Asia/Shanghai')}&compare=false`;
|
||||||
|
|
||||||
const statsResponse = await fetch(statsUrl, {
|
|
||||||
headers: {
|
|
||||||
'x-umami-share-token': token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!statsResponse.ok) {
|
if (!statsData) {
|
||||||
throw new Error('获取统计数据失败');
|
throw new Error('统计功能未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
const statsData = await statsResponse.json();
|
|
||||||
const pageviews = statsData.pageviews?.value || 0;
|
const pageviews = statsData.pageviews?.value || 0;
|
||||||
// const visitors = statsData.visits?.value || 0;
|
|
||||||
|
|
||||||
const statsElement = document.getElementById('site-stats');
|
|
||||||
if (statsElement) {
|
|
||||||
statsElement.textContent = `浏览量 ${pageviews}`;
|
statsElement.textContent = `浏览量 ${pageviews}`;
|
||||||
}
|
statsElement.dataset.umamiState = 'loaded';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取全站统计失败:', error);
|
console.error('获取全站统计失败:', error);
|
||||||
const statsElement = document.getElementById('site-stats');
|
|
||||||
if (statsElement) {
|
|
||||||
statsElement.textContent = '统计不可用';
|
statsElement.textContent = '统计不可用';
|
||||||
}
|
statsElement.dataset.umamiState = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载完成后获取统计数据
|
// 页面加载完成后获取统计数据
|
||||||
document.addEventListener('DOMContentLoaded', loadSiteStats);
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', loadSiteStats, { once: true });
|
||||||
|
} else {
|
||||||
|
loadSiteStats();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<!-- 获取 Commit 信息 via API -->
|
<!-- 获取 Commit 信息 via API -->
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
import ConfigCarrier from "@components/ConfigCarrier.astro";
|
||||||
import { profileConfig, siteConfig } from "@/config";
|
import { profileConfig, siteConfig, umamiConfig } from "@/config";
|
||||||
import {
|
import {
|
||||||
AUTO_MODE,
|
AUTO_MODE,
|
||||||
BANNER_HEIGHT,
|
BANNER_HEIGHT,
|
||||||
@@ -195,9 +195,78 @@ const bannerOffset =
|
|||||||
|
|
||||||
<slot name="head"></slot>
|
<slot name="head"></slot>
|
||||||
|
|
||||||
|
<script is:inline define:vars={{ umamiConfig }} data-swup-ignore-script>
|
||||||
|
window['__blogUmami'] = window['__blogUmami'] || {
|
||||||
|
shareDataPromise: null,
|
||||||
|
statsPromiseCache: new Map(),
|
||||||
|
statsValueCache: new Map(),
|
||||||
|
async getShareData() {
|
||||||
|
if (!umamiConfig.enable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.shareDataPromise) {
|
||||||
|
return this.shareDataPromise;
|
||||||
|
}
|
||||||
|
this.shareDataPromise = fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`)
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('获取分享信息失败');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.shareDataPromise = null;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
return this.shareDataPromise;
|
||||||
|
},
|
||||||
|
async getStats(cacheKey, buildStatsUrl) {
|
||||||
|
if (this.statsValueCache.has(cacheKey)) {
|
||||||
|
return this.statsValueCache.get(cacheKey);
|
||||||
|
}
|
||||||
|
if (this.statsPromiseCache.has(cacheKey)) {
|
||||||
|
return this.statsPromiseCache.get(cacheKey);
|
||||||
|
}
|
||||||
|
const requestPromise = this.getShareData()
|
||||||
|
.then(async (shareData) => {
|
||||||
|
if (!shareData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const statsUrl = buildStatsUrl(shareData.websiteId);
|
||||||
|
const statsResponse = await fetch(statsUrl, {
|
||||||
|
headers: {
|
||||||
|
'x-umami-share-token': shareData.token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!statsResponse.ok) {
|
||||||
|
throw new Error('获取统计数据失败');
|
||||||
|
}
|
||||||
|
const statsData = await statsResponse.json();
|
||||||
|
this.statsValueCache.set(cacheKey, statsData);
|
||||||
|
this.statsPromiseCache.delete(cacheKey);
|
||||||
|
return statsData;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.statsPromiseCache.delete(cacheKey);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
this.statsPromiseCache.set(cacheKey, requestPromise);
|
||||||
|
return requestPromise;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<link rel="alternate" type="application/rss+xml" title={profileConfig.name} href={`${Astro.site}rss.xml`}/>
|
<link rel="alternate" type="application/rss+xml" title={profileConfig.name} href={`${Astro.site}rss.xml`}/>
|
||||||
<!-- Umami分析(自建) -->
|
<!-- Umami分析(自建) -->
|
||||||
<script defer src="https://umami.adclosenn.top/script.js" data-website-id="7610548d-677b-40cb-9ebd-31dc14e16be7"></script>
|
<script defer src="https://umami.adclosenn.top/script.js" data-website-id="7610548d-677b-40cb-9ebd-31dc14e16be7" data-swup-ignore-script></script>
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-YRCGFG45C1"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'G-YRCGFG45C1');
|
||||||
|
</script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body class=" min-h-screen transition " class:list={[{"lg:is-home": isHomePage, "enable-banner": enableBanner}]}
|
<body class=" min-h-screen transition " class:list={[{"lg:is-home": isHomePage, "enable-banner": enableBanner}]}
|
||||||
|
|||||||
@@ -226,6 +226,14 @@ import { Icon } from "astro-icon/components";
|
|||||||
<div class="text-sm text-black/50 dark:text-white/50">艾拉酱世界第一可爱!</div>
|
<div class="text-sm text-black/50 dark:text-white/50">艾拉酱世界第一可爱!</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="https://blog.4365754.xyz" target="_blank" class="friend-card">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img src="https://photos.4365754.xyz/ac0cf34feb992487db7e63382418382dba213210.jpg" loading="lazy" class="w-5 h-5 rounded">
|
||||||
|
<div class="font-bold text-black dark:text-white">雪诺的小博客 </div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-black/50 dark:text-white/50">分享关于网络的众多有趣的小知识</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 申请友链 -->
|
<!-- 申请友链 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user