feat: 分离文章slug与URL字段并支持slug模式匹配查询
- 新增 `post_url` 字段存储文章完整URL,`post_slug` 仅存储标识符 - 更新管理后台评论列表、编辑、统计及域名获取接口以使用新字段 - 修改公共评论查询接口,支持slug精确匹配及带`?`、`#`参数的模糊匹配 - 更新前端管理界面,在评论编辑表单中增加"评论地址"(`postUrl`)字段 - 同步更新API文档,明确字段用途及查询参数说明
This commit is contained in:
@@ -13,6 +13,7 @@ export type CommentItem = {
|
|||||||
email: string;
|
email: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
postSlug: string;
|
postSlug: string;
|
||||||
|
postUrl: string | null;
|
||||||
url: string | null;
|
url: string | null;
|
||||||
ipAddress: string | null;
|
ipAddress: string | null;
|
||||||
contentText: string;
|
contentText: string;
|
||||||
@@ -172,6 +173,7 @@ export function updateComment(data: {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
|
postUrl?: string | null;
|
||||||
postSlug?: string;
|
postSlug?: string;
|
||||||
contentText: string;
|
contentText: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
@@ -182,6 +184,7 @@ export function updateComment(data: {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
url: data.url ?? null,
|
url: data.url ?? null,
|
||||||
|
postUrl: data.postUrl ?? null,
|
||||||
postSlug: data.postSlug,
|
postSlug: data.postSlug,
|
||||||
content: data.contentText,
|
content: data.contentText,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
|
|||||||
@@ -16,9 +16,13 @@
|
|||||||
<input v-model="form.url" class="form-input" type="text" />
|
<input v-model="form.url" class="form-input" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label class="form-label">评论地址</label>
|
<label class="form-label">页面标识</label>
|
||||||
<input v-model="form.postSlug" class="form-input" type="text" />
|
<input v-model="form.postSlug" class="form-input" type="text" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">评论地址</label>
|
||||||
|
<input v-model="form.postUrl" class="form-input" type="text" />
|
||||||
|
</div>
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label class="form-label">评论内容</label>
|
<label class="form-label">评论内容</label>
|
||||||
<textarea v-model="form.contentText" class="form-input" rows="4"></textarea>
|
<textarea v-model="form.contentText" class="form-input" rows="4"></textarea>
|
||||||
@@ -66,6 +70,7 @@ interface EditForm {
|
|||||||
email: string;
|
email: string;
|
||||||
url: string;
|
url: string;
|
||||||
postSlug: string;
|
postSlug: string;
|
||||||
|
postUrl: string;
|
||||||
contentText: string;
|
contentText: string;
|
||||||
status: string;
|
status: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ const editForm = ref<{
|
|||||||
email: string;
|
email: string;
|
||||||
url: string;
|
url: string;
|
||||||
postSlug: string;
|
postSlug: string;
|
||||||
|
postUrl: string;
|
||||||
contentText: string;
|
contentText: string;
|
||||||
status: string;
|
status: string;
|
||||||
priority: number;
|
priority: number;
|
||||||
@@ -438,6 +439,7 @@ function openEdit(item: CommentItem) {
|
|||||||
email: item.email,
|
email: item.email,
|
||||||
url: item.url || "",
|
url: item.url || "",
|
||||||
postSlug: item.postSlug || "",
|
postSlug: item.postSlug || "",
|
||||||
|
postUrl: item.postUrl || "",
|
||||||
contentText: item.contentText,
|
contentText: item.contentText,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
priority:
|
priority:
|
||||||
@@ -466,6 +468,7 @@ async function submitEdit() {
|
|||||||
const trimmedContent = current.contentText.trim();
|
const trimmedContent = current.contentText.trim();
|
||||||
const trimmedUrl = current.url.trim();
|
const trimmedUrl = current.url.trim();
|
||||||
const trimmedPostSlug = current.postSlug.trim();
|
const trimmedPostSlug = current.postSlug.trim();
|
||||||
|
const trimmedPostUrl = current.postUrl.trim();
|
||||||
const priorityValue =
|
const priorityValue =
|
||||||
typeof current.priority === "number" && Number.isFinite(current.priority)
|
typeof current.priority === "number" && Number.isFinite(current.priority)
|
||||||
? current.priority
|
? current.priority
|
||||||
@@ -485,6 +488,7 @@ async function submitEdit() {
|
|||||||
name: trimmedName,
|
name: trimmedName,
|
||||||
email: trimmedEmail,
|
email: trimmedEmail,
|
||||||
url: trimmedUrl || null,
|
url: trimmedUrl || null,
|
||||||
|
postUrl: trimmedPostUrl || null,
|
||||||
postSlug: newPostSlug,
|
postSlug: newPostSlug,
|
||||||
contentText: trimmedContent,
|
contentText: trimmedContent,
|
||||||
status: current.status,
|
status: current.status,
|
||||||
@@ -497,6 +501,7 @@ async function submitEdit() {
|
|||||||
email: trimmedEmail,
|
email: trimmedEmail,
|
||||||
url: trimmedUrl || null,
|
url: trimmedUrl || null,
|
||||||
postSlug: newPostSlug,
|
postSlug: newPostSlug,
|
||||||
|
postUrl: trimmedPostUrl || null,
|
||||||
contentText: trimmedContent,
|
contentText: trimmedContent,
|
||||||
status: current.status,
|
status: current.status,
|
||||||
priority: priorityValue,
|
priority: priorityValue,
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ export const getDomains = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
|
|
||||||
const { results: commentRows } = await c.env.CWD_DB.prepare(
|
const { results: commentRows } = await c.env.CWD_DB.prepare(
|
||||||
'SELECT post_slug, url FROM Comment'
|
'SELECT post_slug, post_url FROM Comment'
|
||||||
).all<{
|
).all<{
|
||||||
post_slug: string;
|
post_slug: string;
|
||||||
url: string | null;
|
post_url: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
for (const row of commentRows) {
|
for (const row of commentRows) {
|
||||||
const domain =
|
const domain =
|
||||||
extractDomain(row.post_slug) || extractDomain(row.url);
|
extractDomain(row.post_url) || extractDomain(row.post_slug);
|
||||||
if (domain) {
|
if (domain) {
|
||||||
domains.add(domain);
|
domains.add(domain);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ export const getStats = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
const domainFilter = rawDomain.trim().toLowerCase();
|
const domainFilter = rawDomain.trim().toLowerCase();
|
||||||
|
|
||||||
const { results } = await c.env.CWD_DB.prepare(
|
const { results } = await c.env.CWD_DB.prepare(
|
||||||
'SELECT created, post_slug, url, status FROM Comment'
|
'SELECT created, post_slug, post_url, status FROM Comment'
|
||||||
).all<{
|
).all<{
|
||||||
created: number;
|
created: number;
|
||||||
post_slug: string;
|
post_slug: string;
|
||||||
url: string | null;
|
post_url: string | null;
|
||||||
status: string;
|
status: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export const getStats = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
|
|
||||||
for (const row of results) {
|
for (const row of results) {
|
||||||
const domain =
|
const domain =
|
||||||
extractDomain(row.post_slug) || extractDomain(row.url) || 'unknown';
|
extractDomain(row.post_url) || extractDomain(row.post_slug) || 'unknown';
|
||||||
|
|
||||||
let counts = domainMap.get(domain);
|
let counts = domainMap.get(domain);
|
||||||
if (!counts) {
|
if (!counts) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
const params: (string | number)[] = [];
|
const params: (string | number)[] = [];
|
||||||
if (domain) {
|
if (domain) {
|
||||||
const pattern = `%://${domain}/%`;
|
const pattern = `%://${domain}/%`;
|
||||||
whereSql = 'WHERE post_slug LIKE ? OR url LIKE ?';
|
whereSql = 'WHERE post_slug LIKE ? OR post_url LIKE ?';
|
||||||
params.push(pattern, pattern);
|
params.push(pattern, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,16 @@ export const updateComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = await c.env.CWD_DB.prepare(
|
const existing = await c.env.CWD_DB.prepare(
|
||||||
'SELECT id, status, post_slug, priority FROM Comment WHERE id = ?'
|
'SELECT id, status, post_slug, post_url, priority FROM Comment WHERE id = ?'
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(id)
|
||||||
.first<{ id: number; status: string; post_slug: string; priority: number | null }>();
|
.first<{
|
||||||
|
id: number;
|
||||||
|
status: string;
|
||||||
|
post_slug: string;
|
||||||
|
post_url: string | null;
|
||||||
|
priority: number | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
return c.json({ message: 'Comment not found' }, 404);
|
return c.json({ message: 'Comment not found' }, 404);
|
||||||
@@ -48,6 +54,15 @@ export const updateComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
: typeof body.post_slug === 'string'
|
: typeof body.post_slug === 'string'
|
||||||
? body.post_slug
|
? body.post_slug
|
||||||
: '';
|
: '';
|
||||||
|
const hasPostUrlField =
|
||||||
|
Object.prototype.hasOwnProperty.call(body, 'postUrl') ||
|
||||||
|
Object.prototype.hasOwnProperty.call(body, 'post_url');
|
||||||
|
const rawPostUrl =
|
||||||
|
typeof body.postUrl === 'string'
|
||||||
|
? body.postUrl
|
||||||
|
: typeof body.post_url === 'string'
|
||||||
|
? body.post_url
|
||||||
|
: '';
|
||||||
|
|
||||||
const contentSource =
|
const contentSource =
|
||||||
typeof body.content === 'string'
|
typeof body.content === 'string'
|
||||||
@@ -63,6 +78,9 @@ export const updateComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
const postSlug = hasPostSlugField
|
const postSlug = hasPostSlugField
|
||||||
? (rawPostSlug.trim() || existing.post_slug)
|
? (rawPostSlug.trim() || existing.post_slug)
|
||||||
: existing.post_slug;
|
: existing.post_slug;
|
||||||
|
const postUrl = hasPostUrlField
|
||||||
|
? (rawPostUrl.trim() || null)
|
||||||
|
: existing.post_url;
|
||||||
|
|
||||||
let priority: number = typeof existing.priority === 'number' && Number.isFinite(existing.priority)
|
let priority: number = typeof existing.priority === 'number' && Number.isFinite(existing.priority)
|
||||||
? existing.priority
|
? existing.priority
|
||||||
@@ -107,9 +125,9 @@ export const updateComment = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { success } = await c.env.CWD_DB.prepare(
|
const { success } = await c.env.CWD_DB.prepare(
|
||||||
'UPDATE Comment SET name = ?, email = ?, url = ?, content_text = ?, content_html = ?, status = ?, post_slug = ?, priority = ? WHERE id = ?'
|
'UPDATE Comment SET name = ?, email = ?, url = ?, content_text = ?, content_html = ?, status = ?, post_slug = ?, post_url = ?, priority = ? WHERE id = ?'
|
||||||
)
|
)
|
||||||
.bind(name, email, url, contentText, contentHtml, status, postSlug, priority, id)
|
.bind(name, email, url, contentText, contentHtml, status, postSlug, postUrl, priority, id)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
@@ -33,28 +33,39 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let query = `
|
const equalSlugs = Array.from(new Set(slugList))
|
||||||
SELECT id, name, email, url, content_text as contentText,
|
const likePatternsSet = new Set<string>()
|
||||||
content_html as contentHtml, created, parent_id as parentId,
|
for (const s of equalSlugs) {
|
||||||
post_slug as postSlug, post_url as postUrl, priority, COALESCE(likes, 0) as likes
|
likePatternsSet.add(`${s}#%`)
|
||||||
FROM Comment
|
likePatternsSet.add(`${s}?%`)
|
||||||
WHERE status = "approved" AND post_slug = ?
|
|
||||||
ORDER BY priority DESC, created DESC
|
|
||||||
`
|
|
||||||
if (slugList.length > 1) {
|
|
||||||
const placeholders = slugList.map(() => '?').join(', ')
|
|
||||||
query = `
|
|
||||||
SELECT id, name, email, url, content_text as contentText,
|
|
||||||
content_html as contentHtml, created, parent_id as parentId,
|
|
||||||
post_slug as postSlug, post_url as postUrl, priority, COALESCE(likes, 0) as likes
|
|
||||||
FROM Comment
|
|
||||||
WHERE status = "approved" AND post_slug IN (${placeholders})
|
|
||||||
ORDER BY priority DESC, created DESC
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
const likePatterns = Array.from(likePatternsSet)
|
||||||
|
const whereParts: string[] = []
|
||||||
|
if (equalSlugs.length === 1) {
|
||||||
|
whereParts.push('post_slug = ?')
|
||||||
|
} else if (equalSlugs.length > 1) {
|
||||||
|
const placeholders = equalSlugs.map(() => '?').join(', ')
|
||||||
|
whereParts.push(`post_slug IN (${placeholders})`)
|
||||||
|
}
|
||||||
|
for (let i = 0; i < likePatterns.length; i += 1) {
|
||||||
|
whereParts.push('post_slug LIKE ?')
|
||||||
|
}
|
||||||
|
const whereClause =
|
||||||
|
whereParts.length > 0
|
||||||
|
? `status = "approved" AND (${whereParts.join(' OR ')})`
|
||||||
|
: 'status = "approved"'
|
||||||
|
const query = `
|
||||||
|
SELECT id, name, email, url, content_text as contentText,
|
||||||
|
content_html as contentHtml, created, parent_id as parentId,
|
||||||
|
post_slug as postSlug, post_url as postUrl, priority, COALESCE(likes, 0) as likes
|
||||||
|
FROM Comment
|
||||||
|
WHERE ${whereClause}
|
||||||
|
ORDER BY priority DESC, created DESC
|
||||||
|
`
|
||||||
|
const bindParams: unknown[] = [...equalSlugs, ...likePatterns]
|
||||||
|
|
||||||
const [commentsResult, adminEmailRows] = await Promise.all([
|
const [commentsResult, adminEmailRows] = await Promise.all([
|
||||||
c.env.CWD_DB.prepare(query).bind(...slugList).all(),
|
c.env.CWD_DB.prepare(query).bind(...bindParams).all(),
|
||||||
c.env.CWD_DB.prepare('SELECT key, value FROM Settings WHERE key IN (?, ?)')
|
c.env.CWD_DB.prepare('SELECT key, value FROM Settings WHERE key IN (?, ?)')
|
||||||
.bind('comment_admin_email', 'admin_notify_email')
|
.bind('comment_admin_email', 'admin_notify_email')
|
||||||
.all<{ key: string; value: string }>()
|
.all<{ key: string; value: string }>()
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ GET /admin/comments/list
|
|||||||
"created": 1736762400000,
|
"created": 1736762400000,
|
||||||
"name": "张三",
|
"name": "张三",
|
||||||
"email": "zhangsan@example.com",
|
"email": "zhangsan@example.com",
|
||||||
"postSlug": "https://your-blog.example.com/blog/hello-world",
|
"postSlug": "hello-world",
|
||||||
|
"postUrl": "https://your-blog.example.com/blog/hello-world",
|
||||||
"url": "https://zhangsan.me",
|
"url": "https://zhangsan.me",
|
||||||
"ipAddress": "127.0.0.1",
|
"ipAddress": "127.0.0.1",
|
||||||
"contentText": "很棒的文章!",
|
"contentText": "很棒的文章!",
|
||||||
@@ -63,6 +64,25 @@ GET /admin/comments/list
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
返回字段说明:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| ----------- | ------ | -------------------------- |
|
||||||
|
| `id` | number | 评论 ID |
|
||||||
|
| `created` | number | 创建时间戳 |
|
||||||
|
| `name` | string | 评论者昵称 |
|
||||||
|
| `email` | string | 评论者邮箱 |
|
||||||
|
| `postSlug` | string | 文章 slug |
|
||||||
|
| `postUrl` | string | null) | 文章完整 URL |
|
||||||
|
| `url` | string | null) | 评论者个人主页地址 |
|
||||||
|
| `ipAddress` | string | 评论者 IP 地址 |
|
||||||
|
| `contentText` | string | 评论内容(纯文本) |
|
||||||
|
| `contentHtml` | string | 评论内容(渲染后的 HTML) |
|
||||||
|
| `status` | string | 评论状态 |
|
||||||
|
| `priority` | number | 置顶权重 |
|
||||||
|
| `ua` | string | 用户代理 |
|
||||||
|
| `avatar` | string | 头像 URL(Gravatar) |
|
||||||
|
|
||||||
**鉴权错误**
|
**鉴权错误**
|
||||||
|
|
||||||
- 未携带 Token 或 Token 失效:
|
- 未携带 Token 或 Token 失效:
|
||||||
@@ -156,7 +176,8 @@ PUT /admin/comments/update
|
|||||||
"name": "张三",
|
"name": "张三",
|
||||||
"email": "zhangsan@example.com",
|
"email": "zhangsan@example.com",
|
||||||
"url": "https://zhangsan.me",
|
"url": "https://zhangsan.me",
|
||||||
"postSlug": "https://example.com/blog/hello-world",
|
"postSlug": "hello-world",
|
||||||
|
"postUrl": "https://example.com/blog/hello-world",
|
||||||
"contentText": "更新后的评论内容",
|
"contentText": "更新后的评论内容",
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"priority": 2
|
"priority": 2
|
||||||
@@ -171,10 +192,12 @@ PUT /admin/comments/update
|
|||||||
| `name` | string | 是 | 评论者昵称 |
|
| `name` | string | 是 | 评论者昵称 |
|
||||||
| `email` | string | 是 | 评论者邮箱 |
|
| `email` | string | 是 | 评论者邮箱 |
|
||||||
| `url` | string | 否 | 评论者个人主页或站点地址 |
|
| `url` | string | 否 | 评论者个人主页或站点地址 |
|
||||||
| `postSlug` | string | 否 | 评论地址(文章 URL),不传则保持原值 |
|
| `postSlug` | string | 否 | 文章 slug,不传则保持原值,支持 `postSlug` 或 `post_slug` |
|
||||||
| `contentText` | string | 是 | 评论内容(纯文本) |
|
| `postUrl` | string | 否 | 文章完整 URL,不传则保持原值,支持 `postUrl` 或 `post_url` |
|
||||||
`| `status` | string | 否 | 评论状态,可选 `approved`、`pending`、`rejected` |
|
| `content` | string | 是* | 评论内容(Markdown 或纯文本),与 `contentText` 二选一 |
|
||||||
| `priority` | number | 否 | 置顶权重,默认为 1,数值越大越靠前 |
|
| `contentText` | string | 是* | 评论内容(纯文本),与 `content` 二选一 |
|
||||||
|
| `status` | string | 否 | 评论状态,可选 `approved`、`pending`、`rejected` |
|
||||||
|
| `priority` | number | 否 | 置顶权重,默认为 1,数值越大越靠前,必须 >= 1 |
|
||||||
|
|
||||||
**置顶权重说明**:
|
**置顶权重说明**:
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ GET /api/comments
|
|||||||
**查询参数**
|
**查询参数**
|
||||||
|
|
||||||
| 名称 | 位置 | 类型 | 必填 | 说明 |
|
| 名称 | 位置 | 类型 | 必填 | 说明 |
|
||||||
| ----------- | ----- | ------- | ---- | -------------------------------------------------------------------------------------------- |
|
| ----------- | ----- | ------- | ---- | ------------------------------------------ |
|
||||||
| `post_slug` | query | string | 是 | 使用 `window.location.origin + window`location.pathname`,获取带域名的链接,否则后台无法识别 |
|
| `post_slug` | query | string | 是 | 文章 slug,与前端 `CWDComments` 的 `postSlug` 参数对应 |
|
||||||
| `page` | query | integer | 否 | 页码,默认 `1` |
|
| `page` | query | integer | 否 | 页码,默认 `1` |
|
||||||
| `limit` | query | integer | 否 | 每页数量,默认 `20`,最大 `50` |
|
| `limit` | query | integer | 否 | 每页数量,默认 `20`,最大 `50` |
|
||||||
| `nested` | query | string | 否 | 是否返回嵌套结构,默认 `'true'` |
|
| `nested` | query | string | 否 | 是否返回嵌套结构,默认 `'true'` |
|
||||||
@@ -70,6 +70,25 @@ GET /api/comments
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
返回字段说明:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| ----------- | ------ | -------------------------- |
|
||||||
|
| `id` | number | 评论 ID |
|
||||||
|
| `author` | string | 评论者昵称 |
|
||||||
|
| `email` | string | 评论者邮箱(hash后) |
|
||||||
|
| `url` | string | null) | 评论者个人主页地址 |
|
||||||
|
| `contentText` | string | 评论内容(纯文本) |
|
||||||
|
| `contentHtml` | string | 评论内容(渲染后的 HTML) |
|
||||||
|
| `pubDate` | string | 发布时间(ISO 8601 格式) |
|
||||||
|
| `postSlug` | string | 文章 slug |
|
||||||
|
| `avatar` | string | 头像 URL(Gravatar) |
|
||||||
|
| `priority` | number | 置顶权重,数值越大排序越靠前 |
|
||||||
|
| `likes` | number | 点赞数,默认为 0 |
|
||||||
|
| `replies` | array | 子评论列表(嵌套结构时) |
|
||||||
|
| `parentId` | number | null) | 父评论 ID(回复时) |
|
||||||
|
| `replyToAuthor` | string( | null) | 被回复者的昵称(回复时) |
|
||||||
|
|
||||||
说明:
|
说明:
|
||||||
|
|
||||||
- 当 `nested=true`(默认)时,接口返回的是"根评论列表",每条根评论包含其 `replies`。
|
- 当 `nested=true`(默认)时,接口返回的是"根评论列表",每条根评论包含其 `replies`。
|
||||||
@@ -121,7 +140,7 @@ POST /api/comments
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"post_slug": "https://example.com/blog/hello-world",
|
"post_slug": "hello-world",
|
||||||
"post_title": "博客标题,可选",
|
"post_title": "博客标题,可选",
|
||||||
"post_url": "https://example.com/blog/hello-world",
|
"post_url": "https://example.com/blog/hello-world",
|
||||||
"name": "张三",
|
"name": "张三",
|
||||||
@@ -136,12 +155,12 @@ POST /api/comments
|
|||||||
字段说明:
|
字段说明:
|
||||||
|
|
||||||
| 字段名 | 类型 | 必填 | 说明 |
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
| ------------ | ------ | ---- | ------------------------------------------------------------------------------------------------------------- |
|
| ------------ | ------ | ---- | -------------------------------------------------------------------- |
|
||||||
| `post_slug` | string | 是 | 文章唯一标识符,应与前端组件初始化时的 `postSlug` 值一致,`window.location.origin + window.location.pathname` |
|
| `post_slug` | string | 是 | 文章 slug,应与前端组件初始化时的 `postSlug` 值一致,如 `hello-world` |
|
||||||
| `post_title` | string | 否 | 文章标题,用于邮件通知内容 |
|
| `post_title` | string | 否 | 文章标题,用于邮件通知内容 |
|
||||||
| `post_url` | string | 否 | 文章 URL,用于邮件通知中的跳转链接 |
|
| `post_url` | string | 否 | 文章完整 URL,用于邮件通知中的跳转链接 |
|
||||||
| `name` | string | 是 | 评论者昵称 |
|
| `name` | string | 是 | 评论者昵称 |
|
||||||
| `email` | string | 是 | 评论者邮箱,需为合法邮箱格式 |
|
| `email` | string | | 是 | 评论者邮箱,需为合法邮箱格式 |
|
||||||
| `url` | string | 否 | 评论者个人主页或站点地址 |
|
| `url` | string | 否 | 评论者个人主页或站点地址 |
|
||||||
| `content` | string | 是 | 评论内容,内部会过滤 `<script>...</script>` 片段 |
|
| `content` | string | 是 | 评论内容,内部会过滤 `<script>...</script>` 片段 |
|
||||||
| `parent_id` | number | 否 | 父评论 ID,用于回复功能;缺省或 `null` 表示根评论 |
|
| `parent_id` | number | 否 | 父评论 ID,用于回复功能;缺省或 `null` 表示根评论 |
|
||||||
|
|||||||
Reference in New Issue
Block a user