网站信息卡
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import { getCategoryHref, type BlogPost } from '@/lib/posts';
|
||||
import SiteInfoCard from '@/components/SiteInfoCard.astro';
|
||||
import { siteConfig } from '../../site.config.mjs';
|
||||
|
||||
type TocItem = {
|
||||
@@ -17,9 +18,10 @@ interface Props {
|
||||
post: BlogPost;
|
||||
toc: TocItem[];
|
||||
relatedPosts: BlogPost[];
|
||||
posts: BlogPost[];
|
||||
}
|
||||
|
||||
const { post, toc, relatedPosts } = Astro.props;
|
||||
const { post, toc, relatedPosts, posts } = Astro.props;
|
||||
const avatar = siteConfig.author.avatar;
|
||||
const topDepth = toc.length > 0 ? Math.min(...toc.map((item) => item.depth)) : 1;
|
||||
const tocSections = toc.reduce<TocSection[]>((sections, item) => {
|
||||
@@ -107,5 +109,7 @@ const tocSections = toc.reduce<TocSection[]>((sections, item) => {
|
||||
<p class="sidebar-empty">这个分类下暂时没有其他文章。</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<SiteInfoCard posts={posts} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import type { BlogPost } from '@/lib/posts';
|
||||
import SiteInfoCard from '@/components/SiteInfoCard.astro';
|
||||
import { siteConfig } from '../../site.config.mjs';
|
||||
|
||||
interface Props {
|
||||
@@ -111,6 +112,8 @@ const recentPosts = posts.slice(0, 5);
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<SiteInfoCard posts={posts} />
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
import type { BlogPost } from '@/lib/posts';
|
||||
import { siteConfig } from '../../site.config.mjs';
|
||||
|
||||
interface Props {
|
||||
posts: BlogPost[];
|
||||
}
|
||||
|
||||
const { posts } = Astro.props;
|
||||
const latestPost = posts[0];
|
||||
const totalWordCount = posts.reduce((total, post) => {
|
||||
const plainText = post.body
|
||||
.replace(/```[\s\S]*?```/g, '')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/[#>*_`~\-\[\]\(\)]/g, '')
|
||||
.replace(/\s+/g, '');
|
||||
|
||||
return total + plainText.length;
|
||||
}, 0);
|
||||
const formatStatNumber = (value: number) => new Intl.NumberFormat('en-US').format(value);
|
||||
const siteStats = [
|
||||
{ label: '文章数目', value: formatStatNumber(posts.length) },
|
||||
{ label: '本站总字数', value: formatStatNumber(totalWordCount) },
|
||||
{ label: '本站访客数', value: '--', key: 'visitors' },
|
||||
{ label: '本站访问量', value: '--', key: 'pageviews' },
|
||||
{ label: '最后更新时间', value: latestPost?.dateText ?? '--' },
|
||||
];
|
||||
const statsEndpoint = siteConfig.analytics.umami.statsEndpoint;
|
||||
---
|
||||
|
||||
<section class="sidebar-card site-info-card" data-site-info-card data-stats-endpoint={statsEndpoint}>
|
||||
<h2><i class="fa-solid fa-chart-line" aria-hidden="true"></i> 网站信息</h2>
|
||||
<dl class="site-info-list">
|
||||
{siteStats.map((item) => (
|
||||
<div>
|
||||
<dt>{item.label}</dt>
|
||||
<dd data-stat-key={item.key}>{item.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const cacheDuration = 10 * 60 * 1000;
|
||||
const formatter = new Intl.NumberFormat('en-US');
|
||||
const cards = [...document.querySelectorAll('[data-site-info-card]')];
|
||||
const endpoint = cards.find((card) => card.dataset.statsEndpoint)?.dataset.statsEndpoint;
|
||||
|
||||
if (!endpoint) return;
|
||||
|
||||
const cacheKey = `site-info-stats:${endpoint}`;
|
||||
|
||||
const updateCards = (stats) => {
|
||||
const visitors = Number(stats?.visitors);
|
||||
const pageviews = Number(stats?.pageviews);
|
||||
|
||||
cards.forEach((card) => {
|
||||
const visitorsEl = card.querySelector('[data-stat-key="visitors"]');
|
||||
const pageviewsEl = card.querySelector('[data-stat-key="pageviews"]');
|
||||
if (visitorsEl && Number.isFinite(visitors)) visitorsEl.textContent = formatter.format(visitors);
|
||||
if (pageviewsEl && Number.isFinite(pageviews)) pageviewsEl.textContent = formatter.format(pageviews);
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(localStorage.getItem(cacheKey) || 'null');
|
||||
if (cached?.time && Date.now() - cached.time < cacheDuration) {
|
||||
updateCards(cached.data);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
localStorage.removeItem(cacheKey);
|
||||
}
|
||||
|
||||
fetch(endpoint, { headers: { Accept: 'application/json' } })
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(`Stats request failed: ${response.status}`);
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
localStorage.setItem(cacheKey, JSON.stringify({ time: Date.now(), data }));
|
||||
updateCards(data);
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -21,6 +21,7 @@ const publicConfigJson = JSON.stringify(publicSiteConfig).replace(/</g, '\\u003c
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content={description} />
|
||||
<link rel="icon" href={siteConfig.assets.icon} />
|
||||
<script defer src={siteConfig.analytics.umami.script} data-website-id={siteConfig.analytics.umami.websiteId}></script>
|
||||
<script is:inline set:html={`window.SITE_CONFIG = ${publicConfigJson};`}></script>
|
||||
<script is:inline>
|
||||
(() => {
|
||||
|
||||
@@ -127,7 +127,7 @@ const relatedPosts = allPosts
|
||||
<PostCopyrightCard post={post} />
|
||||
{post.comments && <TwikooComments envId={siteConfig.comments.envId} region={siteConfig.comments.region} path={post.href} title={post.title} />}
|
||||
</article>
|
||||
<ArticleSidebar post={post} toc={toc} relatedPosts={relatedPosts} />
|
||||
<ArticleSidebar post={post} toc={toc} relatedPosts={relatedPosts} posts={allPosts} />
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
|
||||
@@ -686,6 +686,56 @@ a {
|
||||
line-height: 1.9;
|
||||
}
|
||||
|
||||
.site-info-card h2 i {
|
||||
width: 18px;
|
||||
color: #3eb8be;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.site-info-list {
|
||||
display: grid;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-info-list div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
border-top: 1px dashed rgba(104, 111, 120, 0.2);
|
||||
padding: 9px 0;
|
||||
}
|
||||
|
||||
.site-info-list div:first-child {
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.site-info-list div:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.site-info-list dt,
|
||||
.site-info-list dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-info-list dt {
|
||||
color: #68717a;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.site-info-list dd {
|
||||
min-width: 0;
|
||||
color: #1aa8f6;
|
||||
font-size: 0.98rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.3;
|
||||
text-align: right;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#welcome-info,
|
||||
#welcome-ip-location-info {
|
||||
margin: 0.65em 0;
|
||||
@@ -2885,6 +2935,7 @@ meting-js {
|
||||
.notice-card p,
|
||||
#welcome-info,
|
||||
#welcome-ip-location-info,
|
||||
.site-info-list dt,
|
||||
.toc-link-child,
|
||||
.same-category-list a,
|
||||
.profile-links,
|
||||
@@ -2893,6 +2944,10 @@ meting-js {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .site-info-list div {
|
||||
border-top-color: rgba(174, 205, 197, 0.16);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] :where(
|
||||
.meta-list a,
|
||||
.article-tag-list a,
|
||||
|
||||
Reference in New Issue
Block a user