This commit is contained in:
2026-04-26 20:40:08 +08:00
parent 44ced193d5
commit aea0d42d52
2 changed files with 432 additions and 122 deletions
+180 -4
View File
@@ -1,7 +1,183 @@
<template>
<router-view />
<div class="app-shell">
<aside :class="['sidebar', { collapsed: isSidebarCollapsed }]">
<div class="sidebar-top">
<button class="sidebar-toggle" type="button" @click="toggleSidebar">
<el-icon><component :is="isSidebarCollapsed ? Expand : Fold" /></el-icon>
</button>
<div v-if="!isSidebarCollapsed" class="brand-block">
<span class="brand-title">Stock Lab</span>
<span class="brand-subtitle">页面导航</span>
</div>
</div>
<el-menu
:default-active="activeMenu"
:collapse="isSidebarCollapsed"
:collapse-transition="false"
class="nav-menu"
router
>
<el-menu-item
v-for="item in menuItems"
:key="item.path"
:index="item.path"
@click="navigateTo(item.path)"
>
<el-icon><DataAnalysis /></el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</el-menu>
</aside>
<main class="page-main">
<router-view />
</main>
</div>
</template>
<style>
body { margin: 0; background-color: #f5f7fa; }
</style>
<script setup>
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { DataAnalysis, Expand, Fold } from '@element-plus/icons-vue'
const route = useRoute()
const router = useRouter()
const isSidebarCollapsed = ref(false)
const menuItems = computed(() =>
router.options.routes
.filter((item) => item.path && item.meta?.title && !item.redirect)
.map((item) => ({
path: item.path,
title: item.meta.title
}))
)
const activeMenu = computed(() => route.path)
const toggleSidebar = () => {
isSidebarCollapsed.value = !isSidebarCollapsed.value
}
const navigateTo = (path) => {
if (route.path === path) return
router.push(path)
}
</script>
<style scoped>
.app-shell {
display: flex;
min-height: 100vh;
background:
linear-gradient(180deg, #f8fbff 0%, #f2f6fb 100%);
}
.sidebar {
width: 236px;
flex-shrink: 0;
display: flex;
flex-direction: column;
padding: 18px 14px;
box-sizing: border-box;
background: #ffffff;
border-right: 1px solid #e5edf5;
transition: width 0.25s ease, padding 0.25s ease;
}
.sidebar.collapsed {
width: 76px;
padding-inline: 10px;
}
.sidebar-top {
display: flex;
align-items: center;
gap: 12px;
min-height: 52px;
margin-bottom: 18px;
}
.sidebar-toggle {
width: 40px;
height: 40px;
border: 1px solid #dbe5f0;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
background: #f7fbff;
color: #245d9c;
cursor: pointer;
}
.sidebar-toggle:hover {
background: #eef6ff;
}
.brand-block {
display: flex;
flex-direction: column;
min-width: 0;
}
.brand-title {
font-size: 18px;
line-height: 24px;
font-weight: 700;
color: #1f2a37;
}
.brand-subtitle {
font-size: 12px;
color: #7c8aa5;
}
.nav-menu {
border-right: none;
background: transparent;
}
.page-main {
flex: 1;
min-width: 0;
}
:deep(.nav-menu .el-menu-item) {
height: 46px;
margin-bottom: 8px;
border-radius: 12px;
color: #41536d;
}
:deep(.nav-menu .el-menu-item .el-icon) {
color: #5f7ea5;
}
:deep(.nav-menu .el-menu-item:hover) {
background: #eef6ff;
color: #1f5fa8;
}
:deep(.nav-menu .el-menu-item.is-active) {
background: #e3f0ff;
color: #13559d;
}
:deep(.nav-menu.el-menu--collapse .el-menu-item) {
padding: 0;
justify-content: center;
}
@media (max-width: 820px) {
.sidebar {
width: 88px;
padding-inline: 10px;
}
.brand-block {
display: none;
}
}
</style>