U:优化页面控制栏
This commit is contained in:
1 parent
3122a80cc3
commit
8e2b4cb785
1 file changed
+51
-166
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<n-layout class="layout-container">
|
||||
<template>
|
||||
<div class="app-layout">
|
||||
<!-- 头部 -->
|
||||
<n-layout-header bordered class="layout-header">
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<!-- 左侧 Logo 和标题 -->
|
||||
<router-link to="/" class="header-left no-select">
|
||||
<n-icon size="32" class="logo-icon">
|
||||
<img src="@/assets/logo.svg" alt="logo" />
|
||||
</n-icon>
|
||||
<span class="site-title">Argus</span>
|
||||
<!-- 使用 v-show 避免折叠时文字换行导致的跳动 -->
|
||||
<span class="site-title" v-show="!collapsed">Argus</span>
|
||||
</router-link>
|
||||
|
||||
<!-- 中间搜索框 -->
|
||||
@@ -32,22 +33,25 @@
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</n-layout-header>
|
||||
</header>
|
||||
|
||||
<!-- 主体布局 -->
|
||||
<n-layout has-sider class="layout-body">
|
||||
<!-- 主体布局 (Flex Container) -->
|
||||
<div class="app-body">
|
||||
<!-- 左侧导航栏 -->
|
||||
<!-- 注意:这里保留 n-layout-sider 是为了利用它的折叠动画和样式 -->
|
||||
<n-layout-sider
|
||||
bordered
|
||||
show-trigger
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="290"
|
||||
:native-scrollbar="false"
|
||||
class="layout-sider"
|
||||
class="app-sider"
|
||||
v-model:collapsed="collapsed"
|
||||
show-trigger
|
||||
>
|
||||
<n-menu
|
||||
v-model:value="activeKey"
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptionsWithIcons"
|
||||
@@ -58,22 +62,19 @@
|
||||
</n-layout-sider>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<n-layout-content class="layout-content">
|
||||
<main class="app-content">
|
||||
<div class="content-wrapper">
|
||||
<router-view />
|
||||
</div>
|
||||
</n-layout-content>
|
||||
</n-layout>
|
||||
|
||||
<!-- 底部(暂时隐藏) -->
|
||||
<n-layout-footer v-if="false" bordered> Footer Footer Footer</n-layout-footer>
|
||||
</n-layout>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed, h, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { NMenu, NIcon } from 'naive-ui'
|
||||
import { NMenu, NIcon, NLayoutSider, NInput } from 'naive-ui'
|
||||
import ThemeToggle from '@/components/ThemeToggle.vue'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import {
|
||||
@@ -96,7 +97,9 @@ const router = useRouter()
|
||||
const route = useRoute()
|
||||
const themeStore = useThemeStore()
|
||||
|
||||
// 初始化主题
|
||||
// 侧边栏折叠状态
|
||||
const collapsed = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
themeStore.initTheme()
|
||||
})
|
||||
@@ -117,17 +120,14 @@ const iconMap = {
|
||||
'archive-outline': ArchiveOutline,
|
||||
}
|
||||
|
||||
// 渲染图标
|
||||
const renderIcon = (iconName: string) => {
|
||||
const IconComponent = iconMap[iconName as keyof typeof iconMap]
|
||||
return IconComponent ? () => h(NIcon, null, { default: () => h(IconComponent) }) : undefined
|
||||
}
|
||||
|
||||
// 转换菜单选项,添加图标渲染函数
|
||||
const menuOptionsWithIcons = computed(() => {
|
||||
return menuOptions.map((option: MenuOption) => {
|
||||
if ('children' in option) {
|
||||
// 分组菜单
|
||||
const group: MenuGroup = option
|
||||
return {
|
||||
key: group.key,
|
||||
@@ -140,7 +140,6 @@ const menuOptionsWithIcons = computed(() => {
|
||||
})),
|
||||
}
|
||||
} else {
|
||||
// 普通菜单项
|
||||
const item: MenuItem = option
|
||||
return {
|
||||
key: item.key,
|
||||
@@ -152,11 +151,9 @@ const menuOptionsWithIcons = computed(() => {
|
||||
})
|
||||
|
||||
const activeKey = ref('')
|
||||
// 监听路由变化,更新activeKey
|
||||
watch(
|
||||
() => route.path,
|
||||
(currentPath) => {
|
||||
// 从菜单选项中找到匹配当前路径的key
|
||||
for (const option of menuOptions) {
|
||||
if ('children' in option) {
|
||||
for (const child of option.children) {
|
||||
@@ -177,11 +174,8 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 处理菜单选择
|
||||
const handleMenuSelect = (key: string) => {
|
||||
// 根据key找到对应的路径
|
||||
let targetPath = ''
|
||||
|
||||
for (const option of menuOptions) {
|
||||
if ('children' in option) {
|
||||
for (const child of option.children) {
|
||||
@@ -198,24 +192,28 @@ const handleMenuSelect = (key: string) => {
|
||||
}
|
||||
if (targetPath) break
|
||||
}
|
||||
|
||||
if (targetPath && targetPath !== route.path) {
|
||||
router.push(targetPath)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout-container {
|
||||
.app-layout {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
.app-header {
|
||||
height: var(--header-height);
|
||||
padding: 0;
|
||||
box-shadow: 0 1px 4px var(--shadow-color);
|
||||
z-index: 10;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background-color: var(--header-bg);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -224,22 +222,15 @@ const handleMenuSelect = (key: string) => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--spacing-lg);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
min-width: 200px;
|
||||
width: 200px; /* 固定宽度避免折叠时右侧内容跳动 */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-left:hover {
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
margin-right: var(--spacing-md);
|
||||
}
|
||||
@@ -247,13 +238,11 @@ const handleMenuSelect = (key: string) => {
|
||||
.logo-icon img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -262,7 +251,6 @@ const handleMenuSelect = (key: string) => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
@@ -271,155 +259,52 @@ const handleMenuSelect = (key: string) => {
|
||||
}
|
||||
|
||||
.header-right {
|
||||
flex-shrink: 0;
|
||||
min-width: 120px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.layout-body {
|
||||
height: calc(100vh - var(--header-height));
|
||||
overflow: hidden;
|
||||
/* 关键布局区域 */
|
||||
.app-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden; /* 防止body本身滚动 */
|
||||
}
|
||||
|
||||
.layout-sider {
|
||||
height: 100%;
|
||||
box-shadow: 2px 0 8px var(--shadow-color);
|
||||
margin-left: 7px;
|
||||
/* 侧边栏 */
|
||||
.app-sider {
|
||||
height: 100%; /* 撑满 body 高度 */
|
||||
background-color: var(--sidebar-bg);
|
||||
/* n-layout-sider 内部会自动处理 overflow */
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
height: 100%;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.layout-content {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
/* 内容区 */
|
||||
.app-content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden; /* 内部滚动 */
|
||||
background-color: var(--content-bg);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content-wrapper h1 {
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.content-wrapper p {
|
||||
color: var(--text-color-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.header-center {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.search-input {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Naive UI 组件主题深度适配 */
|
||||
:deep(.n-layout) {
|
||||
background-color: var(--bg-color) !important;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-header) {
|
||||
background-color: var(--header-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
overflow-y: auto; /* 只有内容区滚动 */
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Naive UI 覆盖 */
|
||||
:deep(.n-layout-sider) {
|
||||
background-color: var(--sidebar-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-content) {
|
||||
background-color: var(--content-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
:deep(.n-menu) {
|
||||
background-color: transparent !important;
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
:deep(.n-menu-item) {
|
||||
color: var(--text-color) !important;
|
||||
margin-right: 12px !important;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* 折叠状态下的菜单项样式 */
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item:hover) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-layout-sider--collapsed .n-menu-item.n-menu-item--selected) {
|
||||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-menu-item:hover) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
margin-right: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-menu-item.n-menu-item--selected) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
margin-right: 12px !important;
|
||||
border-radius: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-input) {
|
||||
background-color: var(--bg-color-secondary) !important;
|
||||
color: var(--text-color) !important;
|
||||
border-color: var(--border-color) !important;
|
||||
}
|
||||
|
||||
:deep(.n-input__input-el) {
|
||||
background-color: transparent !important;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
:deep(.n-input__placeholder) {
|
||||
color: var(--text-color-tertiary) !important;
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user