mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-08-12 01:29:45 +08:00
feat: update the layout mode
This commit is contained in:
parent
fcd28fc9d5
commit
4e08b796c0
@ -31,7 +31,7 @@
|
||||
- Secondary encapsulation of commonly used components to meet basic work requirements.
|
||||
- Dark theme adaptation, maintaining the Naive style for interface aesthetics.
|
||||
- Only performs eslint validation during submission without excessive restrictions for simpler development.
|
||||
- Flexible and configurable interface style layout.
|
||||
- Flexible and configurable interface layout based on [pro-naive-ui](https://github.com/Zheng-Changfu/pro-naive-ui)
|
||||
- Multilanguage (i18n) support.
|
||||
|
||||
## Project preview
|
||||
|
@ -31,7 +31,7 @@
|
||||
- 对日常使用频率较高的组件二次封装,满足基础工作需求
|
||||
- 黑暗主题适配, 界面样式保持Naive风格
|
||||
- 仅在提交时进行eslint校验,没有过多限制,开发更简便
|
||||
- 界面样式布局灵活可配置
|
||||
- 基于[pro-naive-ui](https://github.com/Zheng-Changfu/pro-naive-ui)的界面布局,灵活可配置
|
||||
- 多语言(i18n)支持
|
||||
|
||||
## 项目预览
|
||||
|
@ -59,9 +59,12 @@
|
||||
"backHome": "Back to the homepage",
|
||||
"getRouteError": "Failed to obtain route, please try again later.",
|
||||
"layoutSetting": "Layout settings",
|
||||
"leftMenu": "Left menu",
|
||||
"topMenu": "Top menu",
|
||||
"mixMenu": "Mix menu"
|
||||
"verticalLayout": "Vertical layout",
|
||||
"horizontalLayout": "Horizontal layout",
|
||||
"twoColumnLayout": "Two column layout",
|
||||
"mixedTwoColumnLayout": "Mixed two column layout",
|
||||
"sidebarLayout": "Sidebar layout",
|
||||
"mixedSidebarLayout": "Mixed sidebar layout"
|
||||
},
|
||||
"login": {
|
||||
"signInTitle": "Login",
|
||||
|
@ -59,9 +59,12 @@
|
||||
"backHome": "回到首页",
|
||||
"getRouteError": "获取路由失败,请稍后再试",
|
||||
"layoutSetting": "布局设置",
|
||||
"leftMenu": "左侧菜单",
|
||||
"topMenu": "顶部菜单",
|
||||
"mixMenu": "混合菜单"
|
||||
"verticalLayout": "竖向布局",
|
||||
"horizontalLayout": "横向布局",
|
||||
"twoColumnLayout": "双栏布局",
|
||||
"mixedTwoColumnLayout": "混合双栏布局",
|
||||
"sidebarLayout": "侧边栏布局",
|
||||
"mixedSidebarLayout": "双栏布局"
|
||||
},
|
||||
"http": {
|
||||
"400": "请求出现语法错误",
|
||||
|
@ -57,6 +57,7 @@
|
||||
"md-editor-v3": "^5.6.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"pro-naive-ui": "^2.4.3",
|
||||
"quill": "^2.0.3",
|
||||
"radash": "^12.1.0",
|
||||
"vue": "^3.5.16",
|
||||
|
22
src/layouts/Content.vue
Normal file
22
src/layouts/Content.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const routeStore = useRouteStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-el>
|
||||
<router-view
|
||||
v-slot="{ Component, route }"
|
||||
class="flex-1 p-16px"
|
||||
style="background-color: var(--action-color);"
|
||||
>
|
||||
<transition :name="appStore.transitionAnimation" mode="out-in">
|
||||
<keep-alive :include="routeStore.cacheRoutes">
|
||||
<component :is="Component" v-if="appStore.loadFlag" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</n-el>
|
||||
</template>
|
@ -1,63 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import type { LayoutMode } from '@/store/app'
|
||||
import type { ProLayoutMode } from 'pro-naive-ui'
|
||||
|
||||
const value = defineModel<LayoutMode>('value', { required: true })
|
||||
const value = defineModel<ProLayoutMode>('value', { required: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-center gap-4">
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<div class="selector-wapper gap-4">
|
||||
<n-tooltip placement="top" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'leftMenu',
|
||||
'outline outline-2': value === 'vertical',
|
||||
}"
|
||||
class="grid grid-cols-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'leftMenu'"
|
||||
@click="value = 'vertical'"
|
||||
>
|
||||
<div class="bg-[var(--primary-color)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.leftMenu') }} </span>
|
||||
<span> {{ $t('app.verticalLayout') }} </span>
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<n-tooltip placement="top" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'topMenu',
|
||||
'outline outline-2': value === 'horizontal',
|
||||
}"
|
||||
class="grid grid-rows-[30%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'topMenu'"
|
||||
@click="value = 'horizontal'"
|
||||
>
|
||||
<div class="bg-[var(--primary-color)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.topMenu') }} </span>
|
||||
<span> {{ $t('app.horizontalLayout') }} </span>
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip placement="top" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'two-column',
|
||||
}"
|
||||
class="grid grid-cols-[10%_15%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'two-column'"
|
||||
>
|
||||
<div class="bg-[var(--primary-color)]" />
|
||||
<div class="bg-[var(--primary-color-suppl)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.twoColumnLayout') }} </span>
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'mixMenu',
|
||||
'outline outline-2': value === 'mixed-two-column',
|
||||
}"
|
||||
class="grid grid-cols-[20%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'mixMenu'"
|
||||
class="grid grid-cols-[10%_15%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'mixed-two-column'"
|
||||
>
|
||||
<div class="bg-[var(--primary-color)] row-span-2" />
|
||||
<div class="bg-[var(--primary-color-suppl)] row-span-2" />
|
||||
<div class="bg-[var(--primary-color-suppl)] row-span-2" />
|
||||
<div class="bg-[var(--primary-color)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.mixMenu') }} </span>
|
||||
<span> {{ $t('app.mixedTwoColumnLayout') }} </span>
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'sidebar',
|
||||
}"
|
||||
class="grid grid-cols-[20%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'sidebar'"
|
||||
>
|
||||
<div class="bg-[var(--divider-color)] col-span-2" />
|
||||
<div class="bg-[var(--primary-color)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.sidebarLayout') }} </span>
|
||||
</n-tooltip>
|
||||
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-el
|
||||
:class="{
|
||||
'outline outline-2': value === 'mixed-sidebar',
|
||||
}"
|
||||
class="grid grid-cols-[20%_1fr] grid-rows-[20%_1fr] outline-[var(--primary-color)] hover:(outline outline-2) cursor-pointer"
|
||||
@click="value = 'mixed-sidebar'"
|
||||
>
|
||||
<div class="bg-[var(--primary-color)] col-span-2" />
|
||||
<div class="bg-[var(--primary-color-suppl)]" />
|
||||
<div class="bg-[var(--divider-color)]" />
|
||||
</n-el>
|
||||
</template>
|
||||
<span> {{ $t('app.mixedSidebarLayout') }} </span>
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.selector-wapper{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.grid{
|
||||
height: 60px;
|
||||
width: 86px;
|
||||
|
@ -5,6 +5,16 @@ const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
|
||||
const name = import.meta.env.VITE_APP_NAME
|
||||
|
||||
const hidenLogoText = computed(() => {
|
||||
if (['sidebar', 'mixed-sidebar', 'horizontal'].includes(appStore.layoutMode)) {
|
||||
return false
|
||||
}
|
||||
if (['two-column', 'mixed-two-column'].includes(appStore.layoutMode)) {
|
||||
return true
|
||||
}
|
||||
return appStore.collapsed
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -14,7 +24,7 @@ const name = import.meta.env.VITE_APP_NAME
|
||||
>
|
||||
<svg-icons-logo class="text-1.5em" />
|
||||
<span
|
||||
v-show="!appStore.collapsed"
|
||||
v-show="!hidenLogoText"
|
||||
class="text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
>{{ name }}</span>
|
||||
</div>
|
@ -1,6 +1,7 @@
|
||||
import BackTop from './common/BackTop.vue'
|
||||
import Setting from './common/Setting.vue'
|
||||
import SettingDrawer from './common/SettingDrawer.vue'
|
||||
import Logo from './common/Logo.vue'
|
||||
|
||||
import Breadcrumb from './header/Breadcrumb.vue'
|
||||
import CollapaseButton from './header/CollapaseButton.vue'
|
||||
@ -9,9 +10,6 @@ import Notices from './header/Notices.vue'
|
||||
import Search from './header/Search.vue'
|
||||
import UserCenter from './header/UserCenter.vue'
|
||||
|
||||
import Logo from './sider/Logo.vue'
|
||||
import Menu from './sider/Menu.vue'
|
||||
|
||||
import TabBar from './tab/TabBar.vue'
|
||||
|
||||
export {
|
||||
@ -20,7 +18,6 @@ export {
|
||||
CollapaseButton,
|
||||
FullScreen,
|
||||
Logo,
|
||||
Menu,
|
||||
Notices,
|
||||
Search,
|
||||
Setting,
|
||||
|
@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { MenuInst } from 'naive-ui'
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
|
||||
const route = useRoute()
|
||||
const appStore = useAppStore()
|
||||
const routeStore = useRouteStore()
|
||||
|
||||
const menuInstRef = ref<MenuInst | null>(null)
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
menuInstRef.value?.showOption(routeStore.activeMenu as string)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-menu
|
||||
ref="menuInstRef"
|
||||
:collapsed="appStore.collapsed"
|
||||
:indent="20"
|
||||
:collapsed-width="64"
|
||||
:options="routeStore.menus"
|
||||
:value="routeStore.activeMenu"
|
||||
/>
|
||||
</template>
|
@ -114,7 +114,7 @@ useDraggable(el, tabs, {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-scrollbar ref="scrollbar" class="relative flex h-full tab-bar-scroller-wrapper" content-class="pr-34 tab-bar-scroller-content" :x-scrollable="true" @wheel="onWheel">
|
||||
<n-scrollbar ref="scrollbar" class="relative flex h-full tab-bar-scroller-wrapper" content-class="h-full pr-34 tab-bar-scroller-content" :x-scrollable="true" @wheel="onWheel">
|
||||
<div class="p-l-2 flex wh-full relative">
|
||||
<div class="flex items-end">
|
||||
<TabBarItem
|
||||
|
@ -1,19 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { SettingDrawer } from './components'
|
||||
import leftMenu from './leftMenu.layout.vue'
|
||||
import mixMenu from './mixMenu.layout.vue'
|
||||
import topMenu from './topMenu.layout.vue'
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
import {
|
||||
BackTop,
|
||||
Breadcrumb,
|
||||
CollapaseButton,
|
||||
FullScreen,
|
||||
Logo,
|
||||
Notices,
|
||||
Search,
|
||||
Setting,
|
||||
SettingDrawer,
|
||||
TabBar,
|
||||
UserCenter,
|
||||
} from './components'
|
||||
import Content from './Content.vue'
|
||||
|
||||
import { ProLayout, useLayoutMenu } from 'pro-naive-ui'
|
||||
|
||||
const route = useRoute()
|
||||
const appStore = useAppStore()
|
||||
const layoutMap = {
|
||||
leftMenu,
|
||||
topMenu,
|
||||
mixMenu,
|
||||
}
|
||||
const routeStore = useRouteStore()
|
||||
const { layoutMode } = storeToRefs(useAppStore())
|
||||
|
||||
const {
|
||||
layout,
|
||||
activeKey,
|
||||
} = useLayoutMenu({
|
||||
mode: layoutMode,
|
||||
accordion: true,
|
||||
menus: routeStore.menus,
|
||||
})
|
||||
|
||||
watch(() => route.path, (value) => {
|
||||
activeKey.value = value
|
||||
}, { immediate: true })
|
||||
|
||||
const sidebarWidth = ref(240)
|
||||
const sidebarCollapsedWidth = ref(64)
|
||||
|
||||
const hasHorizontalMenu = computed(() => ['horizontal', 'mixed-two-column', 'mixed-sidebar'].includes(layoutMode.value))
|
||||
|
||||
const hidenCollapaseButton = computed(() => ['horizontal'].includes(layoutMode.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SettingDrawer />
|
||||
<component :is="layoutMap[appStore.layoutMode]" />
|
||||
<ProLayout
|
||||
v-model:collapsed="appStore.collapsed"
|
||||
:mode="layoutMode"
|
||||
:show-logo="appStore.showLogo"
|
||||
:show-footer="appStore.showFooter"
|
||||
:show-tabbar="appStore.showTabs"
|
||||
nav-fixed
|
||||
show-nav
|
||||
show-sidebar
|
||||
:nav-height="60"
|
||||
:tabbar-height="45"
|
||||
:footer-height="40"
|
||||
:sidebar-width="sidebarWidth"
|
||||
:sidebar-collapsed-width="sidebarCollapsedWidth"
|
||||
>
|
||||
<template #logo>
|
||||
<Logo />
|
||||
</template>
|
||||
|
||||
<template #nav-left>
|
||||
<div v-if="!hasHorizontalMenu || !hidenCollapaseButton" class="h-full flex-y-center gap-1 p-x-sm">
|
||||
<CollapaseButton v-if="!hidenCollapaseButton" />
|
||||
<Breadcrumb v-if="!hasHorizontalMenu" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #nav-center>
|
||||
<div class="h-full flex-y-center gap-1">
|
||||
<n-menu v-if="hasHorizontalMenu" v-bind="layout.horizontalMenuProps" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #nav-right>
|
||||
<div class="h-full flex-y-center gap-1 p-x-xl">
|
||||
<Search />
|
||||
<Notices />
|
||||
<FullScreen />
|
||||
<DarkModeSwitch />
|
||||
<LangsSwitch />
|
||||
<Setting />
|
||||
<UserCenter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #sidebar>
|
||||
<n-menu v-bind="layout.verticalMenuProps" :collapsed-width="sidebarCollapsedWidth" />
|
||||
</template>
|
||||
|
||||
<template #sidebar-extra>
|
||||
<n-scrollbar class="flex-[1_0_0]">
|
||||
<n-menu v-bind="layout.verticalExtraMenuProps" :collapsed-width="sidebarCollapsedWidth" />
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<template #tabbar>
|
||||
<TabBar />
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex-center h-full">
|
||||
{{ appStore.footerText }}
|
||||
</div>
|
||||
</template>
|
||||
<Content />
|
||||
<BackTop />
|
||||
<SettingDrawer />
|
||||
</ProLayout>
|
||||
</template>
|
||||
|
@ -1,102 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
import {
|
||||
BackTop,
|
||||
Breadcrumb,
|
||||
CollapaseButton,
|
||||
FullScreen,
|
||||
Logo,
|
||||
Menu,
|
||||
Notices,
|
||||
Search,
|
||||
Setting,
|
||||
TabBar,
|
||||
UserCenter,
|
||||
} from './components'
|
||||
|
||||
const routeStore = useRouteStore()
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-layout
|
||||
has-sider
|
||||
class="wh-full"
|
||||
embedded
|
||||
>
|
||||
<n-layout-sider
|
||||
v-if="!appStore.contentFullScreen"
|
||||
bordered
|
||||
:collapsed="appStore.collapsed"
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="240"
|
||||
content-style="display: flex;flex-direction: column;min-height:100%;"
|
||||
>
|
||||
<Logo v-if="appStore.showLogo" />
|
||||
<n-scrollbar class="flex-1">
|
||||
<Menu />
|
||||
</n-scrollbar>
|
||||
</n-layout-sider>
|
||||
<n-layout
|
||||
class="h-full flex flex-col"
|
||||
content-style="display: flex;flex-direction: column;min-height:100%;"
|
||||
embedded
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<n-layout-header bordered position="absolute" class="z-999">
|
||||
<div v-if="!appStore.contentFullScreen" class="h-60px flex-y-center justify-between">
|
||||
<div class="flex-y-center h-full">
|
||||
<CollapaseButton />
|
||||
<Breadcrumb />
|
||||
</div>
|
||||
<div class="flex-y-center gap-1 h-full p-x-xl">
|
||||
<Search />
|
||||
<Notices />
|
||||
<FullScreen />
|
||||
<DarkModeSwitch />
|
||||
<LangsSwitch />
|
||||
<Setting />
|
||||
<UserCenter />
|
||||
</div>
|
||||
</div>
|
||||
<TabBar v-if="appStore.showTabs" class="h-45px" />
|
||||
</n-layout-header>
|
||||
<!-- 121 = 16 + 45 + 60 45是面包屑高度 60是标签栏高度 -->
|
||||
<!-- 56 = 16 + 40 40是页脚高度 -->
|
||||
<div
|
||||
class="flex-1 p-16px flex flex-col"
|
||||
:class="{
|
||||
'p-t-121px': appStore.showTabs,
|
||||
'p-b-56px': appStore.showFooter && !appStore.contentFullScreen,
|
||||
'p-t-76px': !appStore.showTabs,
|
||||
'p-t-61px': appStore.contentFullScreen,
|
||||
}"
|
||||
>
|
||||
<router-view v-slot="{ Component, route }" class="flex-1">
|
||||
<transition
|
||||
:name="appStore.transitionAnimation"
|
||||
mode="out-in"
|
||||
>
|
||||
<keep-alive :include="routeStore.cacheRoutes">
|
||||
<component
|
||||
:is="Component"
|
||||
v-if="appStore.loadFlag"
|
||||
:key="route.fullPath"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<n-layout-footer
|
||||
v-if="appStore.showFooter && !appStore.contentFullScreen"
|
||||
bordered
|
||||
position="absolute"
|
||||
class="h-40px flex-center"
|
||||
>
|
||||
{{ appStore.footerText }}
|
||||
</n-layout-footer>
|
||||
<BackTop />
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</template>
|
@ -1,160 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MenuInst, MenuOption } from 'naive-ui'
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
import {
|
||||
BackTop,
|
||||
CollapaseButton,
|
||||
FullScreen,
|
||||
Logo,
|
||||
Notices,
|
||||
Search,
|
||||
Setting,
|
||||
TabBar,
|
||||
UserCenter,
|
||||
} from './components'
|
||||
|
||||
const routeStore = useRouteStore()
|
||||
const appStore = useAppStore()
|
||||
const pageRoute = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const menuInstRef = ref<MenuInst | null>(null)
|
||||
|
||||
watch(
|
||||
() => pageRoute.path,
|
||||
() => {
|
||||
menuInstRef.value?.showOption(routeStore.activeMenu as string)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const topMenu = ref<MenuOption[]>([])
|
||||
const activeTopMenu = ref<string>('')
|
||||
function handleTopMenu(rowMenu: MenuOption[]) {
|
||||
topMenu.value = rowMenu.map((i) => {
|
||||
const { icon, label, key } = i
|
||||
return {
|
||||
icon,
|
||||
label,
|
||||
key,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleTopMenu(routeStore.menus)
|
||||
|
||||
// 根据当前页面获取选中菜单和对应侧边菜单
|
||||
const currentMenuKey = pageRoute.matched[1].path
|
||||
handleSideMenu(currentMenuKey)
|
||||
activeTopMenu.value = currentMenuKey
|
||||
})
|
||||
|
||||
const sideMenu = ref<MenuOption[]>([])
|
||||
function handleSideMenu(key: string) {
|
||||
const routeMenu = routeStore.menus as MenuOption[]
|
||||
const targetMenu = routeMenu.find(i => i.key === key)
|
||||
if (targetMenu) {
|
||||
sideMenu.value = targetMenu.children ? targetMenu.children : [targetMenu]
|
||||
}
|
||||
}
|
||||
|
||||
function updateTopMenu(key: string) {
|
||||
handleSideMenu(key)
|
||||
activeTopMenu.value = key
|
||||
router.push(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-layout
|
||||
has-sider
|
||||
class="wh-full"
|
||||
embedded
|
||||
>
|
||||
<n-layout-sider
|
||||
v-if="!appStore.contentFullScreen"
|
||||
bordered
|
||||
:collapsed="appStore.collapsed"
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="240"
|
||||
content-style="display: flex;flex-direction: column;min-height:100%;"
|
||||
>
|
||||
<Logo v-if="appStore.showLogo" />
|
||||
<n-scrollbar class="flex-1">
|
||||
<n-menu
|
||||
ref="menuInstRef"
|
||||
:collapsed="appStore.collapsed"
|
||||
:indent="20"
|
||||
:collapsed-width="64"
|
||||
:options="sideMenu"
|
||||
:value="routeStore.activeMenu"
|
||||
/>
|
||||
</n-scrollbar>
|
||||
</n-layout-sider>
|
||||
<n-layout
|
||||
class="h-full flex flex-col"
|
||||
content-style="display: flex;flex-direction: column;min-height:100%;"
|
||||
embedded
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
<n-layout-header bordered position="absolute" class="z-999">
|
||||
<div v-if="!appStore.contentFullScreen" class="h-60px flex-y-center justify-between">
|
||||
<CollapaseButton />
|
||||
<n-menu
|
||||
ref="menuInstRef"
|
||||
mode="horizontal"
|
||||
responsive
|
||||
:options="topMenu"
|
||||
:value="activeTopMenu"
|
||||
@update:value="updateTopMenu"
|
||||
/>
|
||||
<div class="flex-y-center gap-1 h-full p-x-xl">
|
||||
<Search />
|
||||
<Notices />
|
||||
<FullScreen />
|
||||
<DarkModeSwitch />
|
||||
<LangsSwitch />
|
||||
<Setting />
|
||||
<UserCenter />
|
||||
</div>
|
||||
</div>
|
||||
<TabBar v-if="appStore.showTabs" class="h-45px" />
|
||||
</n-layout-header>
|
||||
<div
|
||||
class="flex-1 p-16px flex flex-col"
|
||||
:class="{
|
||||
'p-t-121px': appStore.showTabs,
|
||||
'p-b-56px': appStore.showFooter && !appStore.contentFullScreen,
|
||||
'p-t-76px': !appStore.showTabs,
|
||||
'p-t-61px': appStore.contentFullScreen,
|
||||
}"
|
||||
>
|
||||
<router-view v-slot="{ Component, route }" class="flex-1">
|
||||
<transition
|
||||
:name="appStore.transitionAnimation"
|
||||
mode="out-in"
|
||||
>
|
||||
<keep-alive :include="routeStore.cacheRoutes">
|
||||
<component
|
||||
:is="Component"
|
||||
v-if="appStore.loadFlag"
|
||||
:key="route.fullPath"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<n-layout-footer
|
||||
v-if="appStore.showFooter && !appStore.contentFullScreen"
|
||||
bordered
|
||||
position="absolute"
|
||||
class="h-40px flex-center"
|
||||
>
|
||||
{{ appStore.footerText }}
|
||||
</n-layout-footer>
|
||||
<BackTop />
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</template>
|
@ -1,67 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { useAppStore, useRouteStore } from '@/store'
|
||||
import {
|
||||
BackTop,
|
||||
FullScreen,
|
||||
Logo,
|
||||
Menu,
|
||||
Notices,
|
||||
Search,
|
||||
Setting,
|
||||
TabBar,
|
||||
UserCenter,
|
||||
} from './components'
|
||||
|
||||
const routeStore = useRouteStore()
|
||||
const appStore = useAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-layout class="wh-full" embedded>
|
||||
<n-layout
|
||||
class="h-full flex flex-col" content-style="display: flex;flex-direction: column;min-height:100%;"
|
||||
embedded :native-scrollbar="false"
|
||||
>
|
||||
<n-layout-header bordered position="absolute" class="z-999">
|
||||
<div v-if="!appStore.contentFullScreen" class="h-60px flex-y-center justify-between shrink-0">
|
||||
<Logo v-if="appStore.showLogo" />
|
||||
<Menu mode="horizontal" responsive />
|
||||
<div class="flex-y-center gap-1 h-full p-x-xl">
|
||||
<Search />
|
||||
<Notices />
|
||||
<FullScreen />
|
||||
<DarkModeSwitch />
|
||||
<LangsSwitch />
|
||||
<Setting />
|
||||
<UserCenter />
|
||||
</div>
|
||||
</div>
|
||||
<TabBar v-if="appStore.showTabs" class="h-45px" />
|
||||
</n-layout-header>
|
||||
<div
|
||||
class="flex-1 p-16px flex flex-col"
|
||||
:class="{
|
||||
'p-t-121px': appStore.showTabs,
|
||||
'p-b-56px': appStore.showFooter && !appStore.contentFullScreen,
|
||||
'p-t-76px': !appStore.showTabs,
|
||||
'p-t-61px': appStore.contentFullScreen,
|
||||
}"
|
||||
>
|
||||
<router-view v-slot="{ Component, route }" class="flex-1">
|
||||
<transition :name="appStore.transitionAnimation" mode="out-in">
|
||||
<keep-alive :include="routeStore.cacheRoutes">
|
||||
<component :is="Component" v-if="appStore.loadFlag" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<n-layout-footer
|
||||
v-if="appStore.showFooter && !appStore.contentFullScreen"
|
||||
bordered position="absolute" class="h-40px flex-center"
|
||||
>
|
||||
{{ appStore.footerText }}
|
||||
</n-layout-footer>
|
||||
<BackTop />
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</template>
|
@ -3,9 +3,9 @@ import { local, setLocale } from '@/utils'
|
||||
import { colord } from 'colord'
|
||||
import { set } from 'radash'
|
||||
import themeConfig from './theme.json'
|
||||
import type { ProLayoutMode } from 'pro-naive-ui'
|
||||
|
||||
export type TransitionAnimation = '' | 'fade-slide' | 'fade-bottom' | 'fade-scale' | 'zoom-fade' | 'zoom-out'
|
||||
export type LayoutMode = 'leftMenu' | 'topMenu' | 'mixMenu'
|
||||
|
||||
const { VITE_DEFAULT_LANG, VITE_COPYRIGHT_INFO } = import.meta.env
|
||||
|
||||
@ -37,7 +37,7 @@ export const useAppStore = defineStore('app-store', {
|
||||
showWatermark: false,
|
||||
showSetting: false,
|
||||
transitionAnimation: 'fade-slide' as TransitionAnimation,
|
||||
layoutMode: 'leftMenu' as LayoutMode,
|
||||
layoutMode: 'vertical' as ProLayoutMode,
|
||||
contentFullScreen: false,
|
||||
}
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user