Files
cwd/docs/widget/src/core/api.js
anghunk 2ad9c90d18 fix(widget): 修复页面访问量获取失败的问题
- 前端 trackVisit 现在传递 siteId 参数
- 后端 getPagePv 查询时处理 site_id 为空或 NULL 的情况
2026-03-16 14:09:59 +08:00

248 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* API 请求封装
*/
/**
* 创建 API 客户端
* @param {Object} config - 配置对象
* @param {string} config.apiBaseUrl - API 基础地址
* @param {string} config.postSlug - 文章标识符
* @param {string} config.postTitle - 文章标题(可选)
* @param {string} config.postUrl - 文章 URL可选
* @returns {Object}
*/
export function createApiClient(config) {
const baseUrl = config.apiBaseUrl.replace(/\/$/, '');
function getLikeUserId() {
try {
const storageKey = 'cwd_like_uid';
let token = localStorage.getItem(storageKey);
if (!token) {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
token = crypto.randomUUID();
} else {
token = `${Date.now()}_${Math.random().toString(16).slice(2)}`;
}
localStorage.setItem(storageKey, token);
}
return token;
} catch (e) {
return 'anonymous';
}
}
/**
* 获取评论列表
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @returns {Promise<Object>}
*/
async function fetchComments(page = 1, limit = 20) {
const params = new URLSearchParams({
post_slug: config.postSlug,
page: page.toString(),
limit: limit.toString(),
nested: 'true',
});
if (config.avatarPrefix) {
params.set('avatar_prefix', config.avatarPrefix);
}
if (config.siteId) {
params.set('site_id', config.siteId);
}
const response = await fetch(`${baseUrl}/api/comments?${params}`);
if (!response.ok) {
throw new Error(`获取评论失败:${response.status} ${response.statusText}`);
}
return response.json();
}
/**
* 提交评论
* @param {Object} data - 评论数据
* @param {string} data.name - 昵称
* @param {string} data.email - 邮箱
* @param {string} data.url - 网址(可选)
* @param {string} data.content - 评论内容
* @param {number} data.parentId - 父评论 ID可选用于回复
* @returns {Promise<Object>}
*/
async function submitComment(data) {
const response = await fetch(`${baseUrl}/api/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
post_slug: config.postSlug,
post_title: config.postTitle,
post_url: config.postUrl,
name: data.name,
email: data.email,
url: data.url || undefined,
content: data.content,
parent_id: data.parentId,
adminToken: data.adminToken,
site_id: config.siteId
}),
});
if (!response.ok) {
// Try to parse error message
let msg = response.statusText;
try {
const json = await response.json();
if (json.message) msg = json.message;
} catch (e) {}
throw new Error(msg);
}
return response.json();
}
async function verifyAdminKey(key) {
const response = await fetch(`${baseUrl}/api/verify-admin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ adminToken: key })
});
if (!response.ok) {
let msg = response.statusText;
try {
const json = await response.json();
if (json.message) msg = json.message;
} catch (e) {}
throw new Error(msg);
}
return response.json();
}
async function trackVisit() {
try {
const body = {
postSlug: config.postUrl || config.postSlug,
postTitle: config.postTitle,
postUrl: config.postUrl
};
if (config.siteId) {
body.siteId = config.siteId;
}
await fetch(`${baseUrl}/api/analytics/visit`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
} catch (e) {
}
}
async function getLikeStatus() {
const params = new URLSearchParams({
post_slug: config.postUrl
});
const headers = {
'X-CWD-Like-User': getLikeUserId()
};
const response = await fetch(`${baseUrl}/api/like?${params.toString()}`, {
method: 'GET',
headers
});
if (!response.ok) {
return {
liked: false,
alreadyLiked: false,
totalLikes: 0
};
}
return response.json();
}
async function likePage() {
const headers = {
'Content-Type': 'application/json',
'X-CWD-Like-User': getLikeUserId()
};
const response = await fetch(`${baseUrl}/api/like`, {
method: 'POST',
headers,
body: JSON.stringify({
postSlug: config.postSlug,
postTitle: config.postTitle,
postUrl: config.postUrl
})
});
if (!response.ok) {
let msg = response.statusText;
try {
const json = await response.json();
if (json.message) msg = json.message;
} catch (e) {}
throw new Error(msg);
}
return response.json();
}
async function likeComment(commentId, isLike = true) {
const id =
typeof commentId === 'number'
? commentId
: typeof commentId === 'string' && commentId.trim()
? Number.parseInt(commentId.trim(), 10)
: NaN;
if (!Number.isFinite(id) || id <= 0) {
throw new Error('Invalid comment id');
}
const method = isLike ? 'POST' : 'DELETE';
const response = await fetch(`${baseUrl}/api/comments/like`, {
method,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id })
});
if (!response.ok) {
let msg = response.statusText;
try {
const json = await response.json();
if (json.message) msg = json.message;
} 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();
}
return {
fetchComments,
submitComment,
verifyAdminKey,
trackVisit,
getLikeStatus,
likePage,
likeComment,
getPagePv
};
}