feat(邮件通知): 重构邮件通知系统并增加配置选项

- 移除 Cloudflare Email 绑定,改为支持外部邮件网关
- 新增邮件通知全局开关及管理员/用户通知独立配置
- 在管理后台添加邮件通知配置界面
- 优化邮件发送逻辑,增加错误处理和日志记录
- 更新相关文档说明
This commit is contained in:
anghunk
2026-01-20 11:23:52 +08:00
parent 4146cfed6f
commit 2e177f58f5
11 changed files with 322 additions and 230 deletions

File diff suppressed because one or more lines are too long

View File

@@ -40,6 +40,12 @@ export type CommentSettingsResponse = {
adminEnabled: boolean;
};
export type EmailNotifySettingsResponse = {
globalEnabled: boolean;
adminEnabled: boolean;
userEnabled: boolean;
};
export async function loginAdmin(name: string, password: string): Promise<string> {
const res = await post<AdminLoginResponse>('/admin/login', { name, password });
const key = res.data.key;
@@ -71,6 +77,18 @@ export function saveAdminEmail(email: string): Promise<{ message: string }> {
return put<{ message: string }>('/admin/settings/email', { email });
}
export function fetchEmailNotifySettings(): Promise<EmailNotifySettingsResponse> {
return get<EmailNotifySettingsResponse>('/admin/settings/email-notify');
}
export function saveEmailNotifySettings(data: {
globalEnabled?: boolean;
adminEnabled?: boolean;
userEnabled?: boolean;
}): Promise<{ message: string }> {
return put<{ message: string }>('/admin/settings/email-notify', data);
}
export function fetchCommentSettings(): Promise<CommentSettingsResponse> {
return get<CommentSettingsResponse>('/admin/settings/comments');
}

View File

@@ -41,6 +41,27 @@
<div class="card">
<h3 class="card-title">通知邮箱设置</h3>
<div class="form-item">
<label class="form-label">开启邮件通知</label>
<label class="switch">
<input v-model="emailGlobalEnabled" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-item">
<label class="form-label">新评论通知管理员</label>
<label class="switch">
<input v-model="emailAdminEnabled" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-item">
<label class="form-label">评论回复通知用户</label>
<label class="switch">
<input v-model="emailUserEnabled" type="checkbox" />
<span class="slider" />
</label>
</div>
<div class="form-item">
<label class="form-label">管理员通知邮箱</label>
<input v-model="email" class="form-input" type="email" />
@@ -69,9 +90,14 @@ import {
saveAdminEmail,
fetchCommentSettings,
saveCommentSettings,
fetchEmailNotifySettings,
saveEmailNotifySettings,
} from "../api/admin";
const email = ref("");
const emailGlobalEnabled = ref(true);
const emailAdminEnabled = ref(true);
const emailUserEnabled = ref(true);
const commentAdminEmail = ref("");
const commentAdminBadge = ref("");
const avatarPrefix = ref("");
@@ -97,15 +123,19 @@ function showToast(msg: string, type: "success" | "error" = "success") {
async function load() {
loading.value = true;
try {
const [notifyRes, commentRes] = await Promise.all([
const [notifyRes, commentRes, emailNotifyRes] = await Promise.all([
fetchAdminEmail(),
fetchCommentSettings(),
fetchEmailNotifySettings(),
]);
email.value = notifyRes.email || "";
commentAdminEmail.value = commentRes.adminEmail || "";
commentAdminBadge.value = commentRes.adminBadge || "博主";
avatarPrefix.value = commentRes.avatarPrefix || "";
commentAdminEnabled.value = !!commentRes.adminEnabled;
emailGlobalEnabled.value = !!emailNotifyRes.globalEnabled;
emailAdminEnabled.value = !!emailNotifyRes.adminEnabled;
emailUserEnabled.value = !!emailNotifyRes.userEnabled;
} catch (e: any) {
message.value = e.message || "加载失败";
messageType.value = "error";
@@ -123,8 +153,15 @@ async function saveEmail() {
savingEmail.value = true;
message.value = "";
try {
const res = await saveAdminEmail(email.value);
showToast(res.message || "保存成功", "success");
const [emailRes] = await Promise.all([
saveAdminEmail(email.value),
saveEmailNotifySettings({
globalEnabled: emailGlobalEnabled.value,
adminEnabled: emailAdminEnabled.value,
userEnabled: emailUserEnabled.value,
}),
]);
showToast(emailRes.message || "保存成功", "success");
} catch (e: any) {
message.value = e.message || "保存失败";
messageType.value = "error";