feat(widget): 添加页面访问量显示功能
- 新增公开 API GET /api/analytics/pv 获取页面访问量 - 前端组件初始化时自动填充 #cwd-page-pv 容器 - 支持数字格式化(如 1.2k、1.5M) - 将版本号更新至 0.1.8
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cwd-admin",
|
"name": "cwd-admin",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cwd-api",
|
"name": "cwd-api",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "node scripts/index.js && wrangler deploy",
|
"deploy": "node scripts/index.js && wrangler deploy",
|
||||||
"dev": "wrangler dev",
|
"dev": "wrangler dev",
|
||||||
|
|||||||
27
cwd-api/src/api/public/getPagePv.ts
Normal file
27
cwd-api/src/api/public/getPagePv.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -28,6 +28,7 @@ import { testEmail } from './api/admin/testEmail';
|
|||||||
import { getStats } from './api/admin/getStats';
|
import { getStats } from './api/admin/getStats';
|
||||||
import { getSites } from './api/admin/getDomains';
|
import { getSites } from './api/admin/getDomains';
|
||||||
import { trackVisit } from './api/public/trackVisit';
|
import { trackVisit } from './api/public/trackVisit';
|
||||||
|
import { getPagePv } from './api/public/getPagePv';
|
||||||
import { getVisitOverview, getVisitPages } from './api/admin/visitAnalytics';
|
import { getVisitOverview, getVisitPages } from './api/admin/visitAnalytics';
|
||||||
import { getLikeStatus, likePage } from './api/public/like';
|
import { getLikeStatus, likePage } from './api/public/like';
|
||||||
import { likeComment } from './api/public/likeComment';
|
import { likeComment } from './api/public/likeComment';
|
||||||
@@ -268,6 +269,7 @@ app.get('/api/comments', getComments);
|
|||||||
app.post('/api/comments', postComment);
|
app.post('/api/comments', postComment);
|
||||||
app.post('/api/verify-admin', verifyAdminKey);
|
app.post('/api/verify-admin', verifyAdminKey);
|
||||||
app.post('/api/analytics/visit', trackVisit);
|
app.post('/api/analytics/visit', trackVisit);
|
||||||
|
app.get('/api/analytics/pv', getPagePv);
|
||||||
app.get('/api/like', getLikeStatus);
|
app.get('/api/like', getLikeStatus);
|
||||||
app.post('/api/like', likePage);
|
app.post('/api/like', likePage);
|
||||||
app.post('/api/comments/like', likeComment);
|
app.post('/api/comments/like', likeComment);
|
||||||
|
|||||||
@@ -155,6 +155,7 @@
|
|||||||
|
|
||||||
<div class="demo-post">
|
<div class="demo-post">
|
||||||
<h2>这是一篇示例文章</h2>
|
<h2>这是一篇示例文章</h2>
|
||||||
|
<div id="cwd-page-pv">0</div>
|
||||||
<p>
|
<p>
|
||||||
这是一个演示 CWD Comments Widget 的示例页面。你可以在上方的配置面板中修改 API 地址和主题,
|
这是一个演示 CWD Comments Widget 的示例页面。你可以在上方的配置面板中修改 API 地址和主题,
|
||||||
然后点击"应用配置"按钮重新加载评论组件。
|
然后点击"应用配置"按钮重新加载评论组件。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cwd-widget",
|
"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.",
|
"description": "Server-free, extremely fast and secure, plug-and-play commenting system based on Cloudflare Workers and the Global Edge Network.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "anghunk",
|
"author": "anghunk",
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export class CWDComments {
|
|||||||
};
|
};
|
||||||
this._likeButtonEl = null;
|
this._likeButtonEl = null;
|
||||||
this._likeCountEl = null;
|
this._likeCountEl = null;
|
||||||
|
this._pvElement = null;
|
||||||
|
|
||||||
this._mounted = false;
|
this._mounted = false;
|
||||||
|
|
||||||
@@ -256,6 +257,10 @@ export class CWDComments {
|
|||||||
this.api.trackVisit();
|
this.api.trackVisit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.api && typeof this.api.getPagePv === 'function') {
|
||||||
|
this._fetchAndFillPv();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.api && typeof this.api.getLikeStatus === 'function') {
|
if (this.api && typeof this.api.getLikeStatus === 'function') {
|
||||||
try {
|
try {
|
||||||
const likeResult = await this.api.getLikeStatus();
|
const likeResult = await this.api.getLikeStatus();
|
||||||
@@ -820,4 +825,52 @@ export class CWDComments {
|
|||||||
getConfig() {
|
getConfig() {
|
||||||
return { ...this.config };
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,6 +209,24 @@ export function createApiClient(config) {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
throw new Error(msg);
|
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();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +237,7 @@ export function createApiClient(config) {
|
|||||||
trackVisit,
|
trackVisit,
|
||||||
getLikeStatus,
|
getLikeStatus,
|
||||||
likePage,
|
likePage,
|
||||||
likeComment
|
likeComment,
|
||||||
|
getPagePv
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cwd",
|
"name": "cwd",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user