From 3d0e3a317a0483d1bf951286e0f9a9dbddf593da Mon Sep 17 00:00:00 2001 From: anghunk Date: Mon, 19 Jan 2026 14:58:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9ECWD=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=89=8D=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=8F=8A=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 移除旧版评论系统代码并重构为Vue3前端 docs: 更新后端配置文档说明 fix: 修复评论提交频率限制和邮件通知逻辑 style: 格式化代码并优化样式 test: 添加Vitest测试配置 build: 更新依赖项和构建配置 chore: 清理无用文件和缓存 --- .vscode/settings.json | 3 - cwd-comments-admin/index.html | 13 + cwd-comments-admin/package.json | 22 + cwd-comments-admin/src/App.vue | 23 + cwd-comments-admin/src/api/admin.ts | 65 + cwd-comments-admin/src/api/http.ts | 47 + cwd-comments-admin/src/env.d.ts | 10 + cwd-comments-admin/src/main.ts | 8 + cwd-comments-admin/src/router/index.ts | 52 + cwd-comments-admin/src/views/CommentsView.vue | 254 + cwd-comments-admin/src/views/LayoutView.vue | 130 + cwd-comments-admin/src/views/LoginView.vue | 127 + cwd-comments-admin/src/views/SettingsView.vue | 151 + cwd-comments-admin/tsconfig.json | 20 + cwd-comments-admin/vite.config.ts | 10 + .../.dev.vars.example | 5 +- .../.editorconfig | 0 .../.gitattributes | 0 package.json => cwd-comments-api/package.json | 7 +- .../pnpm-lock.yaml | 0 .../schemas}/comment.sql | 0 .../src}/api/admin/deleteComment.ts | 0 .../src/api/admin/getAdminEmail.ts | 18 + .../src}/api/admin/listComments.ts | 0 .../src}/api/admin/login.ts | 0 .../src/api/admin/setAdminEmail.ts | 23 + .../src}/api/admin/updateStatus.ts | 0 .../src}/api/public/getComments.ts | 0 .../src/api/public/postComment.ts | 151 + {src => cwd-comments-api/src}/bindings.ts | 9 +- {src => cwd-comments-api/src}/index.ts | 18 +- {src => cwd-comments-api/src}/utils/auth.ts | 0 {src => cwd-comments-api/src}/utils/cors.ts | 0 {src => cwd-comments-api/src}/utils/email.ts | 95 +- .../src}/utils/getAvatar.ts | 0 .../tsconfig.json | 0 .../vitest.config.mts | 0 .../worker-configuration.d.ts | 0 .../wrangler.jsonc.example | 0 .../deps_temp_d8b5c95d/chunk-QE257C5J.js | 12824 ---------------- .../deps_temp_d8b5c95d/chunk-QE257C5J.js.map | 7 - .../cache/deps_temp_d8b5c95d/package.json | 3 - .../vitepress___@vue_devtools-api.js | 4505 ------ .../vitepress___@vue_devtools-api.js.map | 7 - .../vitepress___@vueuse_core.js | 9731 ------------ .../vitepress___@vueuse_core.js.map | 7 - .../cache/deps_temp_d8b5c95d/vue.js | 347 - .../cache/deps_temp_d8b5c95d/vue.js.map | 7 - docs/guide/backend-config.md | 29 +- src/api/public/postComment.ts | 106 - src/views/admin.ts | 339 - src/views/login.ts | 129 - src/views/settings.ts | 130 - 53 files changed, 1201 insertions(+), 28231 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 cwd-comments-admin/index.html create mode 100644 cwd-comments-admin/package.json create mode 100644 cwd-comments-admin/src/App.vue create mode 100644 cwd-comments-admin/src/api/admin.ts create mode 100644 cwd-comments-admin/src/api/http.ts create mode 100644 cwd-comments-admin/src/env.d.ts create mode 100644 cwd-comments-admin/src/main.ts create mode 100644 cwd-comments-admin/src/router/index.ts create mode 100644 cwd-comments-admin/src/views/CommentsView.vue create mode 100644 cwd-comments-admin/src/views/LayoutView.vue create mode 100644 cwd-comments-admin/src/views/LoginView.vue create mode 100644 cwd-comments-admin/src/views/SettingsView.vue create mode 100644 cwd-comments-admin/tsconfig.json create mode 100644 cwd-comments-admin/vite.config.ts rename .dev.vars.example => cwd-comments-api/.dev.vars.example (51%) rename .editorconfig => cwd-comments-api/.editorconfig (100%) rename .gitattributes => cwd-comments-api/.gitattributes (100%) rename package.json => cwd-comments-api/package.json (61%) rename pnpm-lock.yaml => cwd-comments-api/pnpm-lock.yaml (100%) rename {schemas => cwd-comments-api/schemas}/comment.sql (100%) rename {src => cwd-comments-api/src}/api/admin/deleteComment.ts (100%) create mode 100644 cwd-comments-api/src/api/admin/getAdminEmail.ts rename {src => cwd-comments-api/src}/api/admin/listComments.ts (100%) rename {src => cwd-comments-api/src}/api/admin/login.ts (100%) create mode 100644 cwd-comments-api/src/api/admin/setAdminEmail.ts rename {src => cwd-comments-api/src}/api/admin/updateStatus.ts (100%) rename {src => cwd-comments-api/src}/api/public/getComments.ts (100%) create mode 100644 cwd-comments-api/src/api/public/postComment.ts rename {src => cwd-comments-api/src}/bindings.ts (58%) rename {src => cwd-comments-api/src}/index.ts (71%) rename {src => cwd-comments-api/src}/utils/auth.ts (100%) rename {src => cwd-comments-api/src}/utils/cors.ts (100%) rename {src => cwd-comments-api/src}/utils/email.ts (54%) rename {src => cwd-comments-api/src}/utils/getAvatar.ts (100%) rename tsconfig.json => cwd-comments-api/tsconfig.json (100%) rename vitest.config.mts => cwd-comments-api/vitest.config.mts (100%) rename worker-configuration.d.ts => cwd-comments-api/worker-configuration.d.ts (100%) rename wrangler.jsonc.example => cwd-comments-api/wrangler.jsonc.example (100%) delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/chunk-QE257C5J.js delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/chunk-QE257C5J.js.map delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/package.json delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vitepress___@vue_devtools-api.js delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vitepress___@vue_devtools-api.js.map delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vitepress___@vueuse_core.js delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vitepress___@vueuse_core.js.map delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vue.js delete mode 100644 docs/.vitepress/cache/deps_temp_d8b5c95d/vue.js.map delete mode 100644 src/api/public/postComment.ts delete mode 100644 src/views/admin.ts delete mode 100644 src/views/login.ts delete mode 100644 src/views/settings.ts diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index aef8443..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file diff --git a/cwd-comments-admin/index.html b/cwd-comments-admin/index.html new file mode 100644 index 0000000..092c0b2 --- /dev/null +++ b/cwd-comments-admin/index.html @@ -0,0 +1,13 @@ + + + + + + CWD 评论系统后台 + + +
+ + + + diff --git a/cwd-comments-admin/package.json b/cwd-comments-admin/package.json new file mode 100644 index 0000000..b3a62da --- /dev/null +++ b/cwd-comments-admin/package.json @@ -0,0 +1,22 @@ +{ + "name": "cwd-comments-admin", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.13", + "vue-router": "^4.4.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.4", + "typescript": "^5.5.2", + "vite": "^6.0.11", + "vue-tsc": "^2.1.10" + } +} + diff --git a/cwd-comments-admin/src/App.vue b/cwd-comments-admin/src/App.vue new file mode 100644 index 0000000..b8e6c22 --- /dev/null +++ b/cwd-comments-admin/src/App.vue @@ -0,0 +1,23 @@ + + + + + + diff --git a/cwd-comments-admin/src/api/admin.ts b/cwd-comments-admin/src/api/admin.ts new file mode 100644 index 0000000..fa85f49 --- /dev/null +++ b/cwd-comments-admin/src/api/admin.ts @@ -0,0 +1,65 @@ +import { get, post, put, del } from './http'; + +export type AdminLoginResponse = { + data: { + key: string; + }; +}; + +export type CommentItem = { + id: number; + pubDate: string; + author: string; + email: string; + postSlug: string; + url: string | null; + ipAddress: string | null; + contentText: string; + contentHtml: string; + status: string; +}; + +export type CommentListResponse = { + data: CommentItem[]; + pagination: { + page: number; + limit: number; + total: number; + }; +}; + +export type AdminEmailResponse = { + email: string | null; +}; + +export async function loginAdmin(name: string, password: string): Promise { + const res = await post('/admin/login', { name, password }); + const key = res.data.key; + localStorage.setItem('cwd_admin_token', key); + return key; +} + +export function logoutAdmin(): void { + localStorage.removeItem('cwd_admin_token'); +} + +export function fetchComments(page: number): Promise { + return get(`/admin/comments/list?page=${page}`); +} + +export function deleteComment(id: number): Promise<{ message: string }> { + return del<{ message: string }>(`/admin/comments/delete?id=${id}`); +} + +export function updateCommentStatus(id: number, status: string): Promise<{ message: string }> { + return put<{ message: string }>(`/admin/comments/status?id=${id}&status=${encodeURIComponent(status)}`); +} + +export function fetchAdminEmail(): Promise { + return get('/admin/settings/email'); +} + +export function saveAdminEmail(email: string): Promise<{ message: string }> { + return put<{ message: string }>('/admin/settings/email', { email }); +} + diff --git a/cwd-comments-admin/src/api/http.ts b/cwd-comments-admin/src/api/http.ts new file mode 100644 index 0000000..8c66460 --- /dev/null +++ b/cwd-comments-admin/src/api/http.ts @@ -0,0 +1,47 @@ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, ''); + +type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; + +async function request(method: HttpMethod, path: string, body?: unknown): Promise { + const token = localStorage.getItem('cwd_admin_token'); + const headers: HeadersInit = {}; + if (body !== undefined) { + headers['Content-Type'] = 'application/json'; + } + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const res = await fetch(`${API_BASE_URL}${path}`, { + method, + headers, + body: body !== undefined ? JSON.stringify(body) : undefined + }); + let data: any = null; + try { + data = await res.json(); + } catch { + data = null; + } + if (!res.ok) { + const message = data && data.message ? data.message : `请求失败,状态码 ${res.status}`; + throw new Error(message); + } + return data as T; +} + +export function get(path: string): Promise { + return request('GET', path); +} + +export function post(path: string, body?: unknown): Promise { + return request('POST', path, body); +} + +export function put(path: string, body?: unknown): Promise { + return request('PUT', path, body); +} + +export function del(path: string): Promise { + return request('DELETE', path); +} + diff --git a/cwd-comments-admin/src/env.d.ts b/cwd-comments-admin/src/env.d.ts new file mode 100644 index 0000000..fd5d2e7 --- /dev/null +++ b/cwd-comments-admin/src/env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + diff --git a/cwd-comments-admin/src/main.ts b/cwd-comments-admin/src/main.ts new file mode 100644 index 0000000..69181f8 --- /dev/null +++ b/cwd-comments-admin/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import { router } from './router'; + +const app = createApp(App); +app.use(router); +app.mount('#app'); + diff --git a/cwd-comments-admin/src/router/index.ts b/cwd-comments-admin/src/router/index.ts new file mode 100644 index 0000000..51d5c50 --- /dev/null +++ b/cwd-comments-admin/src/router/index.ts @@ -0,0 +1,52 @@ +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; +import LoginView from '../views/LoginView.vue'; +import LayoutView from '../views/LayoutView.vue'; +import CommentsView from '../views/CommentsView.vue'; +import SettingsView from '../views/SettingsView.vue'; + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'login', + component: LoginView + }, + { + path: '/', + component: LayoutView, + children: [ + { + path: '', + redirect: '/comments' + }, + { + path: 'comments', + name: 'comments', + component: CommentsView + }, + { + path: 'settings', + name: 'settings', + component: SettingsView + } + ] + } +]; + +export const router = createRouter({ + history: createWebHistory(), + routes +}); + +router.beforeEach((to, from, next) => { + if (to.name === 'login') { + next(); + return; + } + const token = localStorage.getItem('cwd_admin_token'); + if (!token) { + next({ name: 'login' }); + return; + } + next(); +}); + diff --git a/cwd-comments-admin/src/views/CommentsView.vue b/cwd-comments-admin/src/views/CommentsView.vue new file mode 100644 index 0000000..20fef71 --- /dev/null +++ b/cwd-comments-admin/src/views/CommentsView.vue @@ -0,0 +1,254 @@ + + + + + + diff --git a/cwd-comments-admin/src/views/LayoutView.vue b/cwd-comments-admin/src/views/LayoutView.vue new file mode 100644 index 0000000..3d21ae3 --- /dev/null +++ b/cwd-comments-admin/src/views/LayoutView.vue @@ -0,0 +1,130 @@ + + + + + + diff --git a/cwd-comments-admin/src/views/LoginView.vue b/cwd-comments-admin/src/views/LoginView.vue new file mode 100644 index 0000000..2135780 --- /dev/null +++ b/cwd-comments-admin/src/views/LoginView.vue @@ -0,0 +1,127 @@ + + + + + + diff --git a/cwd-comments-admin/src/views/SettingsView.vue b/cwd-comments-admin/src/views/SettingsView.vue new file mode 100644 index 0000000..6868304 --- /dev/null +++ b/cwd-comments-admin/src/views/SettingsView.vue @@ -0,0 +1,151 @@ + + + + + + diff --git a/cwd-comments-admin/tsconfig.json b/cwd-comments-admin/tsconfig.json new file mode 100644 index 0000000..f932362 --- /dev/null +++ b/cwd-comments-admin/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "types": ["vite/client"] + }, + "include": ["src"] +} + diff --git a/cwd-comments-admin/vite.config.ts b/cwd-comments-admin/vite.config.ts new file mode 100644 index 0000000..59b4f41 --- /dev/null +++ b/cwd-comments-admin/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5176 + } +}); + diff --git a/.dev.vars.example b/cwd-comments-api/.dev.vars.example similarity index 51% rename from .dev.vars.example rename to cwd-comments-api/.dev.vars.example index 61c3520..a865df8 100644 --- a/.dev.vars.example +++ b/cwd-comments-api/.dev.vars.example @@ -1,6 +1,5 @@ ALLOW_ORIGIN="http://localhost:4321,https://blog.example.top" -RESEND_API_KEY="re_xxxxxxxx" -RESEND_FROM_EMAIL="" +CF_FROM_EMAIL="noreply@yourdomain.com" EMAIL_ADDRESS="admin@example.top" ADMIN_NAME="Admin" -ADMIN_PASSWORD="password" \ No newline at end of file +ADMIN_PASSWORD="password" diff --git a/.editorconfig b/cwd-comments-api/.editorconfig similarity index 100% rename from .editorconfig rename to cwd-comments-api/.editorconfig diff --git a/.gitattributes b/cwd-comments-api/.gitattributes similarity index 100% rename from .gitattributes rename to cwd-comments-api/.gitattributes diff --git a/package.json b/cwd-comments-api/package.json similarity index 61% rename from package.json rename to cwd-comments-api/package.json index 6edda5d..d55f0e6 100644 --- a/package.json +++ b/cwd-comments-api/package.json @@ -7,12 +7,7 @@ "dev": "wrangler dev", "start": "wrangler dev", "test": "vitest", - "cf-typegen": "wrangler types", - "widget:dev": "cd widget && npm install && npm run dev", - "widget:build": "cd widget && npm run build", - "docs:dev": "cd docs && npm run dev", - "docs:build": "cd docs && npm run build", - "docs:preview": "cd docs && npm run preview" + "cf-typegen": "wrangler types" }, "devDependencies": { "@cloudflare/vitest-pool-workers": "^0.8.19", diff --git a/pnpm-lock.yaml b/cwd-comments-api/pnpm-lock.yaml similarity index 100% rename from pnpm-lock.yaml rename to cwd-comments-api/pnpm-lock.yaml diff --git a/schemas/comment.sql b/cwd-comments-api/schemas/comment.sql similarity index 100% rename from schemas/comment.sql rename to cwd-comments-api/schemas/comment.sql diff --git a/src/api/admin/deleteComment.ts b/cwd-comments-api/src/api/admin/deleteComment.ts similarity index 100% rename from src/api/admin/deleteComment.ts rename to cwd-comments-api/src/api/admin/deleteComment.ts diff --git a/cwd-comments-api/src/api/admin/getAdminEmail.ts b/cwd-comments-api/src/api/admin/getAdminEmail.ts new file mode 100644 index 0000000..88c7a7f --- /dev/null +++ b/cwd-comments-api/src/api/admin/getAdminEmail.ts @@ -0,0 +1,18 @@ +import { Context } from 'hono'; +import { Bindings } from '../../bindings'; + +export const getAdminEmail = async (c: Context<{ Bindings: Bindings }>) => { + try { + let email: string | null = null; + if (c.env.CWD_CONFIG_KV) { + email = await c.env.CWD_CONFIG_KV.get('settings:admin_notify_email'); + } + if (!email) { + email = c.env.EMAIL_ADDRESS || null; + } + return c.json({ email }); + } catch (e: any) { + return c.json({ message: e.message }, 500); + } +}; + diff --git a/src/api/admin/listComments.ts b/cwd-comments-api/src/api/admin/listComments.ts similarity index 100% rename from src/api/admin/listComments.ts rename to cwd-comments-api/src/api/admin/listComments.ts diff --git a/src/api/admin/login.ts b/cwd-comments-api/src/api/admin/login.ts similarity index 100% rename from src/api/admin/login.ts rename to cwd-comments-api/src/api/admin/login.ts diff --git a/cwd-comments-api/src/api/admin/setAdminEmail.ts b/cwd-comments-api/src/api/admin/setAdminEmail.ts new file mode 100644 index 0000000..d66c2e1 --- /dev/null +++ b/cwd-comments-api/src/api/admin/setAdminEmail.ts @@ -0,0 +1,23 @@ +import { Context } from 'hono'; +import { Bindings } from '../../bindings'; + +function isValidEmail(email: string) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +export const setAdminEmail = async (c: Context<{ Bindings: Bindings }>) => { + try { + const { email } = await c.req.json(); + if (!email || !isValidEmail(email)) { + return c.json({ message: '邮箱格式不正确' }, 400); + } + if (!c.env.CWD_CONFIG_KV) { + return c.json({ message: '未配置 CWD_CONFIG_KV,无法保存设置' }, 500); + } + await c.env.CWD_CONFIG_KV.put('settings:admin_notify_email', email); + return c.json({ message: '保存成功' }); + } catch (e: any) { + return c.json({ message: e.message }, 500); + } +}; + diff --git a/src/api/admin/updateStatus.ts b/cwd-comments-api/src/api/admin/updateStatus.ts similarity index 100% rename from src/api/admin/updateStatus.ts rename to cwd-comments-api/src/api/admin/updateStatus.ts diff --git a/src/api/public/getComments.ts b/cwd-comments-api/src/api/public/getComments.ts similarity index 100% rename from src/api/public/getComments.ts rename to cwd-comments-api/src/api/public/getComments.ts diff --git a/cwd-comments-api/src/api/public/postComment.ts b/cwd-comments-api/src/api/public/postComment.ts new file mode 100644 index 0000000..f08136b --- /dev/null +++ b/cwd-comments-api/src/api/public/postComment.ts @@ -0,0 +1,151 @@ +import { Context } from 'hono'; +import { UAParser } from 'ua-parser-js'; +import { Bindings } from '../../bindings'; +import { sendCommentNotification, sendCommentReplyNotification } from '../../utils/email'; + +// 检查内容,将 - - - - - -
- - -
- - {{ toast.message }} -
-
- - - - - -
-
-

评论管理

- -
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - -
信息内容状态操作
暂无评论数据
-
-
-
- - () -
- {{ comment.url }} -
IP:
-
{{ formatDate(comment.pubDate) }}
-
-
-
-
- - {{ config.blogDomain + comment.postSlug }} - -
- - {{ comment.status }} - - - - - -
-
- - -
- -
-
-
-
- - - - -`; diff --git a/src/views/login.ts b/src/views/login.ts deleted file mode 100644 index 569577e..0000000 --- a/src/views/login.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { html } from 'hono/html'; - -export const loginView = (isDev: boolean, adminName?: string, adminPassword?: string) => html` - - - - - - 登录 - CWD 评论后台 - - - - - - -
-
-

管理员登录

-
-
- - -
-
- - -
-
- - -
- -
-

{{ error }}

-
-
- - - - -`; diff --git a/src/views/settings.ts b/src/views/settings.ts deleted file mode 100644 index cd4a496..0000000 --- a/src/views/settings.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { html } from 'hono/html'; - -export const SettingsView = html` - - - - - - 设置 - CWD 评论后台 - - - - - - -
- - -
- - {{ toast.message }} -
-
- - - - - -
-

设置

-
-
- - -

设置后,文章链接将自动拼接此前缀

-
-
-
- -
-
-
- - - - -`;