refactor: 模块管理支持内置工具展示

将邮件配置、IP 安全、审计日志等系统功能从固定菜单移至模块管理页面,以内置工具形式统一展示
This commit is contained in:
fawney19
2026-02-09 22:52:46 +08:00
parent 80d65ad2e4
commit 1d625916ff
3 changed files with 107 additions and 5 deletions

View File

@@ -0,0 +1,35 @@
import { Mail, Shield, AlertTriangle } from 'lucide-vue-next'
import type { LucideIcon } from 'lucide-vue-next'
export interface BuiltinTool {
name: string
description: string
href: string
icon: LucideIcon
}
export const BUILTIN_TOOLS: BuiltinTool[] = [
{
name: '邮件配置',
description: '配置 SMTP 邮件服务,管理邮件模板和发送设置',
href: '/admin/email',
icon: Mail,
},
{
name: 'IP 安全',
description: '管理 IP 黑白名单,控制系统访问权限',
href: '/admin/ip-security',
icon: Shield,
},
{
name: '审计日志',
description: '查看系统操作日志,追踪安全事件与变更记录',
href: '/admin/audit-logs',
icon: AlertTriangle,
},
]
/** href → display name mapping for breadcrumbs */
export const BUILTIN_TOOL_BREADCRUMBS: Record<string, string> = Object.fromEntries(
BUILTIN_TOOLS.map(t => [t.href, t.name])
)

View File

@@ -363,7 +363,6 @@ import {
Megaphone,
Menu,
X,
Mail,
Puzzle,
Zap,
FileUp,
@@ -372,6 +371,7 @@ import {
} from 'lucide-vue-next'
import GithubIcon from '@/components/icons/GithubIcon.vue'
import { BUILTIN_TOOL_BREADCRUMBS } from '@/config/builtin-tools'
const router = useRouter()
const route = useRoute()
@@ -507,9 +507,6 @@ const navigation = computed(() => {
const systemItems: { name: string; href: string; icon: LucideIcon }[] = [
{ name: '公告管理', href: '/admin/announcements', icon: Megaphone },
{ name: '缓存监控', href: '/admin/cache-monitoring', icon: Gauge },
{ name: 'IP 安全', href: '/admin/ip-security', icon: Shield },
{ name: '审计日志', href: '/admin/audit-logs', icon: AlertTriangle },
{ name: '邮件配置', href: '/admin/email', icon: Mail },
]
// 动态添加已激活模块的菜单项
@@ -597,6 +594,15 @@ const breadcrumbs = computed((): BreadcrumbItem[] => {
]
}
// Special case: built-in tools under module management
if (BUILTIN_TOOL_BREADCRUMBS[route.path]) {
return [
{ label: '系统' },
{ label: '模块管理', href: '/admin/modules' },
{ label: BUILTIN_TOOL_BREADCRUMBS[route.path] }
]
}
// Find section and page from navigation
for (const group of navigation.value) {
const activeItem = group.items.find(item => isNavActive(item.href))

View File

@@ -32,6 +32,57 @@
</div>
<div>
<!-- 内置工具 -->
<div
v-if="filteredBuiltinTools.length > 0"
class="mb-8"
>
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4">
内置工具
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
<div
v-for="tool in filteredBuiltinTools"
:key="tool.name"
class="group relative border rounded-2xl p-6 transition-all duration-200 hover:shadow-lg border-border bg-card hover:border-primary/20 cursor-pointer"
@click="router.push(tool.href)"
>
<div class="flex items-start gap-4 mb-3">
<div class="w-11 h-11 rounded-xl flex items-center justify-center shrink-0 transition-colors bg-primary/15 text-primary">
<component
:is="tool.icon"
class="w-5 h-5"
/>
</div>
<div class="flex-1 min-w-0 pt-1">
<h4 class="font-semibold text-base truncate">
{{ tool.name }}
</h4>
</div>
</div>
<p class="text-sm text-muted-foreground leading-relaxed line-clamp-2 min-h-[2.5rem]">
{{ tool.description }}
</p>
<div class="mt-5 pt-4 border-t border-border/50 flex items-center justify-end">
<Button
variant="outline"
size="sm"
class="gap-1.5"
@click.stop="router.push(tool.href)"
>
<Settings class="w-3.5 h-3.5" />
管理
</Button>
</div>
</div>
</div>
</div>
<!-- 扩展模块 -->
<h3 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4">
扩展模块
</h3>
<!-- 模块卡片网格 -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
<div
@@ -130,7 +181,7 @@
<!-- 搜索无结果 -->
<div
v-if="filteredModules.length === 0 && searchQuery && !loading"
v-if="filteredModules.length === 0 && filteredBuiltinTools.length === 0 && searchQuery && !loading"
class="text-center py-16"
>
<Search class="w-12 h-12 mx-auto text-muted-foreground/50 mb-4" />
@@ -163,6 +214,7 @@ import Input from '@/components/ui/input.vue'
import { PageHeader, PageContainer } from '@/components/layout'
import { useToast } from '@/composables/useToast'
import { useModuleStore } from '@/stores/modules'
import { BUILTIN_TOOLS } from '@/config/builtin-tools'
import { log } from '@/utils/logger'
import { getErrorMessage } from '@/types/api-error'
@@ -174,6 +226,15 @@ const loading = ref(false)
const toggling = ref<Record<string, boolean>>({})
const searchQuery = ref('')
// 过滤后的内置工具
const filteredBuiltinTools = computed(() => {
if (!searchQuery.value.trim()) return BUILTIN_TOOLS
const query = searchQuery.value.toLowerCase()
return BUILTIN_TOOLS.filter(
t => t.name.toLowerCase().includes(query) || t.description.toLowerCase().includes(query)
)
})
// 获取分类图标
function getCategoryIcon(category: string) {
const icons: Record<string, any> = {