fix: 更新文档链接和说明,调整评论管理界面样式
This commit is contained in:
@@ -5,15 +5,17 @@
|
||||
|
||||
Cloudflare Worker 版本基于 Cloudflare Workers + D1 + KV 实现,无需服务器即可部署运行的评论组件。
|
||||
|
||||
[文档地址](https://cwd-comments.zishu.me)
|
||||
[文档地址](https://cwd-comments-docs.zishu.me)
|
||||
|
||||
## 特性
|
||||
|
||||
- ⚡️ **极速响应**:基于 Cloudflare 全球边缘网络
|
||||
- 🔒 **安全可靠**:内置管理员认证、CORS 保护
|
||||
- 📧 **邮件通知**:基于 Cloudflare Workers 发送邮件
|
||||
- 🎨 **易于集成**:提供完整的 REST API
|
||||
|
||||
**开发中:**
|
||||
- [ ]📧 **邮件通知**:基于 Cloudflare Workers 发送邮件
|
||||
|
||||
## 前置要求
|
||||
|
||||
- Node.js 16+
|
||||
|
||||
@@ -17,63 +17,63 @@
|
||||
<div v-if="loading" class="page-hint">加载中...</div>
|
||||
<div v-else-if="error" class="page-error">{{ error }}</div>
|
||||
<div v-else>
|
||||
<div class="comment-list">
|
||||
<div
|
||||
v-for="item in filteredComments"
|
||||
:key="item.id"
|
||||
class="comment-card"
|
||||
>
|
||||
<div class="comment-card-header">
|
||||
<div class="comment-author">
|
||||
<div class="comment-author-name">
|
||||
{{ item.author }}
|
||||
</div>
|
||||
<div class="comment-author-email">
|
||||
{{ item.email }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-meta">
|
||||
<span class="comment-path">{{ item.postSlug }}</span>
|
||||
<span class="comment-time">{{ formatDate(item.pubDate) }}</span>
|
||||
</div>
|
||||
<div class="comment-table">
|
||||
<div class="table-header">
|
||||
<div class="table-cell table-cell-id">ID</div>
|
||||
<div class="table-cell table-cell-author">作者</div>
|
||||
<div class="table-cell table-cell-content">内容</div>
|
||||
<div class="table-cell table-cell-path">路径</div>
|
||||
<div class="table-cell table-cell-time">时间</div>
|
||||
<div class="table-cell table-cell-status">状态</div>
|
||||
<div class="table-cell table-cell-actions">操作</div>
|
||||
</div>
|
||||
<div v-for="item in filteredComments" :key="item.id" class="table-row">
|
||||
<div class="table-cell table-cell-id">
|
||||
<span class="cell-id">#{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
{{ item.contentText }}
|
||||
<div class="table-cell table-cell-author">
|
||||
<div class="cell-author-name">{{ item.author }}</div>
|
||||
<div class="cell-author-email">{{ item.email }}</div>
|
||||
</div>
|
||||
<div class="comment-footer">
|
||||
<div class="comment-info">
|
||||
<span class="comment-id">#{{ item.id }}</span>
|
||||
<span
|
||||
class="comment-status"
|
||||
:class="`comment-status-${item.status}`"
|
||||
>
|
||||
{{ formatStatus(item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="comment-actions">
|
||||
<div class="table-cell table-cell-content">
|
||||
<div class="cell-content-text">{{ item.contentText }}</div>
|
||||
</div>
|
||||
<div class="table-cell table-cell-path">
|
||||
<span class="cell-path" :title="item.postSlug">{{ item.postSlug }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-cell-time">
|
||||
<span class="cell-time">{{ formatDate(item.pubDate) }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-cell-status">
|
||||
<span class="cell-status" :class="`cell-status-${item.status}`">
|
||||
{{ formatStatus(item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-cell table-cell-actions">
|
||||
<div class="table-actions">
|
||||
<button
|
||||
class="comment-action"
|
||||
class="table-action"
|
||||
@click="changeStatus(item, 'approved')"
|
||||
:disabled="item.status === 'approved'"
|
||||
>
|
||||
通过
|
||||
</button>
|
||||
<button
|
||||
class="comment-action"
|
||||
class="table-action"
|
||||
@click="changeStatus(item, 'pending')"
|
||||
:disabled="item.status === 'pending'"
|
||||
>
|
||||
待审
|
||||
</button>
|
||||
<button
|
||||
class="comment-action"
|
||||
class="table-action"
|
||||
@click="changeStatus(item, 'rejected')"
|
||||
:disabled="item.status === 'rejected'"
|
||||
>
|
||||
拒绝
|
||||
</button>
|
||||
<button
|
||||
class="comment-action comment-action-danger"
|
||||
class="table-action table-action-danger"
|
||||
@click="removeComment(item)"
|
||||
>
|
||||
删除
|
||||
@@ -81,10 +81,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="filteredComments.length === 0"
|
||||
class="page-hint comment-empty"
|
||||
>
|
||||
<div v-if="filteredComments.length === 0" class="table-empty">
|
||||
暂无数据
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,7 +217,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toolbar-left {
|
||||
@@ -236,6 +233,9 @@ onMounted(() => {
|
||||
.toolbar-select {
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
@@ -257,182 +257,192 @@ onMounted(() => {
|
||||
color: #d1242f;
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.comment-card {
|
||||
.comment-table {
|
||||
background-color: #ffffff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d0d7de;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
background-color: #f6f8fa;
|
||||
border-bottom: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #eaeae0;
|
||||
}
|
||||
|
||||
.table-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding: 10px 12px;
|
||||
box-shadow: 0 1px 0 rgba(27, 31, 36, 0.04);
|
||||
}
|
||||
|
||||
.comment-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.comment-author-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #24292f;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comment-author-email {
|
||||
.table-cell-id {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table-cell-author {
|
||||
width: 180px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-cell-content {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.table-cell-path {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-cell-time {
|
||||
width: 150px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.table-cell-status {
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-cell-actions {
|
||||
width: 240px;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-header .table-cell {
|
||||
font-weight: 500;
|
||||
color: #57606a;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cell-id {
|
||||
font-size: 12px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
.cell-author-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.cell-author-email {
|
||||
font-size: 11px;
|
||||
color: #57606a;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.comment-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: #57606a;
|
||||
.cell-content-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.comment-path {
|
||||
max-width: 220px;
|
||||
.cell-path {
|
||||
font-size: 12px;
|
||||
color: #57606a;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #24292f;
|
||||
margin-bottom: 8px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.comment-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.comment-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
.cell-time {
|
||||
font-size: 12px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
.comment-id {
|
||||
padding: 2px 6px;
|
||||
.cell-status {
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
background-color: #f6f8fa;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.comment-status {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.comment-status-approved {
|
||||
.cell-status-approved {
|
||||
color: #1a7f37;
|
||||
background-color: #e7f5eb;
|
||||
}
|
||||
|
||||
.comment-status-pending {
|
||||
.cell-status-pending {
|
||||
color: #9a6700;
|
||||
background-color: #fff8c5;
|
||||
}
|
||||
|
||||
.comment-status-rejected {
|
||||
.cell-status-rejected {
|
||||
color: #d1242f;
|
||||
background-color: #ffebe9;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
.table-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.comment-action {
|
||||
padding: 4px 8px;
|
||||
.table-action {
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d0d7de;
|
||||
background-color: #f6f8fa;
|
||||
font-size: 12px;
|
||||
background-color: #ffffff;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.comment-action-danger {
|
||||
.table-action:hover {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.table-action:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.table-action-danger {
|
||||
border-color: #d1242f;
|
||||
color: #d1242f;
|
||||
}
|
||||
|
||||
.comment-action:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
.table-action-danger:hover {
|
||||
background-color: #ffebe9;
|
||||
}
|
||||
|
||||
.comment-empty {
|
||||
margin-top: 8px;
|
||||
.table-empty {
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #f0f0f0;
|
||||
border-top-color: #0969da;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #57606a;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 8px;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@@ -447,7 +457,13 @@ onMounted(() => {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination-button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-size: 13px;
|
||||
color: #57606a;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,130 +1,149 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<header class="layout-header">
|
||||
<div class="layout-title">CWD 评论后台</div>
|
||||
<div class="layout-actions">
|
||||
<button class="layout-button" @click="goSettings">设置</button>
|
||||
<button class="layout-button" @click="handleLogout">退出</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="layout-body">
|
||||
<nav class="layout-sider">
|
||||
<ul class="menu">
|
||||
<li class="menu-item" :class="{ active: isRouteActive('comments') }" @click="goComments">评论管理</li>
|
||||
<li class="menu-item" :class="{ active: isRouteActive('settings') }" @click="goSettings">系统设置</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="layout-content">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<header class="layout-header">
|
||||
<div class="layout-title">CWD 评论后台</div>
|
||||
<div class="layout-actions">
|
||||
<a
|
||||
class="layout-button"
|
||||
href="https://github.com/anghunk/cwd-comments"
|
||||
target="_blank"
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
<button class="layout-button" @click="goSettings">设置</button>
|
||||
<button class="layout-button" @click="handleLogout">退出</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="layout-body">
|
||||
<nav class="layout-sider">
|
||||
<ul class="menu">
|
||||
<li
|
||||
class="menu-item"
|
||||
:class="{ active: isRouteActive('comments') }"
|
||||
@click="goComments"
|
||||
>
|
||||
评论管理
|
||||
</li>
|
||||
<li
|
||||
class="menu-item"
|
||||
:class="{ active: isRouteActive('settings') }"
|
||||
@click="goSettings"
|
||||
>
|
||||
系统设置
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main class="layout-content">
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { logoutAdmin } from '../api/admin';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { logoutAdmin } from "../api/admin";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
function isRouteActive(name: string) {
|
||||
return route.name === name;
|
||||
return route.name === name;
|
||||
}
|
||||
|
||||
function goComments() {
|
||||
router.push({ name: 'comments' });
|
||||
router.push({ name: "comments" });
|
||||
}
|
||||
|
||||
function goSettings() {
|
||||
router.push({ name: 'settings' });
|
||||
router.push({ name: "settings" });
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
logoutAdmin();
|
||||
router.push({ name: 'login' });
|
||||
logoutAdmin();
|
||||
router.push({ name: "login" });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
background-color: #24292f;
|
||||
color: #ffffff;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
background-color: #24292f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.layout-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.layout-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.layout-button {
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #57606a;
|
||||
background-color: #24292f;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #57606a;
|
||||
background-color: #24292f;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.layout-button:hover {
|
||||
background-color: #32383f;
|
||||
background-color: #32383f;
|
||||
}
|
||||
|
||||
.layout-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.layout-sider {
|
||||
width: 180px;
|
||||
background-color: #f6f8fa;
|
||||
border-right: 1px solid #d0d7de;
|
||||
width: 180px;
|
||||
background-color: #f6f8fa;
|
||||
border-right: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 12px 0;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #24292f;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #24292f;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: #eaeef2;
|
||||
background-color: #eaeef2;
|
||||
}
|
||||
|
||||
.menu-item.active {
|
||||
background-color: #d0ebff;
|
||||
font-weight: 600;
|
||||
background-color: #d0ebff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
flex: 1;
|
||||
padding: 16px 20px;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
padding: 16px 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"name": "cwd-comments",
|
||||
"name": "cwd-comments-api",
|
||||
"main": "src/index.ts",
|
||||
"compatibility_date": "2026-01-03",
|
||||
"workers_dev": true,
|
||||
|
||||
@@ -34,12 +34,16 @@ npm install
|
||||
```bash
|
||||
npx wrangler login
|
||||
```
|
||||
|
||||
* **创建数据库和数据库表**,如果遇到提示,请按回车继续
|
||||
|
||||
```bash
|
||||
npx wrangler d1 create CWD_DB
|
||||
npx wrangler d1 execute CWD_DB --remote --file=./schemas/comment.sql
|
||||
```
|
||||
|
||||
运行完成后可以确认一下 `wrangler.jsonc` 中是否有如下配置
|
||||
|
||||
```jsonc
|
||||
"d1_databases": [
|
||||
{
|
||||
@@ -49,13 +53,17 @@ npm install
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
如果`binding`字段不是`CWD_DB`,请修改为`CWD_DB`
|
||||
|
||||
* **创建 KV 存储**,如果遇到提示,按回车继续
|
||||
|
||||
```bash
|
||||
npx wrangler kv namespace create CWD_AUTH_KV
|
||||
```
|
||||
|
||||
运行完成后可以确认一下 `wrangler.jsonc` 中是否有如下配置
|
||||
|
||||
```jsonc
|
||||
"kv_namespaces": [
|
||||
{
|
||||
@@ -64,12 +72,14 @@ npm install
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
* **部署上线**
|
||||
|
||||
```bash
|
||||
npm run deploy
|
||||
```
|
||||
|
||||
没有异常报错后,可以进入Cloudflare Workers 面板查看是否部署成功,若显示存在一个名称为 `cwd-backend-worker` 的项目即推送成功。
|
||||
没有异常报错后,可以进入 Cloudflare Workers 面板查看是否部署成功,若显示存在一个名称为 `cwd-comments-api` 的项目即推送成功。
|
||||
|
||||
#### 3. 配置环境变量
|
||||
|
||||
@@ -79,30 +89,36 @@ npm install
|
||||
|
||||
#### 4. 检测部署情况
|
||||
|
||||
部署成功后回得到一个域名,即为后端的域名(格式一般为`https://cwd-backend-worker.xxx.workers.dev`。访问该域名,如果显示后端管理页面并可以正常登录则部署成功,将此域名填写到博客的配置文件中即可使用评论功能。
|
||||
部署成功后回得到一个域名,即为后端的域名(格式一般为`https://cwd-comments-api.xxx.workers.dev`。访问该域名,如果显示部署成功页面,说明 API 部署成功,可以到管理后台进行登录。
|
||||
|
||||
当然也可以使用自定义域名,注意不要使用三级域名,即`*.*.example.com`。
|
||||
当然也可以使用自定义域名。
|
||||
|
||||
## 环境变量
|
||||
|
||||
所需环境变量如下表所示,请参考源码中 `.dev.vars.example` 文件
|
||||
所需环境变量如下表所示。
|
||||
|
||||
| 变量名 | 描述 |
|
||||
| -------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `CF_FROM_EMAIL` | 作为发件人显示的邮箱地址(需在 Cloudflare Email 路由中预先配置) |
|
||||
| `EMAIL_ADDRESS` | 管理员接收通知邮件的默认邮箱(可在后台设置中覆盖) |
|
||||
| `ADMIN_NAME` | 管理员登录名称,默认为 admin |
|
||||
| `ADMIN_PASSWORD` | 管理员登录密码,默认密码为 password |
|
||||
| 变量名 | 描述 |
|
||||
| ---------------- | ---------------------------------------------------------------- |
|
||||
| `ADMIN_NAME` | 管理员登录名称 |
|
||||
| `ADMIN_PASSWORD` | 管理员登录密码 |
|
||||
| `CF_FROM_EMAIL` | 作为发件人显示的邮箱地址(需在 Cloudflare Email 路由中预先配置) |
|
||||
| `EMAIL_ADDRESS` | 管理员接收通知邮件的默认邮箱(可在后台设置中覆盖) |
|
||||
|
||||
**注:** 需要在 Cloudflare 控制面板中为 Email 路由开启发送权限并配置发件人域和地址,并在 `wrangler.jsonc` 中为 Worker 添加 `send_email` 绑定,以便在代码中通过 `env.SEND_EMAIL.send()` 直接发信。
|
||||
**注:** 需要在 Cloudflare 控制面板中为 Email 路由开启发送权限并配置发件人域和地址,并在 `wrangler.jsonc` 中为 Worker 添加 `send_email` 绑定,以便在代码中通过 `env.SEND_EMAIL.send()` 直接发信。
|
||||
|
||||
## 发信设置
|
||||
|
||||
## 本地测试
|
||||
手动在 `wrangler.jsonc` 中添加 `send_email` 绑定,以便在代码中通过 `env.SEND_EMAIL.send()` 直接发信。
|
||||
|
||||
如果需要本地测试,环境变量可以使用 `.dev.vars` 文件来设置
|
||||
|
||||
```bash
|
||||
cp .dev.vars.example .dev.vars
|
||||
# 编辑 .dev.vars 文件
|
||||
npm run dev
|
||||
```jsonc
|
||||
{
|
||||
...
|
||||
"send_email": [
|
||||
{
|
||||
"name": "SEND_EMAIL",
|
||||
"destination_address": "hi@zishu.me"
|
||||
}
|
||||
],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<script>
|
||||
const comments = new CWDComments({
|
||||
el: '#comments', // 容器 id
|
||||
apiBaseUrl: 'https://your-api.example.com', // 你部署的api地址
|
||||
postSlug: 'my-post', // 当前页面路径,可使用博客程序支持的 url 模板路径
|
||||
apiBaseUrl: 'https://your-api.example.com', // 你部署的 api 地址
|
||||
postSlug: 'https://example.com/my-post', // 当前页面路径,可使用博客程序支持的 url 模板路径,或者直接使用 window.location.origin
|
||||
});
|
||||
comments.mount();
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
# 快速开始
|
||||
> [!WARNING]
|
||||
> 目前仍处于 Beta 测试阶段,欢迎反馈测试结果。
|
||||
|
||||
## 简介
|
||||
# cwd-comments
|
||||
|
||||
CWD 评论系统是一个基于 Cloudflare Workers 的轻量级评论解决方案,使用 Hono 框架构建,数据存储使用 Cloudflare D1(SQLite)和 KV。
|
||||
Cloudflare Worker 版本基于 Cloudflare Workers + D1 + KV 实现,无需服务器即可部署运行的评论组件。
|
||||
|
||||
[文档地址](https://cwd-comments-docs.zishu.me)
|
||||
|
||||
## 特性
|
||||
|
||||
- ⚡️ **极速响应**:基于 Cloudflare 全球边缘网络
|
||||
- 🔒 **安全可靠**:内置管理员认证、CORS 保护
|
||||
- 📧 **邮件通知**:支持 Resend 邮件服务
|
||||
- 🎨 **易于集成**:提供完整的 REST API
|
||||
|
||||
**开发中:**
|
||||
- [ ]📧 **邮件通知**:基于 Cloudflare Workers 发送邮件
|
||||
|
||||
## 前置要求
|
||||
|
||||
- Node.js 16+
|
||||
@@ -22,13 +27,18 @@ CWD 评论系统是一个基于 Cloudflare Workers 的轻量级评论解决方
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone https://github.com/anghunk/cwd-comments
|
||||
cd cwd-comments
|
||||
|
||||
# 安装依赖
|
||||
# API 项目
|
||||
cd cwd-comments-api
|
||||
# 部署请查看文档
|
||||
|
||||
# 前端项目
|
||||
cd cwd-comments-web
|
||||
npm install
|
||||
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
- [后端配置](./backend-config.md)
|
||||
- [前端配置](./frontend-config.md)
|
||||
- [后端配置](https://cwd-comments-docs.zishu.me/guide/backend-config.html)
|
||||
- [前端配置](https://cwd-comments-docs.zishu.me/guide/frontend-config.html)
|
||||
|
||||
Reference in New Issue
Block a user