blog / src /components /widget /SiteStats.astro
cacode's picture
Upload 434 files
96dd062 verified
---
import { Icon } from "astro-icon/components";
import WidgetLayout from "@/components/common/WidgetLayout.astro";
import { siteConfig } from "@/config";
import I18nKey from "@/i18n/i18nKey";
import { i18n } from "@/i18n/translation";
import {
getCategoryList,
getSortedPosts,
getTagList,
} from "@/utils/content-utils";
interface Props {
class?: string;
style?: string;
}
const { class: className, style } = Astro.props;
// 从配置中获取站点开始日期
const siteStartDate = siteConfig.siteStartDate || "2025-01-01";
// 获取所有文章
const posts = await getSortedPosts();
const categories = await getCategoryList();
const tags = await getTagList();
// 计算总字数
let totalWords = 0;
for (const post of posts) {
if (post.body) {
// 移除代码块和内联代码后计算字数
let text = post.body
.replace(/```[\s\S]*?```/g, "") // 移除代码块
.replace(/`[^`]*`/g, "") // 移除内联代码
.replace(/\s+/g, " ") // 合并空白
.trim();
// 分别计算中文字符和英文字符
const chineseChars = text.match(/[\u4e00-\u9fa5]/g) || [];
const englishChars = text.match(/[a-zA-Z]/g) || [];
totalWords += chineseChars.length + englishChars.length;
}
}
// 格式化数字(添加千位分隔符)
function formatNumber(num: number): string {
return num.toLocaleString();
}
// 获取最新文章日期(用于客户端计算)
const latestPost = posts.reduce((latest, post) => {
if (!latest) return post;
return post.data.published > latest.data.published ? post : latest;
}, posts[0]);
const lastPostDate = latestPost
? latestPost.data.published.toISOString()
: null;
const todayText = i18n(I18nKey.today);
const stats = [
{
icon: "material-symbols:article-outline",
label: i18n(I18nKey.siteStatsPostCount),
value: posts.length,
},
{
icon: "material-symbols:folder-outline",
label: i18n(I18nKey.siteStatsCategoryCount),
value: categories.length,
},
{
icon: "material-symbols:label-outline",
label: i18n(I18nKey.siteStatsTagCount),
value: tags.length,
},
{
icon: "material-symbols:text-ad-outline-rounded",
label: i18n(I18nKey.siteStatsTotalWords),
value: totalWords,
formatted: true,
},
{
icon: "material-symbols:calendar-clock-outline",
label: i18n(I18nKey.siteStatsRunningDays),
value: 0, // 将由客户端更新
suffix: i18n(I18nKey.siteStatsDays).replace("{days}", ""),
dynamic: true,
id: "running-days",
},
{
icon: "material-symbols:ecg-heart-outline",
label: i18n(I18nKey.siteStatsLastUpdate),
value: 0, // 将由客户端更新
suffix: i18n(I18nKey.siteStatsDaysAgo).replace("{days}", ""),
dynamic: true,
id: "last-update",
},
];
---
<WidgetLayout name={i18n(I18nKey.siteStats)} id="site-stats" class={className} style={style}>
<div class="flex flex-col gap-2">
{stats.map((stat) => (
<div class="flex items-center justify-between px-3 py-1.5">
<div class="flex items-center gap-2.5">
<div class="text-(--primary) text-xl">
<Icon name={stat.icon} />
</div>
<span class="text-neutral-700 dark:text-neutral-300 font-medium text-sm">
{stat.label}
</span>
</div>
<div class="flex items-center">
<span
class="text-base font-bold text-neutral-900 dark:text-neutral-100"
data-stat-id={stat.id}
>
{stat.formatted ? formatNumber(stat.value) : stat.value}
</span>
{stat.suffix && (
<span class="text-sm text-neutral-500 dark:text-neutral-400 ml-1">
{stat.suffix}
</span>
)}
</div>
</div>
))}
</div>
</WidgetLayout>
<script is:inline define:vars={{ siteStartDate, lastPostDate, todayText }}>
function updateDynamicStats() {
const today = new Date();
// 更新运行天数
const startDate = new Date(siteStartDate);
const diffTime = Math.abs(today.getTime() - startDate.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const runningDaysElements = document.querySelectorAll('[data-stat-id="running-days"]');
runningDaysElements.forEach((element) => {
element.textContent = diffDays.toString();
});
// 更新最后活动时间
if (lastPostDate) {
const lastPost = new Date(lastPostDate);
const timeSinceLastPost = Math.abs(today.getTime() - lastPost.getTime());
const daysSinceLastUpdate = Math.floor(timeSinceLastPost / (1000 * 60 * 60 * 24));
const lastUpdateElements = document.querySelectorAll('[data-stat-id="last-update"]');
lastUpdateElements.forEach((element) => {
if (daysSinceLastUpdate === 0) {
element.textContent = todayText;
if (element.nextElementSibling) {
element.nextElementSibling.style.display = 'none';
}
} else {
element.textContent = daysSinceLastUpdate.toString();
if (element.nextElementSibling) {
element.nextElementSibling.style.display = '';
}
}
});
}
}
// 页面加载时更新
updateDynamicStats();
// 页面切换时重新更新
document.addEventListener("swup:contentReplaced", () => {
setTimeout(updateDynamicStats, 100);
});
</script>