feat:更新layout

This commit is contained in:
fangyunong 2025-07-03 20:19:09 +08:00
parent 02ea680fac
commit 19322ac91d
7 changed files with 271 additions and 121 deletions

View File

@ -2,6 +2,8 @@ import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Login from "@/views/login/index.vue"; //登录组件
import Layout from "@/views/layout/index.vue"; //首页布局
import CallBack from "@/views/login/OauthCallBack.vue"; //反馈页面
import Home from '@/views/home/index.vue'; //家
import Auth from '@/views/role/pages/Auth.vue'; //权限管理
const routes: Array<RouteRecordRaw> = [
{
@ -18,6 +20,19 @@ const routes: Array<RouteRecordRaw> = [
path: "/layout",
name: "Layout",
component: Layout,
children:[
{
path:'',
name:'home',
component:Home,
},
// 后续用动态菜单
{
path:'role',
name:'roleAuth',
component:Auth
}
]
},
];

8
src/views/home/index.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
<div>我老家</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang='scss'>
</style>

View File

@ -0,0 +1,125 @@
<template>
<n-layout-header bordered>
<div class="header-icon">
<img src="/logo.jpg">
<div class="header-icon__text">
<p class="cn">零枢</p>
<p class="en">ZeroNode</p>
</div>
</div>
<div class="header-avatar">
<div class="header-avatar__info">
<n-avatar src="@/assets/images/luolan_avatar.jpg" round :size="45"
fallback-src="https://07akioni.oss-cn-beijing.aliyuncs.com/07akioni.jpeg">
</n-avatar>
<p class="username">白盐浊泉</p>
<n-dropdown :options="options" @select="handleSelect">
<n-icon :size="16" color="#000" style="cursor: pointer;">
<CaretDownOutline />
</n-icon>
</n-dropdown>
</div>
</div>
</n-layout-header>
</template>
<script setup lang='ts'>
import { useAuth0 } from '@auth0/auth0-vue';
import {
Pencil as EditIcon,
LogOutOutline as LogoutIcon,
PersonCircleOutline as UserIcon,
CaretDownOutline
} from '@vicons/ionicons5';
import { NIcon } from 'naive-ui';
const { logout } = useAuth0();
const router = useRouter();
function renderIcon(icon: Component) {
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
}
const options = [
{
label: '用户资料',
key: 'profile',
icon: renderIcon(UserIcon)
},
{
label: '编辑用户资料',
key: 'editProfile',
icon: renderIcon(EditIcon)
},
{
label: '退出登录',
key: 'logout',
icon: renderIcon(LogoutIcon)
}
];
const handleSelect = (key: string | number) => {
if(key === 'logout'){
logout({ logoutParams: { returnTo: window.location.origin } });
router.push('/');
}
}
</script>
<style scoped lang='scss'>
.n-layout-header {
height: 60px;
padding: 0 20px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
.header-icon {
display: flex;
align-items: center;
height: 100%;
gap: 12px;
img {
height: 45px;
width: 45px;
border-radius: 4px;
}
&__text {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 45px;
.cn {
font-size: 20px;
font-weight: bold;
}
.en {
color: $primaryColor;
font-size: 12px;
font-weight: 600;
}
}
}
//
.header-avatar {
&__info {
display: flex;
align-items: center;
gap: 8px;
.username {
color: #606266;
font-size: 14px;
font-weight: 600;
}
}
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<n-layout-sider bordered collapse-mode="width" :collapsed-width="64" :width="240" :collapsed="!menuApp.isExpaned"
show-trigger @collapse="menuApp.isExpaned = false" @expand="menuApp.isExpaned = true" :style="{
'--n-collapsed-width': '64px',
transition: 'width 0.3s var(--n-bezier)'
}">
<n-menu v-model:value="activeKey" :collapsed="!menuApp.isExpaned" :collapsed-width="64"
:collapsed-icon-size="22" :options="menuOptions" :expand-icon="expandIcon" :style="{
'--n-item-icon-margin': '0 auto',
'--n-item-icon-size': '22px'
}" />
</n-layout-sider>
</template>
<script setup lang='ts'>
import {
LogOutOutline as HomeIcon,
LaptopOutline as WorkIcon,
CaretDownOutline,
HomeSharp,
PersonCircleSharp,
Layers
} from '@vicons/ionicons5'
import { MenuOption, NIcon } from 'naive-ui';
import { useAppStore } from '@/store/app';
import { storeToRefs } from 'pinia';
import { RouterLink } from 'vue-router';
import { h } from 'vue'
const appStore = useAppStore();
const activeKey = ref('default');
const { menuApp } = storeToRefs(appStore)
const expandIcon = () => {
return h(NIcon, null, { default: () => h(CaretDownOutline) })
}
function renderIcon(icon: Component) {
return () => h(NIcon, null, { default: () => h(icon) })
}
const menuOptions: MenuOption[] = [
{
label: () =>
h(
RouterLink,
{
to: {
name: 'home',
params: {
lang: 'zh-CN'
}
}
},
{ default: () => '概览' }
),
key: 'default',
icon: renderIcon(HomeSharp)
},
{
label: '角色管理',
key: 'role',
icon: renderIcon(PersonCircleSharp),
children: [
{
label: () => h(
RouterLink,
{
to: {
name: 'roleAuth',
params: {
lang: 'zh-CN'
}
}
},
{ default: () => '权限分配' }
),
key: 'role-auth',
icon: renderIcon(Layers)
}
]
},
];
</script>
<style scoped lang='scss'>
/* 确保菜单项在折叠状态下图标居中 */
.n-menu-item-content--collapsed {
display: flex;
justify-content: center;
}
/* 添加平滑过渡效果 */
.n-layout-sider {
transition: width 0.3s var(--n-bezier);
height:100%;
}
/* 调整折叠状态下菜单项的样式 */
.n-menu .n-menu-item-content--collapsed .n-menu-item-content__icon {
margin: 0 auto;
}
</style>

View File

@ -1,10 +1,12 @@
<template>
<n-layout>
<n-layout-header>颐和园路</n-layout-header>
<n-layout content-class="layout__main" has-sider>
<n-menu :collapsed="!menuApp.isExpaned" :collapsed-width="64" :collapsed-icon-size="22" :options="menuOptions"
:render-label="renderMenuLabel" :render-icon="renderMenuIcon" :expand-icon="expandIcon" />
<n-layout-content content-style="padding: 24px;">
<n-layout style="height: 100vh;">
<LayoutHeader />
<n-layout has-sider style="height: calc(100vh - 60px);">
<LayoutNav />
<n-layout-content
content-style="padding: 24px; min-height: calc(100vh - 60px);"
:native-scrollbar="false"
>
<RouterView></RouterView>
</n-layout-content>
</n-layout>
@ -12,119 +14,16 @@
</template>
<script setup lang='ts'>
import { BookmarkOutline, CaretDownOutline } from '@vicons/ionicons5';
import { MenuOption, NIcon } from 'naive-ui';
import { useAppStore } from '@/store/app';
import { storeToRefs } from 'pinia';
const appStore = useAppStore();
const { menuApp } = storeToRefs(appStore)
const expandIcon = () => {
return h(NIcon, null, { default: () => h(CaretDownOutline) })
}
const renderMenuIcon = (option: MenuOption) => {
//
if (option.key === 'sheep-man')
return true
// falsy
if (option.key === 'food')
return null
return h(NIcon, null, { default: () => h(BookmarkOutline) })
}
const menuOptions: MenuOption[] = [
{
label: '且听风吟',
key: 'hear-the-wind-sing',
href: 'https://baike.baidu.com/item/%E4%B8%94%E5%90%AC%E9%A3%8E%E5%90%9F/3199'
},
{
label: '1973年的弹珠玩具',
key: 'pinball-1973',
disabled: true,
children: [
{
label: '鼠',
key: 'rat'
}
]
},
{
label: '寻羊冒险记',
key: 'a-wild-sheep-chase',
disabled: true
},
{
label: '舞,舞,舞',
key: 'dance-dance-dance',
children: [
{
type: 'group',
label: '人物',
key: 'people',
children: [
{
label: '叙事者',
key: 'narrator'
},
{
label: '羊男',
key: 'sheep-man'
}
]
},
{
label: '饮品',
key: 'beverage',
children: [
{
label: '威士忌',
key: 'whisky',
href: 'https://baike.baidu.com/item/%E5%A8%81%E5%A3%AB%E5%BF%8C%E9%85%92/2959816?fromtitle=%E5%A8%81%E5%A3%AB%E5%BF%8C&fromid=573&fr=aladdin'
}
]
},
{
label: '食物',
key: 'food',
children: [
{
label: '三明治',
key: 'sandwich'
}
]
},
{
label: '过去增多,未来减少',
key: 'the-past-increases-the-future-recedes'
}
]
}
];
const renderMenuLabel = (option: MenuOption) => {
if ('href' in option) {
return h(
'a',
{ href: option.href, target: '_blank' },
option.label as string
)
}
return option.label as string
}
// icon
import LayoutHeader from './components/LayoutHeader.vue';
import LayoutNav from './components/LayoutNav.vue';
</script>
<style scoped lang='scss'>
.n-layout-header {
height: 60px;
}
.n-layout-sider {
background: rgba(128, 128, 128, 0.3);
}
.n-layout-content {
background: rgba(128, 128, 128, 0.4);
}
.layout__main{
height:calc(100vh - 60px);
overflow: auto;
flex: 1;
background:#f5f7f9
}
</style>

View File

@ -211,14 +211,10 @@ const handleDemo = () => {
console.log('观看演示');
}
const { loginWithRedirect, logout } = useAuth0();
const { loginWithRedirect } = useAuth0();
const handleLogin = () => {
loginWithRedirect();
}
//
const handleLogout = () => {
logout({ logoutParams: { returnTo: window.location.origin } });
}
//
const handleContact = () => {
dialog.warning({

View File

@ -0,0 +1,8 @@
<template>
<div>权限管理菜单内容</div>
</template>
<script setup lang='ts'>
</script>
<style scoped lang='scss'>
</style>