| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- <script setup>
- import { computed, ref } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import {
- RocketOutline,
- GridOutline,
- GitMergeOutline,
- PricetagsOutline,
- SettingsOutline,
- ChevronDownOutline,
- PlayCircleOutline,
- CubeOutline,
- DocumentTextOutline,
- ServerOutline,
- GitNetworkOutline,
- BookOutline,
- SparklesOutline,
- WarningOutline,
- AppsOutline
- } from '@vicons/ionicons5'
- import { NIcon } from 'naive-ui'
- const route = useRoute()
- const router = useRouter()
- const menuItems = [
- {
- group: '知识库管理',
- icon: BookOutline,
- children: [
- { key: '/kb/documents', label: '文档数据', icon: DocumentTextOutline },
- { key: '/kb/datasources', label: '结构化数据', icon: ServerOutline },
- { key: '/kb/graphs', label: '知识图谱', icon: GitNetworkOutline },
- { key: '/kb/rag', label: 'RAG', icon: SparklesOutline },
- { key: '/kb/rag-governance', label: '图谱治理', icon: SettingsOutline }
- ]
- },
- {
- group: '智能体管理',
- icon: RocketOutline,
- children: [
- { key: '/management', label: '技能管理', icon: GridOutline },
- { key: '/skill/generate', label: '技能生成', icon: SparklesOutline },
- { key: '/orchestration', label: '智能体编排', icon: GitMergeOutline },
- { key: '/templates', label: '智能体模板', icon: CubeOutline },
- { key: '/history', label: '运行记录', icon: PlayCircleOutline }
- ]
- },
- {
- group: '其他管理',
- icon: AppsOutline,
- children: [
- { key: '/tags', label: '标签管理', icon: PricetagsOutline },
- { key: '/models', label: '模型管理', icon: SettingsOutline },
- { key: '/error-patterns', label: '错误模式管理', icon: WarningOutline }
- ]
- }
- ]
- // 分组展开状态:按 group 名称独立控制
- const groupExpanded = ref({
- '知识库管理': true,
- '智能体管理': true,
- '其他管理': true
- })
- function toggleGroup(name) {
- groupExpanded.value[name] = !groupExpanded.value[name]
- }
- const activeKey = computed(() => route.path)
- // 判断分组中是否有激活项
- function isGroupActive(group) {
- return group.children?.some(c => activeKey.value.startsWith(c.key))
- }
- function navigateTo(path) {
- router.push(path)
- }
- </script>
- <template>
- <aside class="app-sidebar">
- <!-- Logo 区域 -->
- <div class="sidebar-logo">
- <div class="logo-icon">
- <svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="url(#logo-grad)" stroke-width="2" stroke-linecap="round"
- stroke-linejoin="round" />
- <path d="M2 17L12 22L22 17" stroke="url(#logo-grad)" stroke-width="2" stroke-linecap="round"
- stroke-linejoin="round" />
- <path d="M2 12L12 17L22 12" stroke="url(#logo-grad)" stroke-width="2" stroke-linecap="round"
- stroke-linejoin="round" />
- <defs>
- <linearGradient id="logo-grad" x1="2" y1="2" x2="22" y2="22">
- <stop stop-color="#38BDF8" />
- <stop offset="1" stop-color="#2563EB" />
- </linearGradient>
- </defs>
- </svg>
- </div>
- <div class="logo-text">
- <span class="logo-title">智能体融合管理</span>
- <span class="logo-subtitle"></span>
- </div>
- </div>
- <!-- 导航菜单 -->
- <nav class="sidebar-nav">
- <template v-for="item in menuItems" :key="item.key || item.group">
- <!-- 分组菜单 -->
- <template v-if="item.children">
- <div class="nav-group-header" :class="{ active: isGroupActive(item) }" @click="toggleGroup(item.group)">
- <div class="nav-group-left">
- <div class="nav-item-icon">
- <component :is="item.icon" />
- </div>
- <span class="nav-item-label">{{ item.group }}</span>
- </div>
- <div class="nav-group-arrow" :class="{ expanded: groupExpanded[item.group] }">
- <n-icon size="14">
- <ChevronDownOutline />
- </n-icon>
- </div>
- </div>
- <div v-show="groupExpanded[item.group]" class="nav-group-children">
- <div v-for="child in item.children" :key="child.key" class="nav-item sub-item"
- :class="{ active: activeKey === child.key }" @click="navigateTo(child.key)">
- <div class="nav-item-icon">
- <component :is="child.icon" />
- </div>
- <span class="nav-item-label">{{ child.label }}</span>
- <div v-if="activeKey === child.key" class="nav-item-indicator"></div>
- </div>
- </div>
- </template>
- <!-- 普通菜单项 -->
- <div v-else class="nav-item" :class="{ active: activeKey === item.key }" @click="navigateTo(item.key)">
- <div class="nav-item-icon">
- <component :is="item.icon" />
- </div>
- <span class="nav-item-label">{{ item.label }}</span>
- <div v-if="activeKey === item.key" class="nav-item-indicator"></div>
- </div>
- </template>
- </nav>
- <!-- 底部装饰 -->
- <div class="sidebar-footer">
- <div class="footer-line"></div>
- <div class="footer-text"></div>
- </div>
- </aside>
- </template>
- <style scoped>
- .app-sidebar {
- width: var(--sidebar-width);
- height: 100vh;
- background: var(--bg-sidebar);
- display: flex;
- flex-direction: column;
- border-right: 1px solid var(--border-color);
- position: relative;
- overflow: hidden;
- }
- /* Logo */
- .sidebar-logo {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 24px 20px 32px;
- }
- .logo-icon {
- width: 44px;
- height: 44px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(37, 99, 235, 0.12);
- border-radius: var(--radius-md);
- border: 1px solid rgba(37, 99, 235, 0.2);
- }
- .logo-text {
- display: flex;
- flex-direction: column;
- }
- .logo-title {
- font-size: 18px;
- font-weight: 700;
- color: var(--text-primary);
- letter-spacing: 0.5px;
- }
- .logo-subtitle {
- font-size: 11px;
- color: var(--text-tertiary);
- margin-top: 2px;
- }
- /* 导航菜单 */
- .sidebar-nav {
- flex: 1;
- padding: 0 12px;
- display: flex;
- flex-direction: column;
- gap: 4px;
- }
- .nav-item {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 16px;
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--transition-normal);
- position: relative;
- color: var(--text-secondary);
- }
- .nav-item:hover {
- background: rgba(37, 99, 235, 0.08);
- color: var(--text-primary);
- }
- .nav-item.active {
- background: rgba(37, 99, 235, 0.12);
- color: var(--color-accent);
- }
- .nav-item.sub-item {
- padding: 10px 16px 10px 48px;
- }
- .nav-item-icon {
- width: 20px;
- height: 20px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 18px;
- }
- .nav-item-label {
- font-size: 14px;
- font-weight: 500;
- }
- .nav-item-indicator {
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 3px;
- height: 20px;
- background: var(--gradient-accent);
- border-radius: 0 3px 3px 0;
- box-shadow: 0 0 10px rgba(37, 99, 235, 0.5);
- }
- /* 分组 */
- .nav-group-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 16px;
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--transition-normal);
- color: var(--text-secondary);
- }
- .nav-group-header:hover {
- background: rgba(37, 99, 235, 0.08);
- color: var(--text-primary);
- }
- .nav-group-header.active {
- color: var(--text-primary);
- }
- .nav-group-left {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .nav-group-arrow {
- transition: transform 0.2s;
- color: var(--text-tertiary);
- }
- .nav-group-arrow.expanded {
- transform: rotate(180deg);
- }
- .nav-group-children {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- /* 底部 */
- .sidebar-footer {
- padding: 16px 20px 24px;
- }
- .footer-line {
- height: 1px;
- background: var(--border-color);
- margin-bottom: 16px;
- }
- .footer-text {
- font-size: 11px;
- color: var(--text-muted);
- text-align: center;
- letter-spacing: 1px;
- text-transform: uppercase;
- }
- </style>
|