优化细节

This commit is contained in:
浪子
2026-05-17 17:43:48 +08:00
parent ee4dd9973e
commit bfec198c18
6 changed files with 236 additions and 8 deletions
+12
View File
@@ -1977,6 +1977,18 @@ h1.entry-title {
color: #888888; color: #888888;
} }
.entry-census .bull {
margin: 0 5px;
}
.twikoo-views[hidden] {
display: none !important;
}
.twikoo-comments[hidden] {
display: none !important;
}
.entry-header hr { .entry-header hr {
width: 30%; width: 30%;
height: 1px; height: 1px;
+15 -2
View File
@@ -1,11 +1,14 @@
--- ---
import type { PostListItem } from './PostListThumb.astro'; import type { PostListItem } from './PostListThumb.astro';
import TwikooViews from './TwikooViews.astro';
import config from '../../asky.config';
export interface Props { export interface Props {
posts: PostListItem[]; posts: PostListItem[];
} }
const { posts } = Astro.props; const { posts } = Astro.props;
const showTwikooStats = Boolean((config.twikoo?.envId || '').trim());
function timeSince(date: string): string { function timeSince(date: string): string {
const past = new Date(date).getTime(); const past = new Date(date).getTime();
@@ -45,15 +48,25 @@ function timeSince(date: string): string {
<a href={post.permalink}><i class="iconfont icon-more"></i></a> <a href={post.permalink}><i class="iconfont icon-more"></i></a>
</div> </div>
<div class="info-meta"> <div class="info-meta">
{showTwikooStats && (
<div class="comnum"> <div class="comnum">
<span><i class="iconfont icon-mark"></i>{post.comments ?? 0} 条评论</span> <span class="twikoo-comments" data-twikoo-comments-url={post.permalink} hidden>
<i class="iconfont icon-mark"></i><span data-twikoo-comments-count></span> 条评论
</span>
</div> </div>
)}
{showTwikooStats && (
<div class="views"> <div class="views">
<span><i class="iconfont icon-eye"></i>{post.views ?? 0} 热度</span> <span class="twikoo-views" data-twikoo-views-url={post.permalink} hidden>
<i class="iconfont icon-eye"></i><span data-twikoo-views-count></span> 热度
</span>
</div> </div>
)}
</div> </div>
</footer> </footer>
</div> </div>
<hr /> <hr />
</article> </article>
))} ))}
{showTwikooStats && <TwikooViews />}
+15 -2
View File
@@ -1,5 +1,7 @@
--- ---
import { askyOption, bloginfo } from '../lib/options'; import { askyOption, bloginfo } from '../lib/options';
import TwikooViews from './TwikooViews.astro';
import config from '../../asky.config';
export interface PostListItem { export interface PostListItem {
id: string; id: string;
@@ -23,6 +25,7 @@ const focusLogo = askyOption('focus_logo') || '/images/avatar.jpg';
const showLike = askyOption('post_like') === 'yes'; const showLike = askyOption('post_like') === 'yes';
const blogName = bloginfo('name'); const blogName = bloginfo('name');
const blogUrl = bloginfo('url'); const blogUrl = bloginfo('url');
const showTwikooStats = Boolean((config.twikoo?.envId || '').trim());
function timeSince(date: string): string { function timeSince(date: string): string {
const past = new Date(date).getTime(); const past = new Date(date).getTime();
@@ -69,8 +72,16 @@ function timeSince(date: string): string {
<p class="post-text" style="width:92%;float:right;">{post.excerpt}</p> <p class="post-text" style="width:92%;float:right;">{post.excerpt}</p>
</div> </div>
<div class="post-meta" style="margin-top:10px;width:92%;float:right;"> <div class="post-meta" style="margin-top:10px;width:92%;float:right;">
<span><i class="iconfont icon-eye"></i>{post.views ?? 0} </span> {showTwikooStats && (
<span class="comments-number"><i class="iconfont icon-mark"></i>{post.comments ?? 0}</span> <span class="twikoo-views" data-twikoo-views-url={post.permalink} hidden>
<i class="iconfont icon-eye"></i><span data-twikoo-views-count></span>
</span>
)}
{showTwikooStats && (
<span class="comments-number twikoo-comments" data-twikoo-comments-url={post.permalink} hidden>
<i class="iconfont icon-mark"></i><span data-twikoo-comments-count></span>
</span>
)}
{showLike && ( {showLike && (
<span class="post-like"> <span class="post-like">
<a href="javascript:;" data-action="ding" data-id={post.id} class="specsZan" style="padding:0px"> <a href="javascript:;" data-action="ding" data-id={post.id} class="specsZan" style="padding:0px">
@@ -84,3 +95,5 @@ function timeSince(date: string): string {
</div> </div>
</article> </article>
))} ))}
{showTwikooStats && <TwikooViews />}
+166
View File
@@ -0,0 +1,166 @@
---
import config from '../../asky.config';
export interface Props {
incrementUrl?: string;
title?: string;
}
const { incrementUrl = '', title = '' } = Astro.props;
const envId = (config.twikoo?.envId || '').replace(/\/$/, '');
---
{envId && (
<script is:inline define:vars={{ envId, incrementUrl, title }}>
(function () {
function normalizePath(value) {
if (!value) return '';
var path = '';
try {
path = new URL(value, location.origin).pathname;
} catch (e) {
path = String(value || '');
}
if (path.length > 1 && path.charAt(path.length - 1) !== '/') path += '/';
return path;
}
function callApi(event, data) {
return new Promise(function (resolve, reject) {
var accessToken = null;
try { accessToken = localStorage.getItem('twikoo-access-token'); } catch (e) {}
var body = Object.assign({ event: event, accessToken: accessToken }, data || {});
try {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
try {
var result = JSON.parse(xhr.responseText);
if (result && result.accessToken) {
try { localStorage.setItem('twikoo-access-token', result.accessToken); } catch (e) {}
}
resolve(result);
} catch (e) { reject(e); }
} else {
reject(new Error('HTTP ' + xhr.status));
}
};
xhr.open('POST', envId);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(body));
} catch (e) { reject(e); }
});
}
function storageKey(path) {
return 'twikoo-counter:' + path;
}
function readCachedCount(path) {
try {
var raw = localStorage.getItem(storageKey(path));
if (raw == null || raw === '') return null;
var count = parseInt(raw, 10);
return Number.isFinite(count) ? count : null;
} catch (e) {
return null;
}
}
function writeCachedCount(path, count) {
try { localStorage.setItem(storageKey(path), String(count)); } catch (e) {}
}
function renderCount(path, count) {
document.querySelectorAll('[data-twikoo-views-url]').forEach(function (el) {
if (normalizePath(el.getAttribute('data-twikoo-views-url')) !== path) return;
var target = el.querySelector('[data-twikoo-views-count]');
if (target) target.textContent = String(count);
el.hidden = false;
});
}
function renderCachedCounts() {
document.querySelectorAll('[data-twikoo-views-url]').forEach(function (el) {
var path = normalizePath(el.getAttribute('data-twikoo-views-url'));
var count = readCachedCount(path);
if (count != null) renderCount(path, count);
});
}
function uniqueItems(items) {
return items.filter(function (item, index) {
return item && items.indexOf(item) === index;
});
}
function normalizeCommentPath(value) {
var path = normalizePath(value);
if (path.length > 1) path = path.replace(/\/+$/, '');
return path;
}
function commentPathVariants(value) {
var path = normalizeCommentPath(value);
if (!path) return [];
if (path === '/') return ['/'];
return [path, path + '/'];
}
function collectCommentUrls() {
var urls = [];
document.querySelectorAll('[data-twikoo-comments-url]').forEach(function (el) {
urls = urls.concat(commentPathVariants(el.getAttribute('data-twikoo-comments-url')));
});
return uniqueItems(urls);
}
function renderCommentCount(path, count) {
var normalizedPath = normalizeCommentPath(path);
document.querySelectorAll('[data-twikoo-comments-url]').forEach(function (el) {
if (normalizeCommentPath(el.getAttribute('data-twikoo-comments-url')) !== normalizedPath) return;
var target = el.querySelector('[data-twikoo-comments-count]');
if (target) target.textContent = String(count);
el.hidden = false;
});
}
function loadCommentCounts() {
var urls = collectCommentUrls();
if (!urls.length) return;
callApi('GET_COMMENTS_COUNT', { urls: urls }).then(function (res) {
if (!res || (res.code && res.code !== 0) || !Array.isArray(res.data)) return;
var totals = {};
res.data.forEach(function (item) {
var path = normalizeCommentPath(item.url);
var count = parseInt(item.count, 10);
totals[path] = (totals[path] || 0) + (Number.isFinite(count) ? count : 0);
});
Object.keys(totals).forEach(function (path) {
renderCommentCount(path, totals[path]);
});
}).catch(function () {});
}
renderCachedCounts();
loadCommentCounts();
var currentPath = normalizePath(incrementUrl);
if (!currentPath) return;
callApi('COUNTER_GET', {
url: currentPath,
href: new URL(currentPath, location.origin).href,
title: title || document.title
}).then(function (res) {
if (!res || (res.code && res.code !== 0)) return;
var current = parseInt(res.time, 10);
if (!Number.isFinite(current)) current = 0;
var next = current + 1;
writeCachedCount(currentPath, next);
renderCount(currentPath, next);
}).catch(function () {});
})();
</script>
)}
+10
View File
@@ -18,6 +18,7 @@ export interface Props {
patternAuthorUrl?: string; patternAuthorUrl?: string;
patternAvatar?: string; patternAvatar?: string;
patternMeta?: string; patternMeta?: string;
patternViewsUrl?: string;
/** 自定义 body class */ /** 自定义 body class */
bodyClass?: string; bodyClass?: string;
} }
@@ -34,6 +35,7 @@ const {
patternAuthorUrl = '/', patternAuthorUrl = '/',
patternAvatar, patternAvatar,
patternMeta, patternMeta,
patternViewsUrl,
bodyClass = '' bodyClass = ''
} = Astro.props; } = Astro.props;
@@ -141,6 +143,14 @@ const patternHeaderClass = `pattern-header${patternSingle ? ' single-header' : '
{patternMeta} {patternMeta}
</> </>
)} )}
{patternViewsUrl && (
<>
{(patternAuthor || patternMeta) && <span class="bull">·</span>}
<span class="twikoo-views" data-twikoo-views-url={patternViewsUrl} hidden>
<i class="iconfont icon-eye"></i><span data-twikoo-views-count></span> 热度
</span>
</>
)}
</p> </p>
</> </>
) : ( ) : (
+14
View File
@@ -7,8 +7,10 @@ import AuthorProfile from '../../components/AuthorProfile.astro';
import PostNextPrev from '../../components/PostNextPrev.astro'; import PostNextPrev from '../../components/PostNextPrev.astro';
import ShareLike from '../../components/ShareLike.astro'; import ShareLike from '../../components/ShareLike.astro';
import Comments from '../../components/Comments.astro'; import Comments from '../../components/Comments.astro';
import TwikooViews from '../../components/TwikooViews.astro';
import { askyOption, isOptionOn } from '../../lib/options'; import { askyOption, isOptionOn } from '../../lib/options';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import config from '../../../asky.config';
export async function getStaticPaths() { export async function getStaticPaths() {
const all = (await getCollection('posts')).sort( const all = (await getCollection('posts')).sort(
@@ -35,9 +37,11 @@ const showPattern = isOptionOn('patternimg');
const buildTime = post.data.date.toLocaleString('zh-CN'); const buildTime = post.data.date.toLocaleString('zh-CN');
const tags = post.data.tags ?? []; const tags = post.data.tags ?? [];
const fullUrl = new URL(`/posts/${post.slug}`, Astro.site ?? 'https://example.com').toString(); const fullUrl = new URL(`/posts/${post.slug}`, Astro.site ?? 'https://example.com').toString();
const postPath = `/posts/${post.slug}/`;
const patternImg = showPattern ? undefined : (post.data.thumbnail ?? '/images/hd.jpg'); const patternImg = showPattern ? undefined : (post.data.thumbnail ?? '/images/hd.jpg');
const authorName = post.data.author ?? 'Admin'; const authorName = post.data.author ?? 'Admin';
const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg'; const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg';
const showTwikooViews = Boolean((config.twikoo?.envId || '').trim());
--- ---
<BaseLayout <BaseLayout
@@ -50,6 +54,7 @@ const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg';
patternAuthorUrl="/" patternAuthorUrl="/"
patternAvatar={authorAvatar} patternAvatar={authorAvatar}
patternMeta={buildTime} patternMeta={buildTime}
patternViewsUrl={showTwikooViews ? postPath : undefined}
> >
<Imgbox slot="imgbox" /> <Imgbox slot="imgbox" />
<Header slot="header" /> <Header slot="header" />
@@ -63,6 +68,14 @@ const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg';
<h1 class="entry-title">{post.data.title}</h1> <h1 class="entry-title">{post.data.title}</h1>
<p class="entry-census"> <p class="entry-census">
<i class="iconfont icon-clock"></i>{buildTime} <i class="iconfont icon-clock"></i>{buildTime}
{showTwikooViews && (
<>
<span class="bull">·</span>
<span class="twikoo-views" data-twikoo-views-url={postPath} hidden>
<i class="iconfont icon-eye"></i><span data-twikoo-views-count></span> 热度
</span>
</>
)}
</p> </p>
<hr /> <hr />
</header> </header>
@@ -89,6 +102,7 @@ const authorAvatar = askyOption('focus_logo') || '/images/avatar.jpg';
<PostNextPrev prev={prev} next={next} /> <PostNextPrev prev={prev} next={next} />
<AuthorProfile authorName={authorName} authorUrl="/" /> <AuthorProfile authorName={authorName} authorUrl="/" />
{showTwikooViews && <TwikooViews incrementUrl={postPath} title={post.data.title} />}
<Comments /> <Comments />
</main> </main>
</div> </div>