mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 01:00:04 -04:00
feat: 博客访问量测试
This commit is contained in:
@@ -11,8 +11,8 @@ import {
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { LIGHT_DARK_MODE } from "@/types/config.ts";
|
import type { LIGHT_DARK_MODE } from "@/types/config.ts";
|
||||||
|
|
||||||
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE, AUTO_MODE];
|
const seq: LIGHT_DARK_MODE[] = [LIGHT_MODE, DARK_MODE];
|
||||||
let mode: LIGHT_DARK_MODE = $state(AUTO_MODE);
|
let mode: LIGHT_DARK_MODE = LIGHT_MODE;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
mode = getStoredTheme();
|
mode = getStoredTheme();
|
||||||
@@ -66,9 +66,6 @@ function hidePanel() {
|
|||||||
<div class="absolute" class:opacity-0={mode !== DARK_MODE}>
|
<div class="absolute" class:opacity-0={mode !== DARK_MODE}>
|
||||||
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem]"></Icon>
|
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem]"></Icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute" class:opacity-0={mode !== AUTO_MODE}>
|
|
||||||
<Icon icon="material-symbols:radio-button-partial-outline" class="text-[1.25rem]"></Icon>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div id="light-dark-panel" class="hidden lg:block absolute transition float-panel-closed top-11 -right-2 pt-5" >
|
<div id="light-dark-panel" class="hidden lg:block absolute transition float-panel-closed top-11 -right-2 pt-5" >
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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;
|
||||||
@@ -64,14 +65,15 @@ const { remarkPluginFrontmatter } = await entry.render();
|
|||||||
{ description || remarkPluginFrontmatter.excerpt }
|
{ description || remarkPluginFrontmatter.excerpt }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- word count and read time -->
|
|
||||||
|
<!-- word count, read time and page views https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostCard.astro -->
|
||||||
<div class="text-sm text-black/30 dark:text-white/30 flex gap-4 transition">
|
<div class="text-sm text-black/30 dark:text-white/30 flex gap-4 transition">
|
||||||
<div>
|
<div>{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
|
||||||
{remarkPluginFrontmatter.words} {" " + i18n(remarkPluginFrontmatter.words === 1 ? I18nKey.wordCount : I18nKey.wordsCount)}
|
<div>|</div>
|
||||||
</div>
|
<div>{remarkPluginFrontmatter.minutes} {" " + i18n(I18nKey.minutesCount)}</div>
|
||||||
<div>|</div>
|
<div>|</div>
|
||||||
<div>
|
<div>
|
||||||
{remarkPluginFrontmatter.minutes} {" " + i18n(remarkPluginFrontmatter.minutes === 1 ? I18nKey.minuteCount : I18nKey.minutesCount)}
|
<span class="text-50 text-sm font-medium" id={`page-views-${entry.slug}`}>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -106,5 +108,72 @@ const { remarkPluginFrontmatter } = await entry.render();
|
|||||||
</div>
|
</div>
|
||||||
<div class="transition border-t-[1px] border-dashed mx-6 border-black/10 dark:border-white/[0.15] last:border-t-0 md:hidden"></div>
|
<div class="transition border-t-[1px] border-dashed mx-6 border-black/10 dark:border-white/[0.15] last:border-t-0 md:hidden"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostCard.astro -->
|
||||||
|
|
||||||
|
<script define:vars={{ entry, umamiConfig }}>
|
||||||
|
// 获取文章浏览量统计
|
||||||
|
async function fetchPostCardViews(slug) {
|
||||||
|
if (!umamiConfig.enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 第一步:获取网站ID和token
|
||||||
|
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
||||||
|
if (!shareResponse.ok) {
|
||||||
|
throw new Error('获取分享信息失败');
|
||||||
|
}
|
||||||
|
const shareData = await shareResponse.json();
|
||||||
|
const { websiteId, token } = shareData;
|
||||||
|
|
||||||
|
// 第二步:获取统计数据
|
||||||
|
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`;
|
||||||
|
|
||||||
|
const statsResponse = await fetch(statsUrl, {
|
||||||
|
headers: {
|
||||||
|
'x-umami-share-token': token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!statsResponse.ok) {
|
||||||
|
throw new Error('获取统计数据失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsData = await statsResponse.json();
|
||||||
|
const pageViews = statsData.pageviews?.value || 0;
|
||||||
|
// const visits = statsData.visits?.value || 0;
|
||||||
|
|
||||||
|
const displayElement = document.getElementById(`page-views-${slug}`);
|
||||||
|
if (displayElement) {
|
||||||
|
displayElement.textContent = `浏览量 ${pageViews}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching page views for', slug, ':', error);
|
||||||
|
const displayElement = document.getElementById(`page-views-${slug}`);
|
||||||
|
if (displayElement) {
|
||||||
|
displayElement.textContent = '统计不可用';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后获取统计数据
|
||||||
|
function initPostCardStats() {
|
||||||
|
const slug = entry.slug;
|
||||||
|
if (slug) {
|
||||||
|
fetchPostCardViews(slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initPostCardStats);
|
||||||
|
} else {
|
||||||
|
initPostCardStats();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style define:vars={{coverWidth}}>
|
<style define:vars={{coverWidth}}>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
---
|
---
|
||||||
|
import path from "node:path";
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import I18nKey from "../i18n/i18nKey";
|
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 } 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;
|
||||||
@@ -13,6 +15,7 @@ interface Props {
|
|||||||
category: string | null;
|
category: string | null;
|
||||||
hideTagsForMobile?: boolean;
|
hideTagsForMobile?: boolean;
|
||||||
hideUpdateDate?: boolean;
|
hideUpdateDate?: boolean;
|
||||||
|
slug?: string;
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
published,
|
published,
|
||||||
@@ -21,6 +24,7 @@ const {
|
|||||||
category,
|
category,
|
||||||
hideTagsForMobile = false,
|
hideTagsForMobile = false,
|
||||||
hideUpdateDate = false,
|
hideUpdateDate = false,
|
||||||
|
slug,
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
const className = Astro.props.class;
|
const className = Astro.props.class;
|
||||||
---
|
---
|
||||||
@@ -35,7 +39,7 @@ const className = Astro.props.class;
|
|||||||
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
|
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- update date -->
|
<!-- update date
|
||||||
{!hideUpdateDate && updated && updated.getTime() !== published.getTime() && (
|
{!hideUpdateDate && updated && updated.getTime() !== published.getTime() && (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="meta-icon"
|
<div class="meta-icon"
|
||||||
@@ -45,6 +49,7 @@ const className = Astro.props.class;
|
|||||||
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(updated)}</span>
|
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(updated)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
-->
|
||||||
|
|
||||||
<!-- categories -->
|
<!-- categories -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -79,4 +84,72 @@ const className = Astro.props.class;
|
|||||||
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
|
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
{slug && (
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="meta-icon">
|
||||||
|
<Icon name="material-symbols:visibility-outline-rounded" class="text-xl"></Icon>
|
||||||
|
</div>
|
||||||
|
<span class="text-50 text-sm font-medium" id="page-views-display">加载中...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/PostMeta.astro -->
|
||||||
|
|
||||||
|
{slug && (
|
||||||
|
<script define:vars={{ slug, umamiConfig }}>
|
||||||
|
// 获取访问量统计
|
||||||
|
async function fetchPageViews() {
|
||||||
|
if (!umamiConfig.enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 第一步:获取网站ID和token
|
||||||
|
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
||||||
|
if (!shareResponse.ok) {
|
||||||
|
throw new Error('获取分享信息失败');
|
||||||
|
}
|
||||||
|
const shareData = await shareResponse.json();
|
||||||
|
const { websiteId, token } = shareData;
|
||||||
|
|
||||||
|
// 第二步:获取统计数据
|
||||||
|
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`;
|
||||||
|
|
||||||
|
const statsResponse = await fetch(statsUrl, {
|
||||||
|
headers: {
|
||||||
|
'x-umami-share-token': token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!statsResponse.ok) {
|
||||||
|
throw new Error('获取统计数据失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsData = await statsResponse.json();
|
||||||
|
const pageViews = statsData.pageviews?.value || 0;
|
||||||
|
const visits = statsData.visits?.value || 0;
|
||||||
|
|
||||||
|
const displayElement = document.getElementById('page-views-display');
|
||||||
|
if (displayElement) {
|
||||||
|
displayElement.textContent = `浏览量 ${pageViews} · 访问数 ${visits}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching page views:', error);
|
||||||
|
const displayElement = document.getElementById('page-views-display');
|
||||||
|
if (displayElement) {
|
||||||
|
displayElement.textContent = '统计不可用';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后获取统计数据
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', fetchPageViews);
|
||||||
|
} else {
|
||||||
|
fetchPageViews();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
)}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { profileConfig } from "../../config";
|
import { profileConfig, umamiConfig } 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";
|
||||||
|
|
||||||
@@ -34,6 +34,65 @@ const config = profileConfig;
|
|||||||
{config.links[0].name}
|
{config.links[0].name}
|
||||||
</a>}
|
</a>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 全站访问量统计 https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/widget/Profile.astro -->
|
||||||
|
<div class="text-center text-sm text-neutral-500 dark:text-neutral-400 mt-3 pt-3 border-t border-neutral-200 dark:border-neutral-700">
|
||||||
|
<div class="flex items-center justify-center gap-1">
|
||||||
|
<Icon name="material-symbols:visibility-outline" class="text-base"></Icon>
|
||||||
|
<span id="site-stats">加载中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script define:vars={{ umamiConfig }}>
|
||||||
|
// 获取全站访问量统计
|
||||||
|
async function loadSiteStats() {
|
||||||
|
if (!umamiConfig.enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 第一步:获取网站ID和token
|
||||||
|
const shareResponse = await fetch(`${umamiConfig.baseUrl}/api/share/${umamiConfig.shareId}`);
|
||||||
|
if (!shareResponse.ok) {
|
||||||
|
throw new Error('获取分享信息失败');
|
||||||
|
}
|
||||||
|
const shareData = await shareResponse.json();
|
||||||
|
const { websiteId, token } = shareData;
|
||||||
|
|
||||||
|
// 第二步:获取全站统计数据(不指定url参数获取全站数据)
|
||||||
|
const currentTimestamp = Date.now();
|
||||||
|
const statsUrl = `${umamiConfig.baseUrl}/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent(umamiConfig.timezone)}&compare=false`;
|
||||||
|
|
||||||
|
const statsResponse = await fetch(statsUrl, {
|
||||||
|
headers: {
|
||||||
|
'x-umami-share-token': token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!statsResponse.ok) {
|
||||||
|
throw new Error('获取统计数据失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const statsData = await statsResponse.json();
|
||||||
|
const pageviews = statsData.pageviews?.value || 0;
|
||||||
|
// const visitors = statsData.visits?.value || 0;
|
||||||
|
|
||||||
|
const statsElement = document.getElementById('site-stats');
|
||||||
|
if (statsElement) {
|
||||||
|
statsElement.textContent = `浏览量 ${pageviews}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取全站统计失败:', error);
|
||||||
|
const statsElement = document.getElementById('site-stats');
|
||||||
|
if (statsElement) {
|
||||||
|
statsElement.textContent = '统计不可用';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载完成后获取统计数据
|
||||||
|
document.addEventListener('DOMContentLoaded', loadSiteStats);
|
||||||
|
</script>
|
||||||
+9
-7
@@ -1,10 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
ImageFallbackConfig,
|
|
||||||
ExpressiveCodeConfig,
|
ExpressiveCodeConfig,
|
||||||
LicenseConfig,
|
LicenseConfig,
|
||||||
NavBarConfig,
|
NavBarConfig,
|
||||||
ProfileConfig,
|
ProfileConfig,
|
||||||
SiteConfig,
|
SiteConfig,
|
||||||
|
UmamiConfig,
|
||||||
} from "./types/config";
|
} from "./types/config";
|
||||||
import { LinkPreset } from "./types/config";
|
import { LinkPreset } from "./types/config";
|
||||||
|
|
||||||
@@ -28,7 +28,8 @@ export const siteConfig: SiteConfig = {
|
|||||||
},
|
},
|
||||||
background: {
|
background: {
|
||||||
enable: true, // Enable background image
|
enable: true, // Enable background image
|
||||||
src: "https://eo-pic.2x.nz/h", // Background image URL (supports HTTPS)
|
//src: "https://eo-pic.2x.nz/h", // Background image URL (supports HTTPS)
|
||||||
|
src: "https://api.bimg.cc/random", // Bing daily theme
|
||||||
position: "center", // Background position: 'top', 'center', 'bottom'
|
position: "center", // Background position: 'top', 'center', 'bottom'
|
||||||
size: "cover", // Background size: 'cover', 'contain', 'auto'
|
size: "cover", // Background size: 'cover', 'contain', 'auto'
|
||||||
repeat: "no-repeat", // Background repeat: 'no-repeat', 'repeat', 'repeat-x', 'repeat-y'
|
repeat: "no-repeat", // Background repeat: 'no-repeat', 'repeat', 'repeat-x', 'repeat-y'
|
||||||
@@ -58,8 +59,8 @@ export const navBarConfig: NavBarConfig = {
|
|||||||
LinkPreset.Archive,
|
LinkPreset.Archive,
|
||||||
LinkPreset.About,
|
LinkPreset.About,
|
||||||
{
|
{
|
||||||
name: "GiwtHub",
|
name: "统计信息",
|
||||||
url: "https://github.com/saicaca/fuwari", // Internal links should not include the base path, as it is automatically added
|
url: "https://github.com/统计信息", // Internal links should not include the base path, as it is automatically added
|
||||||
external: true, // Show an external link icon and will open in a new tab
|
external: true, // Show an external link icon and will open in a new tab
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -104,8 +105,9 @@ export const expressiveCodeConfig: ExpressiveCodeConfig = {
|
|||||||
theme: "github-dark",
|
theme: "github-dark",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const imageFallbackConfig: ImageFallbackConfig = {
|
export const umamiConfig: UmamiConfig = {
|
||||||
enable: true,
|
enable: true,
|
||||||
originalDomain: "eo-r2.2x.nz",
|
baseUrl: "https://umami.adclosenn.top",
|
||||||
fallbackDomain: "pub-d433ca7edaa74994b3d7c40a7fd7d9ac.r2.dev",
|
shareId: "XMDJoIb1D21UxHdH",
|
||||||
|
timezone: "Asia/Shanghai",
|
||||||
};
|
};
|
||||||
@@ -115,4 +115,11 @@ export type ImageFallbackConfig = {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
originalDomain: string;
|
originalDomain: string;
|
||||||
fallbackDomain: string;
|
fallbackDomain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UmamiConfig = {
|
||||||
|
enable: boolean;
|
||||||
|
baseUrl: string;
|
||||||
|
shareId: string;
|
||||||
|
timezone: string;
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user