Files
YG_FT/frontend/src/components/AppSidebar.vue
caoxiaozhu c1893cf82c feat: 实现通用组件
顶部栏、侧边栏、分页表格、Markdown 渲染、模型选择对话框、模型状态标签、页面卡片、占位视图等八个公共组件,及 Font Awesome 图标资源。
2026-07-10 16:45:25 +08:00

282 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
/** 当前激活菜单 key取路由路径第一段如 /fine-tune/create → fine-tune */
const activeMenu = computed(() => {
const seg = route.path.split('/')[1] || 'fine-tune'
// 训练日志归到模型调优
if (seg === 'training-log') return 'fine-tune'
// 维度管理归到模型评测
if (route.path.includes('model-eval/dimension')) return 'model-eval'
// 对比对话归到模型推理
if (route.path.startsWith('/model-compare/chat')) return 'model-inference'
// 合并权重归到模型管理
if (route.path === '/model-manage/merge') return 'model-manage'
// 模型管理编辑
if (route.path.match(/^\/model-manage\/\d+\/edit/)) return 'model-manage'
// 数据集编辑/预览
if (route.path.match(/^\/dataset\/(\d+)/)) return 'dataset'
// 工具编辑
if (route.path.match(/^\/tools\/[^/]+\/edit/)) return 'tools'
return seg
})
const menuGroups = [
{
// 服务看板:独立入口,不显示分类小标题
showTitle: false,
items: [{ key: 'dashboard', label: '服务看板', icon: 'fa-tachometer', to: '/dashboard' }],
},
{
title: '模型服务',
items: [
{ key: 'fine-tune', label: '模型微调', icon: 'fa-cogs', to: '/fine-tune' },
{ key: 'model-eval', label: '模型评测', icon: 'fa-line-chart', to: '/model-eval' },
{ key: 'model-inference', label: '模型推理', icon: 'fa-server', to: '/model-inference' },
{ key: 'model-manage', label: '模型管理', icon: 'fa-cube', to: '/model-manage' },
],
},
{
title: '数据治理',
items: [
{ key: 'data-process', label: '数据处理', icon: 'fa-filter', to: '/data-process' },
{ key: 'dataset', label: '数据集管理', icon: 'fa-file-text', to: '/dataset' },
],
},
{
title: '其他工具',
items: [{ key: 'data-convert', label: '数据类型转换', icon: 'fa-exchange', to: '/data-convert' }],
},
{
title: '系统设置',
items: [
{ key: 'hardware', label: '平台性能', icon: 'fa-bar-chart', to: '/hardware' },
{ key: 'logs', label: '查看日志', icon: 'fa-file-text', to: '/logs' },
],
},
]
function handleSelect(key: string) {
const all = menuGroups.flatMap((g) => g.items)
const target = all.find((i) => i.key === key)
if (target) router.push(target.to)
}
function handleLogout() {
auth.logout()
router.push('/login')
}
</script>
<template>
<aside class="app-sidebar">
<!-- 平台 LOGO -->
<div class="sidebar-logo">
<img src="/logo.png" alt="Logo" class="sidebar-logo-img" />
<span class="logo-text">远光软件微调平台</span>
</div>
<!-- 导航 -->
<el-scrollbar class="sidebar-nav" wrap-class="nav-scrollbar-wrap">
<el-menu
:default-active="activeMenu"
class="sidebar-menu"
background-color="transparent"
text-color="var(--sidebar-text)"
active-text-color="var(--sidebar-active-text)"
@select="handleSelect"
>
<template v-for="group in menuGroups" :key="group.title || group.items[0].key">
<div v-if="group.title" class="sidebar-section-title">{{ group.title }}</div>
<el-menu-item
v-for="item in group.items"
:key="item.key"
:index="item.key"
>
<i class="fa" :class="item.icon" />
<span>{{ item.label }}</span>
</el-menu-item>
</template>
</el-menu>
</el-scrollbar>
<!-- 底部信息 -->
<div class="sidebar-footer">
<div class="footer-user" v-if="auth.username">
<el-avatar :size="36" src="https://picsum.photos/id/1005/36/36" class="user-avatar" />
<div class="user-info">
<span class="user-name">{{ auth.username }}</span>
<span class="user-role">Administrator</span>
</div>
<el-tooltip content="退出登录" placement="top">
<div class="logout-btn" @click="handleLogout">
<i class="fa fa-sign-out" />
</div>
</el-tooltip>
</div>
</div>
</aside>
</template>
<style scoped lang="scss">
.app-sidebar {
width: 240px;
flex-shrink: 0;
height: 100vh;
background-color: var(--sidebar-bg);
display: flex;
flex-direction: column;
color: var(--sidebar-text);
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.02);
z-index: 100;
border-right: 1px solid #e2e8f0;
}
.sidebar-logo {
display: flex;
align-items: center;
padding: 24px 20px;
margin-bottom: 8px;
.sidebar-logo-img {
width: 36px;
height: 36px;
object-fit: contain;
margin-right: 12px;
}
.logo-text {
color: #334155;
font-weight: 600;
font-size: 16px;
letter-spacing: 0.5px;
white-space: nowrap;
}
}
.sidebar-nav {
flex: 1;
min-height: 0;
padding: 0 12px;
}
:deep(.nav-scrollbar-wrap) {
overflow-x: hidden;
}
.sidebar-menu {
border-right: none;
background: transparent;
:deep(.el-menu-item) {
height: 46px;
line-height: 46px;
margin-bottom: 4px;
border-radius: 8px;
padding: 0 16px !important;
transition: all 0.3s ease;
.fa {
width: 20px;
text-align: center;
margin-right: 10px;
font-size: 16px;
transition: transform 0.2s;
}
&:hover {
background-color: rgba(0, 0, 0, 0.03) !important;
color: var(--sidebar-active-text) !important;
.fa {
transform: scale(1.1);
}
}
&.is-active {
background-color: var(--sidebar-active-bg) !important;
color: var(--sidebar-active-text) !important;
font-weight: 600;
.fa {
color: inherit;
}
}
}
}
.sidebar-section-title {
padding: 16px 16px 8px;
font-size: 11px;
color: #64748b;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.sidebar-footer {
padding: 20px;
background: transparent;
border-top: 1px solid #e2e8f0;
.footer-user {
display: flex;
align-items: center;
background: #ffffff;
padding: 10px;
border-radius: 12px;
border: 1px solid #e2e8f0;
.user-avatar {
border: 2px solid #e2e8f0;
}
.user-info {
flex: 1;
margin-left: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
.user-name {
color: #334155;
font-size: 14px;
font-weight: 600;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.user-role {
color: #64748b;
font-size: 11px;
margin-top: 2px;
}
}
.logout-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
color: #94a3b8;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: rgba(239, 68, 68, 0.1);
color: var(--danger-color);
}
}
}
}
</style>