refactor(cors): 简化CORS配置并移除未使用的参数

feat(email): 提取邮箱验证逻辑到共享模块并增强验证

docs: 更新文档移除不再使用的ALLOW_ORIGIN配置

style(admin): 为设置页面添加加载状态和样式优化

fix(comments): 在邮件发送前增加邮箱格式验证
This commit is contained in:
anghunk
2026-01-19 17:02:19 +08:00
parent e6e21dff61
commit 73bbe44fbc
9 changed files with 217 additions and 118 deletions

View File

@@ -399,6 +399,38 @@ onMounted(() => {
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);
}
}
.pagination {
margin-top: 8px;
display: flex;

View File

@@ -1,151 +1,199 @@
<template>
<div class="page">
<h2 class="page-title">系统设置</h2>
<div class="card">
<h3 class="card-title">通知邮箱设置</h3>
<div class="form-item">
<label class="form-label">管理员通知邮箱</label>
<input v-model="email" class="form-input" type="email" />
</div>
<div v-if="message" :class="['form-message', messageType === 'error' ? 'form-message-error' : 'form-message-success']">
{{ message }}
</div>
<div class="card-actions">
<button class="card-button" :disabled="saving" @click="save">
<span v-if="saving">保存中...</span>
<span v-else>保存</span>
</button>
</div>
</div>
</div>
<div class="page">
<h2 class="page-title">系统设置</h2>
<div v-if="loading" class="page-hint">加载中...</div>
<div v-else>
<div class="card">
<h3 class="card-title">通知邮箱设置</h3>
<div class="form-item">
<label class="form-label">管理员通知邮箱</label>
<input v-model="email" class="form-input" type="email" />
</div>
<div
v-if="message"
:class="[
'form-message',
messageType === 'error' ? 'form-message-error' : 'form-message-success',
]"
>
{{ message }}
</div>
<div class="card-actions">
<button class="card-button" :disabled="saving" @click="save">
<span v-if="saving">保存中...</span>
<span v-else>保存</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { fetchAdminEmail, saveAdminEmail } from '../api/admin';
import { onMounted, ref } from "vue";
import { fetchAdminEmail, saveAdminEmail } from "../api/admin";
const email = ref('');
const email = ref("");
const saving = ref(false);
const message = ref('');
const messageType = ref<'success' | 'error'>('success');
const loading = ref(false);
const message = ref("");
const messageType = ref<"success" | "error">("success");
async function load() {
try {
const res = await fetchAdminEmail();
email.value = res.email || '';
} catch (e: any) {
message.value = e.message || '加载失败';
messageType.value = 'error';
}
loading.value = true;
try {
const res = await fetchAdminEmail();
email.value = res.email || "";
} catch (e: any) {
message.value = e.message || "加载失败";
messageType.value = "error";
} finally {
loading.value = false;
}
}
async function save() {
if (!email.value) {
message.value = '请输入邮箱';
messageType.value = 'error';
return;
}
saving.value = true;
message.value = '';
try {
const res = await saveAdminEmail(email.value);
message.value = res.message || '保存成功';
messageType.value = 'success';
} catch (e: any) {
message.value = e.message || '保存失败';
messageType.value = 'error';
} finally {
saving.value = false;
}
if (!email.value) {
message.value = "请输入邮箱";
messageType.value = "error";
return;
}
saving.value = true;
message.value = "";
try {
const res = await saveAdminEmail(email.value);
message.value = res.message || "保存成功";
messageType.value = "success";
} catch (e: any) {
message.value = e.message || "保存失败";
messageType.value = "error";
} finally {
saving.value = false;
}
}
onMounted(() => {
load();
load();
});
</script>
<style scoped>
.page {
display: flex;
flex-direction: column;
gap: 12px;
max-width: 520px;
display: flex;
flex-direction: column;
gap: 12px;
max-width: 520px;
}
.page-title {
margin: 0;
font-size: 18px;
color: #24292f;
margin: 0;
font-size: 18px;
color: #24292f;
}
.card {
background-color: #ffffff;
border-radius: 6px;
border: 1px solid #d0d7de;
padding: 16px 18px;
background-color: #ffffff;
border-radius: 6px;
border: 1px solid #d0d7de;
padding: 16px 18px;
}
.card-title {
margin: 0 0 12px;
font-size: 15px;
margin: 0 0 12px;
font-size: 15px;
}
.form-item {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
}
.form-label {
font-size: 14px;
color: #555555;
font-size: 14px;
color: #555555;
}
.form-input {
padding: 8px 10px;
border-radius: 4px;
border: 1px solid #d0d7de;
font-size: 14px;
outline: none;
padding: 8px 10px;
border-radius: 4px;
border: 1px solid #d0d7de;
font-size: 14px;
outline: none;
}
.form-input:focus {
border-color: #0969da;
box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2);
border-color: #0969da;
box-shadow: 0 0 0 1px rgba(9, 105, 218, 0.2);
}
.card-actions {
display: flex;
justify-content: flex-end;
display: flex;
justify-content: flex-end;
}
.card-button {
padding: 8px 14px;
border-radius: 4px;
border: none;
background-color: #0969da;
color: #ffffff;
font-size: 14px;
cursor: pointer;
padding: 8px 14px;
border-radius: 4px;
border: none;
background-color: #0969da;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
.card-button:disabled {
opacity: 0.7;
cursor: default;
opacity: 0.7;
cursor: default;
}
.form-message {
font-size: 13px;
margin-bottom: 8px;
font-size: 13px;
margin-bottom: 8px;
}
.form-message-success {
color: #1a7f37;
color: #1a7f37;
}
.form-message-error {
color: #d1242f;
color: #d1242f;
}
.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);
}
}
.page-hint {
font-size: 14px;
color: #57606a;
}
</style>