diff --git a/cwd-admin/package.json b/cwd-admin/package.json index 356a4a6..1494527 100644 --- a/cwd-admin/package.json +++ b/cwd-admin/package.json @@ -1,6 +1,6 @@ { "name": "cwd-admin", - "version": "0.1.7", + "version": "0.1.8", "type": "module", "scripts": { "dev": "vite", diff --git a/cwd-api/package.json b/cwd-api/package.json index 009838b..1198190 100644 --- a/cwd-api/package.json +++ b/cwd-api/package.json @@ -1,6 +1,6 @@ { "name": "cwd-api", - "version": "0.1.7", + "version": "0.1.8", "scripts": { "deploy": "node scripts/index.js && wrangler deploy", "dev": "wrangler dev", diff --git a/cwd-api/src/api/public/getPagePv.ts b/cwd-api/src/api/public/getPagePv.ts new file mode 100644 index 0000000..78351ac --- /dev/null +++ b/cwd-api/src/api/public/getPagePv.ts @@ -0,0 +1,27 @@ +import type { Context } from 'hono'; +import type { Bindings } from '../../bindings'; + +export const getPagePv = async (c: Context<{ Bindings: Bindings }>) => { + try { + const rawPostSlug = c.req.query('post_slug') || ''; + const postSlug = rawPostSlug.trim(); + const rawSiteId = c.req.query('siteId') || ''; + const siteId = rawSiteId && rawSiteId !== 'default' ? rawSiteId : ''; + + if (!postSlug) { + return c.json({ message: 'post_slug is required' }, 400); + } + + const row = await c.env.CWD_DB.prepare( + 'SELECT pv FROM page_stats WHERE post_slug = ? AND site_id = ?' + ) + .bind(postSlug, siteId) + .first<{ pv: number }>(); + + const pv = row?.pv || 0; + + return c.json({ pv, postSlug }); + } catch (e: any) { + return c.json({ message: e.message || '获取访问量失败' }, 500); + } +}; \ No newline at end of file diff --git a/cwd-api/src/index.ts b/cwd-api/src/index.ts index 7de8e67..cc87437 100644 --- a/cwd-api/src/index.ts +++ b/cwd-api/src/index.ts @@ -28,6 +28,7 @@ import { testEmail } from './api/admin/testEmail'; import { getStats } from './api/admin/getStats'; import { getSites } from './api/admin/getDomains'; import { trackVisit } from './api/public/trackVisit'; +import { getPagePv } from './api/public/getPagePv'; import { getVisitOverview, getVisitPages } from './api/admin/visitAnalytics'; import { getLikeStatus, likePage } from './api/public/like'; import { likeComment } from './api/public/likeComment'; @@ -268,6 +269,7 @@ app.get('/api/comments', getComments); app.post('/api/comments', postComment); app.post('/api/verify-admin', verifyAdminKey); app.post('/api/analytics/visit', trackVisit); +app.get('/api/analytics/pv', getPagePv); app.get('/api/like', getLikeStatus); app.post('/api/like', likePage); app.post('/api/comments/like', likeComment); diff --git a/docs/widget/index.html b/docs/widget/index.html index eb3539d..fd1f78f 100644 --- a/docs/widget/index.html +++ b/docs/widget/index.html @@ -155,6 +155,7 @@

这是一篇示例文章

+
0

这是一个演示 CWD Comments Widget 的示例页面。你可以在上方的配置面板中修改 API 地址和主题, 然后点击"应用配置"按钮重新加载评论组件。 diff --git a/docs/widget/package.json b/docs/widget/package.json index ac2a0f9..75756e1 100644 --- a/docs/widget/package.json +++ b/docs/widget/package.json @@ -1,6 +1,6 @@ { "name": "cwd-widget", - "version": "0.1.7", + "version": "0.1.8", "description": "Server-free, extremely fast and secure, plug-and-play commenting system based on Cloudflare Workers and the Global Edge Network.", "type": "module", "author": "anghunk", diff --git a/docs/widget/src/core/CWDComments.js b/docs/widget/src/core/CWDComments.js index e7332bf..0901f10 100644 --- a/docs/widget/src/core/CWDComments.js +++ b/docs/widget/src/core/CWDComments.js @@ -59,6 +59,7 @@ export class CWDComments { }; this._likeButtonEl = null; this._likeCountEl = null; + this._pvElement = null; this._mounted = false; @@ -256,6 +257,10 @@ export class CWDComments { this.api.trackVisit(); } + if (this.api && typeof this.api.getPagePv === 'function') { + this._fetchAndFillPv(); + } + if (this.api && typeof this.api.getLikeStatus === 'function') { try { const likeResult = await this.api.getLikeStatus(); @@ -820,4 +825,52 @@ export class CWDComments { getConfig() { return { ...this.config }; } + + /** + * 获取并填充页面访问量 + * @private + */ + async _fetchAndFillPv() { + try { + // 查找固定 ID 的容器 + const container = typeof document !== 'undefined' + ? document.querySelector('#cwd-page-pv') + : null; + + if (!container) { + return; // 容器不存在则静默跳过 + } + + this._pvElement = container; + const result = await this.api.getPagePv(); + const pv = result && typeof result.pv === 'number' ? result.pv : 0; + this._updatePvDisplay(pv); + } catch (e) { + // 静默失败,不影响主功能 + } + } + + /** + * 更新 PV 显示 + * @private + */ + _updatePvDisplay(pv) { + if (!this._pvElement) return; + this._pvElement.textContent = this._formatPvNumber(pv); + this._pvElement.setAttribute('data-cwd-pv', String(pv)); + } + + /** + * 格式化 PV 数字 + * @private + */ + _formatPvNumber(num) { + if (num >= 1000000) { + return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; + } + if (num >= 1000) { + return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; + } + return String(num); + } } diff --git a/docs/widget/src/core/api.js b/docs/widget/src/core/api.js index c099c9a..14539ce 100644 --- a/docs/widget/src/core/api.js +++ b/docs/widget/src/core/api.js @@ -209,6 +209,24 @@ export function createApiClient(config) { } catch (e) {} throw new Error(msg); } + return response.json(); + } + + async function getPagePv() { + const params = new URLSearchParams({ + post_slug: config.postUrl || config.postSlug + }); + + if (config.siteId) { + params.set('siteId', config.siteId); + } + + const response = await fetch(`${baseUrl}/api/analytics/pv?${params}`); + + if (!response.ok) { + return { pv: 0, postSlug: config.postSlug }; + } + return response.json(); } @@ -219,6 +237,7 @@ export function createApiClient(config) { trackVisit, getLikeStatus, likePage, - likeComment + likeComment, + getPagePv }; } diff --git a/package.json b/package.json index dda53c5..5b03062 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cwd", - "version": "0.1.7", + "version": "0.1.8", "license": "Apache-2.0", "repository": { "type": "git",