From 1460246ab090089a6cad1b3b8cb980e6c6a64de1 Mon Sep 17 00:00:00 2001
From: npmrun <1549469775@qq.com>
Date: Fri, 26 Jun 2026 21:30:43 +0800
Subject: [PATCH] feat: add email configuration management for admin
- Introduced new API endpoints for managing SMTP email configuration (GET, PUT, POST).
- Implemented email sending functionality using Nodemailer.
- Added middleware to require admin permissions for accessing admin routes.
- Updated admin sidebar navigation to conditionally display items based on admin role.
- Created a new email configuration page in the admin panel with form validation and test functionality.
- Masked sensitive information in API responses.
- Enhanced scheduler execution and task management APIs to require admin permissions.
- Added a new JSON file for desktop topic titles.
---
.reasonix/desktop-topic-title-sources.json | 3 +
.reasonix/desktop-topic-titles.json | 3 +
app/components/admin/AdminSidebarNav.vue | 1 +
app/middleware/auth.global.ts | 12 +
app/pages/admin.vue | 12 +-
app/pages/admin/dashboard.vue | 26 +-
app/pages/admin/email/index.vue | 396 +++++++++++++++++++++
bun.lock | 6 +
package.json | 2 +
packages/drizzle-pkg/db.sqlite | Bin 282624 -> 282624 bytes
server/api/admin/email/config.get.ts | 12 +
server/api/admin/email/config.put.ts | 47 +++
server/api/admin/email/send.post.ts | 22 ++
server/api/admin/email/test.post.ts | 12 +
server/api/config/global.get.ts | 2 +-
server/api/config/global.put.ts | 2 +-
server/api/scheduler/executions.get.ts | 2 +
server/api/scheduler/executions/[id].delete.ts | 2 +
server/api/scheduler/executions/delete-all.post.ts | 2 +
server/api/scheduler/stats.get.ts | 2 +
server/api/scheduler/tasks/[id].delete.ts | 2 +
server/api/scheduler/tasks/[id].get.ts | 2 +
server/api/scheduler/tasks/[id].put.ts | 2 +
server/api/scheduler/tasks/[id]/toggle.post.ts | 2 +
server/api/scheduler/tasks/[id]/trigger.post.ts | 2 +
server/api/scheduler/tasks/index.get.ts | 2 +
server/api/scheduler/tasks/index.post.ts | 2 +
server/api/users/[id].delete.ts | 2 +
server/api/users/[id].put.ts | 2 +-
server/api/users/index.get.ts | 2 +
server/api/users/index.post.ts | 2 +
server/service/config/normalization.ts | 8 +-
server/service/config/registry.ts | 47 +++
server/service/email/index.ts | 89 +++++
34 files changed, 712 insertions(+), 20 deletions(-)
create mode 100644 .reasonix/desktop-topic-title-sources.json
create mode 100644 .reasonix/desktop-topic-titles.json
create mode 100644 app/pages/admin/email/index.vue
create mode 100644 server/api/admin/email/config.get.ts
create mode 100644 server/api/admin/email/config.put.ts
create mode 100644 server/api/admin/email/send.post.ts
create mode 100644 server/api/admin/email/test.post.ts
create mode 100644 server/service/email/index.ts
diff --git a/.reasonix/desktop-topic-title-sources.json b/.reasonix/desktop-topic-title-sources.json
new file mode 100644
index 0000000..f04e650
--- /dev/null
+++ b/.reasonix/desktop-topic-title-sources.json
@@ -0,0 +1,3 @@
+{
+ "topic_20260626-113733_9874ba9ab889b7ed": "manual"
+}
\ No newline at end of file
diff --git a/.reasonix/desktop-topic-titles.json b/.reasonix/desktop-topic-titles.json
new file mode 100644
index 0000000..cd04107
--- /dev/null
+++ b/.reasonix/desktop-topic-titles.json
@@ -0,0 +1,3 @@
+{
+ "topic_20260626-113733_9874ba9ab889b7ed": "新的会话"
+}
\ No newline at end of file
diff --git a/app/components/admin/AdminSidebarNav.vue b/app/components/admin/AdminSidebarNav.vue
index 6497bde..020a2a3 100644
--- a/app/components/admin/AdminSidebarNav.vue
+++ b/app/components/admin/AdminSidebarNav.vue
@@ -4,6 +4,7 @@ export interface NavItem {
to?: string
icon?: string
children?: NavItem[]
+ requireAdmin?: boolean
}
const props = defineProps<{
diff --git a/app/middleware/auth.global.ts b/app/middleware/auth.global.ts
index 4500ed5..99d4032 100644
--- a/app/middleware/auth.global.ts
+++ b/app/middleware/auth.global.ts
@@ -26,6 +26,18 @@ export default defineNuxtRouteMiddleware(async (to) => {
const currentFullPath = to.fullPath;
const isLoggedIn = loggedIn.value;
+ // 管理员路由守卫:除仪表板外 /admin/* 需要管理员权限
+ const isAdminRoute = currentPath.startsWith("/admin");
+ const isDashboardOnly = currentPath === "/admin" || currentPath === "/admin/dashboard";
+ if (isLoggedIn && isAdminRoute && !isDashboardOnly) {
+ const { user } = useAuthSession();
+ if (user.value?.role !== "admin") {
+ const { $toast } = useNuxtApp();
+ $toast?.warning("需要管理员权限");
+ return navigateTo("/");
+ }
+ }
+
if (!isLoggedIn && !isPublicRoute(currentPath)) {
return navigateTo({
path: FRONTEND_LOGIN_PATH,
diff --git a/app/pages/admin.vue b/app/pages/admin.vue
index 5a3936b..92d938f 100644
--- a/app/pages/admin.vue
+++ b/app/pages/admin.vue
@@ -15,17 +15,25 @@ const adminNav: NavItem[] = [
{
label: '定时任务',
to: '/admin/scheduler',
- icon: 'lucide:clock'
+ icon: 'lucide:clock',
+ requireAdmin: true,
},
{
label: '系统管理',
icon: 'lucide:settings',
+ requireAdmin: true,
children: [
{ label: '用户管理', to: '/admin/users' },
+ { label: '邮件配置', to: '/admin/email' },
]
},
]
+const visibleNav = computed(() => {
+ const isAdmin = user.value?.role === 'admin'
+ return adminNav.filter(item => !item.requireAdmin || isAdmin)
+})
+
const logout = async () => {
await clear()
navigateTo("/")
@@ -45,7 +53,7 @@ const logout = async () => {
-