feat: 改进评论系统管理员识别与样式展示

- 后端评论接口增加 isAdmin 字段,不再依赖前端邮箱比对
- 管理后台评论列表支持 Markdown 渲染并添加相应样式
- 前端组件移除 adminEmail 依赖,改用 comment.isAdmin 字段
- 添加 CSS 样式兼容其他框架导入的表情元素
- 更新文档说明部署流程
This commit is contained in:
anghunk
2026-01-27 15:13:51 +08:00
parent 16aee6a87e
commit 4095473a79
9 changed files with 169 additions and 13 deletions

View File

@@ -0,0 +1,127 @@
.cell-content-text {
font-size: 14px;
line-height: 1.6;
color: var(--cwd-text, #24292f);
word-wrap: break-word;
white-space: pre-wrap;
margin-top: 10px;
}
.cell-content-text p {
margin: 0 0 8px;
}
.cell-content-text p:last-child {
margin-bottom: 0;
}
.cell-content-text a {
color: var(--cwd-primary, #0969da);
text-decoration: none;
}
.cell-content-text a:hover {
text-decoration: underline;
}
.cell-content-text h1,
.cell-content-text h2,
.cell-content-text h3,
.cell-content-text h4,
.cell-content-text h5,
.cell-content-text h6 {
margin-top: 16px;
margin-bottom: 8px;
font-weight: 600;
line-height: 1.25;
}
.cell-content-text h1 {
font-size: 1.5em;
}
.cell-content-text h2 {
font-size: 1.3em;
}
.cell-content-text h3 {
font-size: 1.1em;
}
.cell-content-text ul,
.cell-content-text ol {
padding-left: 1.7em;
margin: 0 0 8px;
}
.cell-content-text li+li {
margin-top: 4px;
}
.cell-content-text code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.9em;
background: var(--cwd-bg-secondary, #f6f8fa);
padding: 0.2em 0.4em;
border-radius: 4px;
}
.cell-content-text pre {
padding: 12px;
margin: 12px 0;
overflow-x: auto;
background: var(--cwd-bg-secondary, #f6f8fa);
border-radius: 6px;
font-size: 0.9em;
}
.cell-content-text pre code {
padding: 0;
background: transparent;
border-radius: 0;
display: block;
}
.cell-content-text blockquote {
margin: 8px 0;
padding: 0 12px;
border-left: 4px solid var(--cwd-border, #d0d7de);
color: var(--cwd-text-secondary, #6e7781);
}
.cell-content-text hr {
border: 0;
border-top: 1px solid var(--cwd-border-light, #eaeef2);
margin: 16px 0;
}
.cell-content-text table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
font-size: 13px;
margin: 12px 0;
}
.cell-content-text th,
.cell-content-text td {
border: 1px solid var(--cwd-border, #d0d7de);
padding: 6px 12px;
text-align: left;
}
.cell-content-text th {
background: var(--cwd-bg-secondary, #f6f8fa);
}
.cell-content-text img {
max-width: 100%;
height: auto;
}
/* 兼容其他框架导入的样式 */
.cell-content-text .tk-owo-emotion {
display: inline-block;
vertical-align: bottom;
max-width: 40px;
}

View File

@@ -58,7 +58,7 @@
</div>
</div>
<div class="table-cell table-cell-content">
<div class="cell-content-text">{{ item.contentText }}</div>
<div class="cell-content-text" v-html="item.contentHtml"></div>
</div>
<div class="table-cell table-cell-path">
<a
@@ -81,9 +81,17 @@
>
置顶
</span>
<span class="cell-status cell-likes-number" v-if="item.likes !== 0"
<span class="cell-status cell-likes-number" v-if="item.likes !== 0">
<svg
style="width: 13px"
viewBox="0 0 24 24"
aria-hidden="true"
fill="currentColor"
>
<svg style="width:13px;" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M2 21h4V9H2v12zm20-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L13 1 7.59 6.41C7.22 6.78 7 7.3 7 7.83V19c0 1.1.9 2 2 2h8c.78 0 1.48-.45 1.82-1.11l3.02-7.05c.11-.23.16-.48.16-.74v-2z"></path></svg>
<path
d="M2 21h4V9H2v12zm20-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L13 1 7.59 6.41C7.22 6.78 7 7.3 7 7.83V19c0 1.1.9 2 2 2h8c.78 0 1.48-.45 1.82-1.11l3.02-7.05c.11-.23.16-.48.16-.74v-2z"
></path>
</svg>
{{
typeof item.likes === "number" &&
Number.isFinite(item.likes) &&
@@ -257,6 +265,7 @@
</template>
<script setup lang="ts">
import "../assets/markdown.css";
import { onMounted, ref, computed, watch, inject } from "vue";
import type { Ref } from "vue";
import { useRoute, useRouter } from "vue-router";

View File

@@ -52,12 +52,25 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
ORDER BY priority DESC, created DESC
`
}
const { results } = await c.env.CWD_DB.prepare(query).bind(...slugList).all()
// 并行获取评论和管理员邮箱
// 对 adminEmail 查询进行错误捕获,防止因 Settings 表不存在导致整个接口失败
const [commentsResult, adminEmailRow] = await Promise.all([
c.env.CWD_DB.prepare(query).bind(...slugList).all(),
c.env.CWD_DB.prepare('SELECT value FROM Settings WHERE key = ?')
.bind('admin_notify_email')
.first<{ value: string }>()
.catch(() => null)
]);
const results = commentsResult.results;
const adminEmail = adminEmailRow?.value || null;
// 2. 批量处理头像并格式化
const allComments = await Promise.all(results.map(async (row: any) => ({
...row,
avatar: await getCravatar(row.email, avatar_prefix || undefined),
isAdmin: adminEmail && row.email === adminEmail,
replies: []
})))

View File

@@ -1,6 +1,6 @@
# 更新部署
重新拉取 GitHub 项目代码
重新拉取 GitHub 项目代码
## 后端更新
@@ -16,6 +16,8 @@ npm run deploy
## 前端更新
如果你的管理后台项目,是通过 GitHub 仓库部署到 Cloudflare Pages不需要手动更新只用拉取官方仓库代码重新推送即可触发重新部署无需手动
### 管理后台
如果使用官方后台不需要更新。
@@ -35,4 +37,4 @@ npm run build
如果使用官方评论端 js `https://cwd.js.org/cwd.js`,不需要更新。
如果你想自托管,请下载仓库 `/cwd-admin/public`最新的 `cwd.js` 文件。
如果你想自托管,请使用打包命令,对仓库 `@docs/widget` 文件夹进行构建,然后把 `dist` 文件夹中的最新的 `cwd.js` 文件上传到你托管的地方

File diff suppressed because one or more lines are too long

View File

@@ -39,7 +39,8 @@ export class CommentItem extends Component {
const { comment, isReply, adminEmail, adminBadge } = this.props;
const isPinned = typeof comment.priority === 'number' && comment.priority > 1;
const isReplying = this.props.replyingTo === comment.id;
const isAdmin = adminEmail && comment.email === adminEmail;
// 使用后端返回的 isAdmin 字段,不再依赖前端邮箱比对
const isAdmin = !!comment.isAdmin;
const root = this.createElement('div', {
className: `cwd-comment-item ${isReply ? 'cwd-comment-reply' : ''}`,
@@ -241,7 +242,7 @@ export class CommentItem extends Component {
submitting: this.props.submitting,
currentUser: this.props.currentUser,
onUpdateUserInfo: this.props.onUpdateUserInfo,
adminEmail: this.props.adminEmail,
// adminEmail 已移除
adminBadge: this.props.adminBadge,
enableCommentLike: this.props.enableCommentLike,
onReply: this.props.onReply,

View File

@@ -97,7 +97,7 @@ export class CommentList extends Component {
submitting: this.props.submitting,
currentUser: this.props.currentUser,
onUpdateUserInfo: this.props.onUpdateUserInfo,
adminEmail: this.props.adminEmail,
// adminEmail 已移除,改用 comment.isAdmin 字段
adminBadge: this.props.adminBadge,
enableCommentLike: this.props.enableCommentLike,
onReply: (commentId) => this.handleReply(commentId),

View File

@@ -367,7 +367,7 @@ export class CWDComments {
submitting: state.submitting,
currentUser: state.form,
onUpdateUserInfo: (field, value) => this.store.updateFormField(field, value),
adminEmail: this.config.adminEmail,
// adminEmail 已移除,前端展示改用 isAdmin 字段
adminBadge: this.config.adminBadge,
enableCommentLike: this.config.enableCommentLike !== false,
onRetry: () => this.store.loadComments(),

View File

@@ -986,4 +986,11 @@
background: transparent;
padding: 0;
color: #db850d;
}
/* 兼容其他框架导入的样式 */
.cwd-comment-content .tk-owo-emotion {
display: inline-block;
vertical-align: bottom;
max-width: 40px;
}