feat(component): 加入总字数统计模块

This commit is contained in:
Ad-closeNN
2026-04-20 01:16:10 +08:00
parent 9d7af32e25
commit 8f004a9959
5 changed files with 96 additions and 40 deletions
+58 -36
View File
@@ -3,6 +3,7 @@ import { Icon } from "astro-icon/components";
import { profileConfig } 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";
import TotalWords from "./TotalWords.astro";
const config = profileConfig; const config = profileConfig;
--- ---
@@ -45,6 +46,8 @@ const config = profileConfig;
</a>} </a>}
</div> </div>
<TotalWords />
<!-- 全站访问量统计 https://github.com/afoim/fuwari/blob/81f22decb17ff7ee1dd480c10773f7ba8f4df296/src/components/widget/Profile.astro --> <!-- 全站访问量统计 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="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"> <div class="flex items-center justify-center gap-1">
@@ -74,18 +77,37 @@ const config = profileConfig;
fetch("https://v1.hitokoto.cn") fetch("https://v1.hitokoto.cn")
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
// API 返回的字段是 data.text const hitokotoElement = document.getElementById("hitokoto");
document.getElementById("hitokoto").innerText = data.hitokoto; if (hitokotoElement) {
hitokotoElement.innerText = data.hitokoto;
}
}) })
.catch(error => { .catch(error => {
console.error("获取一言失败:", error); console.error("获取一言失败:", error);
//document.getElementById("hitokoto").innerText = "获取失败,请稍后再试。"; const hitokotoElement = document.getElementById("hitokoto");
//失败后用内置的,成功了用别人的。 if (hitokotoElement) {
document.getElementById("hitokoto").innerText = "再热情的心也经不起冷漠,再爱你的人也经不起冷落。"; hitokotoElement.innerText = "再热情的心也经不起冷漠,再爱你的人也经不起冷落。";
}
}); });
</script> </script>
<script> <script>
type SiteStatsData = {
pageviews?: {
value?: number;
};
};
type BlogUmamiStore = {
getStats: (key: string, createUrl: (websiteId: string) => string) => Promise<SiteStatsData | undefined>;
};
declare global {
interface Window {
__blogUmami?: BlogUmamiStore;
}
}
// 获取全站访问量统计 // 获取全站访问量统计
async function loadSiteStats() { async function loadSiteStats() {
const statsElement = document.getElementById('site-stats'); const statsElement = document.getElementById('site-stats');
@@ -96,8 +118,8 @@ fetch("https://v1.hitokoto.cn")
statsElement.dataset.umamiState = 'loading'; statsElement.dataset.umamiState = 'loading';
try { try {
const umamiStore = window['__blogUmami']; const umamiStore = window.__blogUmami;
const statsData = await umamiStore?.getStats('site:all', (websiteId) => { const statsData = await umamiStore?.getStats('site:all', (websiteId: string) => {
const currentTimestamp = Date.now(); const currentTimestamp = Date.now();
return `https://umami.adclosenn.top/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent('Asia/Shanghai')}&compare=false`; return `https://umami.adclosenn.top/api/websites/${websiteId}/stats?startAt=0&endAt=${currentTimestamp}&unit=hour&timezone=${encodeURIComponent('Asia/Shanghai')}&compare=false`;
}); });
@@ -125,46 +147,46 @@ fetch("https://v1.hitokoto.cn")
</script> </script>
<!-- 获取 Commit 信息 via API --> <!-- 获取 Commit 信息 via API -->
<script> <script>
type GithubCommit = {
sha: string;
html_url: string;
commit: {
message: string;
committer: {
date: string;
};
};
};
async function loadCommitStats() { async function loadCommitStats() {
try { try {
const statsElement = document.getElementById('github-commit'); // 查找 id const statsElement = document.getElementById('github-commit');
const link = document.getElementById('github-commit-link'); // 查找 id const link = document.getElementById('github-commit-link');
// 第一步:调用 API
const githubResponse = await fetch(`https://api.github.com/repos/Ad-closeNN/blog-fuwari/commits?per_page=1`); const githubResponse = await fetch(`https://api.github.com/repos/Ad-closeNN/blog-fuwari/commits?per_page=1`);
if (!githubResponse.ok) { if (!githubResponse.ok) {
throw new Error('获取信息失败'); throw new Error('获取信息失败');
} }
let Data = await githubResponse.json(); const data = (await githubResponse.json()) as GithubCommit[];
Data = Data[0]; const latestCommit = data[0];
// 第二步:获取 Commit 数据
const latestCommit = Data;
const commitData = {
hash: latestCommit.sha.slice(0, 7),
fullHash: latestCommit.sha,
message: latestCommit.commit.message.split('\n')[0],
author: latestCommit.commit.author.name,
date: latestCommit.commit.author.date,
url: latestCommit.html_url
};
if (statsElement) {
statsElement.textContent = `当前提交:${Data.sha.slice(0,7)}`;
}
if (link){ if (!latestCommit) {
// const gurl = "https://github.com/Ad-closeNN/blog-fuwari/commit/"+Data.sha; throw new Error('未获取到提交信息');
const gurl = "/info/"; }
link.href = gurl;
link.title = "("+Data.commit.committer.date + ")" + " " + Data.commit.message; if (statsElement) {
statsElement.textContent = `当前提交:${latestCommit.sha.slice(0, 7)}`;
}
if (link instanceof HTMLAnchorElement) {
const infoUrl = "/info/";
link.href = infoUrl;
link.title = `(${latestCommit.commit.committer.date}) ${latestCommit.commit.message}`;
} }
} catch (error) { } catch (error) {
console.error('获取 Commit 信息失败:', error); console.error('获取 Commit 信息失败:', error);
const statsElement = document.getElementById('github-commit'); const statsElement = document.getElementById('github-commit');
if (statsElement) { if (statsElement) {
statsElement.textContent = '提交信息不可用'; statsElement.textContent = '提交信息不可用';
+15
View File
@@ -0,0 +1,15 @@
---
import { Icon } from "astro-icon/components";
import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation";
import { getTotalWords } from "@utils/content-utils";
const totalWords = await getTotalWords();
const formattedTotalWords = totalWords.toLocaleString("zh-CN");
---
<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:article-outline" class="text-base"></Icon>
<span>总计 {formattedTotalWords} {i18n(I18nKey.wordsCount)}</span>
</div>
</div>
+2 -2
View File
@@ -8,7 +8,7 @@
::github{repo="afoim/fuwari"} ::github{repo="afoim/fuwari"}
# 站点分流 # 站点分流
经过几个月的测试,**本站**之后将弃用 Netlify 托管,转而使用免费且更强大的 Cloudflare。之前使用过的分流测试版站点已移除。谢谢 Netlify,你好 Cloudflare 经过几个月的测试,**本站**之后将弃用 Netlify 托管,转而使用免费且更强大的 Cloudflare。之前使用过的分流测试版站点已移除,且博客域名已改为 `blog.adclosenn.top`。谢谢 Netlify,你好 Cloudflare
# 关于我 # 关于我
一位住在 [中华人民共和国广西壮族自治区](https://baike.baidu.com/item/%E5%B9%BF%E8%A5%BF%E5%A3%AE%E6%97%8F%E8%87%AA%E6%B2%BB%E5%8C%BA/163178) 的学生。 [me.adclosenn.top](https://me.adclosenn.top) 一位住在 [中华人民共和国广西壮族自治区](https://baike.baidu.com/item/%E5%B9%BF%E8%A5%BF%E5%A3%AE%E6%97%8F%E8%87%AA%E6%B2%BB%E5%8C%BA/163178) 的学生。 [me.adclosenn.top](https://me.adclosenn.top)
@@ -16,7 +16,7 @@
## 联系方式 ## 联系方式
电子邮箱:[admin@adclosenn.top](mailto:admin@adclosenn.top) 电子邮箱:[admin@adclosenn.top](mailto:admin@adclosenn.top)
Discordhttps://discord.com/users/1068060784300658688 Discordhttps://discord.com/users/1068060784300658688
BlueSkyhttps://bsky.app/profile/adclosenn.top Xhttps://x.com/Ad_closeNN
# 关于本站 # 关于本站
## 字体 ## 字体
+2 -1
View File
@@ -92,7 +92,8 @@ const customcover = entry.data.customcover;
data-pagefind-body data-pagefind-weight="10" data-pagefind-meta="title" data-pagefind-body data-pagefind-weight="10" data-pagefind-meta="title"
class="transition w-full block font-bold mb-3 class="transition w-full block font-bold mb-3
text-3xl md:text-[2.25rem]/[2.75rem] text-3xl md:text-[2.25rem]/[2.75rem]
text-black/90 dark:text-white/90 text-[var(--primary)]
underline decoration-[3px] decoration-[var(--primary)] underline-offset-[0.45rem]
md:before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)] md:before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)]
before:absolute before:top-[0.75rem] before:left-[-1.125rem] before:absolute before:top-[0.75rem] before:left-[-1.125rem]
"> ">
+19 -1
View File
@@ -1,4 +1,4 @@
import { type CollectionEntry, getCollection } from "astro:content"; import { render, type CollectionEntry, getCollection } from "astro:content";
import I18nKey from "@i18n/i18nKey"; import I18nKey from "@i18n/i18nKey";
import { i18n } from "@i18n/translation"; import { i18n } from "@i18n/translation";
import { getCategoryUrl } from "@utils/url-utils.ts"; import { getCategoryUrl } from "@utils/url-utils.ts";
@@ -46,6 +46,24 @@ export async function getSortedPostsList(): Promise<PostForList[]> {
return sortedPostsList; return sortedPostsList;
} }
let totalWordsCache: number | undefined;
export async function getTotalWords(): Promise<number> {
if (totalWordsCache !== undefined) {
return totalWordsCache;
}
const posts = await getRawSortedPosts();
const renderedPosts = await Promise.all(posts.map((post) => render(post)));
const totalWords = renderedPosts.reduce((sum, { remarkPluginFrontmatter }) => {
const words = Number(remarkPluginFrontmatter?.words ?? 0);
return sum + (Number.isFinite(words) ? words : 0);
}, 0);
totalWordsCache = totalWords;
return totalWords;
}
export type Tag = { export type Tag = {
name: string; name: string;
count: number; count: number;