个人主页:Silence Lamb
本章内容:【Layout布局】
// No layout
{
path: '/401',
component: () => import('errorPage/401')
}
// Has layout
{
path: '/documentation',
// 你可以选择不同的layout组件
component: Layout,
// 这里开始对应的路由都会显示在app-main中 如上图所示
children: [{
path: 'index',
component: () => import('documentation/index'),
name: 'documentation'
}]
}
/*整体页面背景*/
$bodyBg: #ffffff;
/*侧边框宽度*/
$sideBarWidth: 210px;
/*侧边框背景*/
$sideBarBg: rgba(240, 240, 244, 0.98);
/*菜单背景*/
$menuBg: #f0f0f4;
/*菜单鼠标悬停背景*/
$menuHoverBg: rgba(95, 105, 112, 0.44);
/*菜单激活背景*/
$menuActiveBg: rgba(105, 114, 119, 0.47);
/*子菜单背景*/
$subMenuBg: rgba(105, 114, 119, 0.99);
/*子菜单鼠标悬停背景*/
$subMenuHoverBg: rgba(105, 114, 119, 0.99);
/*子菜单激活背景*/
$subMenuActiveBg: rgba(105, 114, 119, 0.99);
/*菜单字体颜色*/
$menuTextColor: #ffffff;
/*菜单字体选中状态的颜色*/
$menuTextActive: #ffffff;
/*菜单字体鼠标悬停的颜色*/
$menuTextHover: #e3ae11;
:export {
/*整体页面背景*/
bodyBg: $bodyBg;
/*侧边框宽度*/
sideBarWidth: $sideBarWidth;
/*侧边框背景*/
sideBarBg: $sideBarBg;
/*侧边栏激活背景*/
menuActiveBg: $menuActiveBg;
/*侧边栏鼠标悬停背景*/
menuHoverBg: $menuHoverBg;
/*侧边栏字体颜色*/
menuTextColor: $menuTextColor;
/*侧边栏字体选中状态的颜色*/
menuTextActive: $menuTextActive;
/*菜单背景*/
menuBg: $menuBg;
/*子菜单背景*/
subMenuBg: $subMenuBg;
/*子菜单鼠标悬停背景*/
subMenuHoverBg: $subMenuHoverBg;
/*子菜单激活背景*/
subMenuActiveBg:$subMenuActiveBg;
/*菜单字体鼠标悬停的颜色*/
menuTextHover: $menuTextHover;
}
computed 属性 key 用于为 router-view 组件提供唯一标识符。这确保当 URL 发生更改时,router-view 组件被重新渲染,而不是重用先前的组件实例。
transition 组件用于在内容更改时应用过渡效果。在本例中,使用 breadcrumb 过渡和 out-in 模式,这意味着出站内容先动画出,然后入站内容动画进入。
<template>
<section class="app-main">
<router-view v-slot="{ Component }">
<transition name="fade-transform" mode="out-in">
<component :is="Component" />
transition>
router-view>
section>
template>
<script>
export default {
name: 'AppMain',
}
script>
<style lang="scss" scoped>
@import "src/styles/theme/variables/index.scss";
.app-main {
/*50 = navbar */
min-height: calc(100vh - #{$sideBarWidth});
width: 100%;
position: relative;
overflow: hidden;
}
style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
style>
import Cookies from 'js-cookie'
//可能是在一个 Vuex store 中作为状态的初始值进行定义
const state = {
sidebar: {
//包含侧边栏信息的对象,包括侧边栏是否已打开或关闭,以及是否有动画效果
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
//它表示侧边栏是否需要动画效果。如果设置为 true,那么在打开或关闭侧边栏时将没有过渡效果,直接切换状态
//这可以提高页面渲染性能,特别是在较慢的设备上。如果设置为 false,那么在打开或关闭侧边栏时将启用过渡动画
withoutAnimation: false
},
//标识设备类型,这里设为“desktop”(电脑)
device: 'desktop'
}
//用于存储更改应用程序状态的函数,这些函数通常在 actions 中被调用
const mutations = {
//可以用来切换侧边栏打开或关闭状态,并且在切换状态时可以选择是否加入动画效果,同时会将状态保存到 cookie 中
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1)
} else {
Cookies.set('sidebarStatus', 0)
}
},
//用于手动关闭侧边栏,并且可以选择是否要加入动画效果
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Cookies.set('sidebarStatus', 0)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
//用于切换设备类型
//一个状态对象,包含了应用程序中所有的状态信息
//一个表示设备类型的字符串参数。该参数用于切换设备类型
TOGGLE_DEVICE: (state, device) => {
state.device = device
}
}
//用于存储异步操作的函数,通常用于执行一些异步操作
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
// Vuex store 中的 getters 对象,用于派生一些新的状态信息
const getters = {
sidebar: state => state.app.sidebar,
}
export default getters
<template>
<component :is="type()" v-bind="linkProps(to)">
<slot />
component>
template>
<script>
import { isExternal } from '@/utils/tool/validate'
export default {
name: 'App-Link',
props: {
to: {
type: String,
required: true
}
},
computed: {
//是否是外部链接:如果是返回true,反正返回false
isExternal() {
return isExternal(this.to)
},
//用于指定动态组件 的类型
type() {
if (this.isExternal) {
//如果这个组件是一个外部链接, 则返回字符串a
return 'a'
}
//表示使用 Vue.js 内置的路由链接组件
//这个属性通常用于指定需要渲染的组件类型
return 'router-link'
}
},
methods: {
linkProps(to) {
//即当前链接是一个外部链接,则将要传递给组件的是一个包含 href、target 和 rel 三个属性的对象
if (this.isExternal) {
return {
href: to,//href 属性的值就是传入的 to 参数,表示链接的地址
target: '_blank',//target 属性设为 _blank,表示链接将在新标签页中打开
rel: 'noopener'//是为了防止在打开链接的新标签页中,被链接页面的 JavaScript 访问到原先页面的 window.opener 属性,进而导致安全隐患
}
}
//如果 isExternal 为假,即当前链接是一个内部路由链接,则将要传递给组件的是一个包含 to 属性的对象
return {
to: to//表示内部路由链接的目标地址
}
}
}
}
script>
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
required: false
},
title: {
type: String,
required: false
}
},
render() {
console.log(this.icon)
console.log(this.title)
var icon = this.icon
var title = this.title
const vnodes = []
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
} else {
vnodes.push(<svg-icon icon-class={icon} />)
}
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
script>
<style scoped>
style>
template>
<div v-if="!item.hidden">
<template
v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'sub-menu-title-noDropdown': !isNest }">
<span><item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>span>
<template #title>
<item :title="onlyOneChild.meta.title"/>
template>
el-menu-item>
app-link>
template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<item :icon="item.meta && item.meta.icon"/>
<span> <item :title="item.meta.title"/>span>
template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:base-path="resolvePath(child.path)"
:is-nest="true"
:item="child"
class="nest-menu"
/>
el-sub-menu>
div>
template>
import path from 'path';
import { isExternal } from '@/utils/tool/validate';
import AppLink from './appLink'
import ItemVue from './item'
export default {
name: 'Sidebar-Item',
components: { AppLink, ItemVue },
//表示一个路由对象,这个属性是必须的
props: {
item: {
type: Object,
required: true
},
//用于表示当前节点是否在嵌套子级中,默认为 false,即不在嵌套子级中,可以通过传入 true 来修改该属性
isNest: {
type: Boolean,
default: false
},
//表示当前路由的基本路径,如果组件需要根据路由生成一些链接,则需要使用该属性
basePath: {
type: String,
default: ''
}
},
data() {
this.onlyOneChild = null
return {}
},
methods: {
// 检查 item.children 中是否至少有一个子节点的 hidden 属性为 false,如果是,则返回 true,否则返回 false
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// 临时设置(当只有一个子路由显示时将会被使用)
this.onlyOneChild = item
return true
}
})
// 当只有一个子路由时,默认情况下会显示子路由
if (showingChildren.length === 1) {
return true
}
// 如果没有子路由可显示,则显示父级路由。
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
//路径解析函数
resolvePath(routePath) {
//如果是外部链接,则直接返回该地址
if (isExternal(routePath)) {
return routePath
}
//会进一步检查当前实例对象的 basePath 属性是否为一个外部 url 地址
if (isExternal(this.basePath)) {
return this.basePath
}
//不是一个外部 url 地址,且 basePath 也不是,则将它们拼接起来,得到一个新的路径
return path.resolve(this.basePath, routePath)
}
}
};
</script>
<template>
<div :class="{ 'has-logo': showLogo }">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu :default-active="activeMenu" :collapse="isCollapse" :unique-opened="false" :collapse-transition="false"
mode="vertical">
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
el-menu>
el-scrollbar>
div>
template>
import sidebarItem from './sidebarItem.vue'
import variables from '@/styles/theme/variables.scss'
import { mapGetters } from 'vuex'
export default {
name: 'Sidebar-Index',
//import 引入的组件需要注入到对象中才能使用
components: { sidebarItem },
//计算属性
computed: {
...mapGetters([
'sidebar'
]),
// 获取路由信息(path,children,component)
routes() {
return this.$router.options.routes
},
// 该属性将指定菜单项的选中状态
activeMenu() {
const route = this.$route
const { meta, path } = route
//选中状态将与当前页面所使用的路由路径保持一致(如:地址栏是:/icon,则返回/icon)
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
//如果侧边栏为折叠状态,则返回 true;否则返回 false。
isCollapse() {
//在vuex中保存的属性值
return !this.sidebar.opened
},
//该计算属性返回了一个名为 variables 的对象,该对象属于全局配置
//包括了一些变量和属性(如 menuBg 表示菜单的背景色,menuText 表示菜单项的文本颜色)
variables() {
return variables
},
}
}
</script>
/*整体页面背景*/
$bodyBg: #88ada6;
/*侧边框宽度*/
$sideBarWidth: 210px;
/*侧边框背景*/
$sideBarBg: #425066;
/*侧边栏激活背景*/
$menuActiveBg: #425066;
/*侧边栏鼠标悬停背景*/
$menuHoverBg: #c2ccd0;
/*侧边栏字体颜色*/
$menuText: #f0fcff;
/*侧边栏字体选中状态的颜色*/
$menuActiveText: #c0ebd7;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
/*整体页面背景*/
bodyBg: $bodyBg;
/*侧边框宽度*/
sideBarWidth: $sideBarWidth;
/*侧边框背景*/
sideBarBg: $sideBarBg;
/*侧边栏激活背景*/
menuActiveBg: $menuActiveBg;
/*侧边栏鼠标悬停背景*/
menuHoverBg: $menuHoverBg;
/*侧边栏字体颜色*/
menuText: $menuText;
/*侧边栏字体选中状态的颜色*/
menuActiveText: $menuActiveText;
}
<template>
<div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} h1>
router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 class="sidebar-title">{{ title }} h1>
router-link>
transition>
div>
template>
<script>
export default {
name: 'Sidebar-Logo',
props: {
collapse: {
type: Boolean,
required: true
}
},
computed: {
//获取标题
title() {
return this.$store.state.settings.title
},
//获取logo
logo(){
console.log(this.$store.state.settings.logo)
return this.$store.state.settings.logo
}
}
}
script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
style>
import defaultSettings from '@/setting/settings'
const { title, fixedHeader, sidebarLogo } = defaultSettings
const state = {
//网站标题
title: title,
//网站logo
logo: logo,
//头部固定
fixedHeader: fixedHeader,
//是否显示Logo
sidebarLogo: sidebarLogo
}
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
if (state.hasOwnProperty(key)) {
state[key] = value
}
}
}
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
module.exports = {
/**
* 网站标题
*/
title: 'Silence-Vue',
/**
* Logo
*/
logo: '',
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: true,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: true
}
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg
:class="{'is-active':isActive}"
class="hamburger"
height="64"
viewBox="0 0 1024 1024"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"/>
svg>
div>
template>
<script>
export default {
name: 'HamBurger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburger.is-active {
transform: rotate(180deg);
}
style>
<template>
<div class="navbar-container">
<ham-burger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar"/>
<bread-crumb class="breadcrumb-container"/>
<div class="right-container">
<el-tooltip content="登录" placement="top">
<a href="/login">
<svg-icon icon-class="user"/>
a>
el-tooltip>
div>
div>
template>
import {mapGetters} from 'vuex'
import Hamburger from '@/components/Hamburger'
export default {
name: 'Navbar-Index',
components: { Hamburger},
computed: {
...mapGetters([
'sidebar',
])
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
}
}
};
</script>
<style lang="scss" scoped>
@import "src/styles/theme/index";
.navbar-container {
margin:0 10px 0 10px;
height: $navbarHeight;
overflow: hidden;
position: relative;
border-radius: 10px;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.hamburger {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .28s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(102, 51, 153, 0.22);
}
}
.breadcrumb{
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
}
.svg-icon {
right: 50px;
fill: #425066;
}
}
style>
<template>
<div class="app-wrapper">
<sidebar class="sidebar-container"/>
<div class="main-container">
<div >
<navbar/>
div>
<app-main/>
div>
<scrollToTop/>
div>
template>
<script>
import Sidebar from './components/Sidebar';
import AppMain from './components/AppMain';
import Navbar from './components/Navbar';
import {mapState} from 'vuex'
export default {
name: 'Layout-Vue',
components: {Sidebar, AppMain, Navbar},
}
script>
<style lang="scss" scoped>
@import "src/styles/index";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
}
/*主要内容区域全局样式*/
@import 'main';
/*侧边栏全局样式*/
@import 'sidebar';
/*主要区域容器全局样式*/
.main-container {
transition: margin-left 0.28s;
/*右移侧边栏宽度*/
margin-left: $sideBarWidth;
border-radius: 10px;
min-height: 100%;
position: relative;
overflow: hidden;
}
/*内容区域全局样式*/
.app-container {
padding-left: 20px;
}
/*主要区域容器全局样式*/
.main-container {
transition: margin-left 0.28s;
/*右移侧边栏宽度*/
margin-left: $sideBarWidth;
border-radius: 10px;
min-height: 100%;
position: relative;
}
/*内容区域全局样式*/
.app-container {
padding-left: 20px;
}
/*侧边栏模块全局样式*/
.sidebar-container {
transition: width 0.28s;
border-radius: 10px;
width: $sideBarWidth;
position: fixed;
font-size: 0;
top: 0;
bottom: 0;
left: 0;
overflow: hidden;
}
/*组件全局样式*/
.components-container {
margin: 30px 50px;
position: relative;
}
/*分页全局样式*/
.pagination-container {
margin-top: 30px;
}
/*过滤器全局样式*/
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
/*组件全局样式*/
.components-container {
margin: 30px 50px;
position: relative;
}
/*分页全局样式*/
.pagination-container {
margin-top: 30px;
}
/*过滤器全局样式*/
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}
#app {
.sidebar-container {
.is-horizontal {
display: none;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 50px);
}
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
background: $sideBarBg !important;
}
/*无子菜单的菜单样式*/
.el-menu-item {
border-radius: 10px;
&:hover {
background-color: $menuHoverBg !important;
}
&.is-active {
color: $menuTextActive !important;
background-color: $menuActiveBg !important;
}
}
/*有子菜单的鼠标悬停背景*/
.el-sub-menu__title {
border-radius: 10px;
&:hover {
color: $menuTextHover;
background-color: $subMenuHoverBg !important;
}
&.is-active {
color: $subMenuActiveBg !important;
background-color: $subMenuActiveBg !important;
}
}
.svg-icon {
margin-right: 10px;
}
}
/*有子菜单的菜单的箭头*/
.el-icon svg {
position: relative;
animation: arrowhead 0.28s;
animation-fill-mode: forwards;
}
/*菜单关闭时*/
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
/*子菜单标题-无下拉列表*/
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0px !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-sub-menu {
overflow: hidden;
.el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-sub-menu__icon-arrow {
display: none;
}
}
}
/*菜单折叠时隐藏title和箭头*/
.el-menu--collapse {
.el-icon svg {
display: none;
}
.el-sub-menu {
& > .el-sub-menu__title {
.title {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
/*没有动画*/
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
/*菜单折叠时*/
.el-menu--vertical {
.vertical {
background-color: rebeccapurple;
}
.el-menu {
/*子菜单弹框菜单颜色*/
background-color: $sideBarBg;
.svg-icon {
margin-right: 0px;
}
.sub-el-icon {
margin-right: 0px;
}
}
.el-menu-item {
border-radius: 10px;
&:hover {
color: $menuTextHover;
background-color: $menuHoverBg !important;
}
&.is-active {
color: $menuTextActive;
}
}
/*有子菜单的鼠标悬停背景*/
.el-sub-menu__title {
border-radius: 10px;
&:hover {
background-color: $subMenuHoverBg !important;
}
&.is-active {
color: $subMenuActiveBg !important;
background-color: $subMenuActiveBg !important;
}
}
// 当子菜单太长时出现滚动条
.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
@include scrollBar;
}
}
@keyframes arrowhead {
from {
right: 50px;
opacity: 0;
}
to {
right: 0;
opacity: 1;
}
}
<ham-burger :is-active="sidebar.opened" class="hamburger" @toggleClick="toggleSideBar"/>
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
}
}
<div :class="classObj" class="app-wrapper">
computed: {
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
/*菜单关闭时*/
.hideSidebar {
.sidebar-container {
width: $sideBarVerticalWidth !important;
}
.main-container {
margin-left: $sideBarVerticalWidth;
}
/*子菜单标题-无下拉列表*/
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-sub-menu {
overflow: hidden;
.el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-sub-menu__icon-arrow {
display: none;
}
}
}
/*菜单折叠时隐藏title和箭头*/
.el-menu--collapse {
.el-icon svg {
display: none;
}
.el-sub-menu {
& > .el-sub-menu__title {
.subMenuTitle {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
import store from '@/store'
const { body } = document //创建了一个变量来引用document.body元素
const WIDTH = 992 // WIDTH常量被设置为992,这是Bootstrap中移动和桌面响应的阈值
export default {
/*watch属性用于监视$route对象的变化*/
watch: {
$route(route) {
/*它检查当前device(在应用程序的其他地方基于屏幕宽度设置)是否为移动设备,并且侧边栏当前是否已打开*/
if (this.device === 'mobile' && this.sidebar.opened) {
/*如果是,则触发一个动作('app/closeSideBar')到VueX商店,传递一个选项对象来指定关闭侧边栏时的动画*/
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
/*它通过监听window对象上的resize事件 将this.$_resizeHandler方法绑定到resize事件上*/
beforeMount() {
/*当window对象触发resize事件时,监听器会调用this.$_resizeHandler方法,该方法会根据窗口大小更新device属性*/
/*this.$_resizeHandler是一个私有方法,它是在组件内部定义的,并负责更新组件中的device属性,以响应屏幕宽度的变化*/
window.addEventListener('resize', this.$_resizeHandler)
},
/*它通过移除之前在window对象上绑定的resize事件监听器,来清除已添加的事件监听程序
因为如果不清除事件监听器,它们将继续在组件销毁后继续运行,可能导致内存泄漏和其他问题*/
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler)
},
mounted() {
/*mounted()方法首先调用$_isMobile()方法来检查应用程序是否在移动设备上运行*/
const isMobile = this.$_isMobile()
if (isMobile) {
/*如果是,它会调用VueX store中的 app/toggleDevice方法来将device属性设置为'mobile'*/
store.dispatch('app/toggleDevice', 'mobile')
/*它会通过调用app/closeSideBar方法来确保侧边栏在移动设备上始终关闭,该方法将一个选项对象传递给store来指定在没有动画的情况下关闭侧边栏*/
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
/*这个方法的作用是检查设备是否为移动设备*/
$_isMobile() {
/*该方法首先使用getBoundingClientRect()方法来获取body元素的宽度和高度,然后将其存储在名为rect的变量中*/
const rect = body.getBoundingClientRect()
/*它将检查rect对象的宽度是否小于预定义的WIDTH值,并将true或false返回给调用该方法的位置*/
return rect.width - 1 < WIDTH //如果$_isMobile()方法返回true,则该组件将认为当前设备为移动设备,并根据需要采取响应行动。
},
/*它是在窗口大小发生变化时调用的方法,用于更新屏幕上不同元素的尺寸和布局,以支持屏幕的响应式支持*/
$_resizeHandler() {
/*首先它使用document.hidden属性,检查文档是否处于隐藏状态(例如:用户最小化了浏览器窗口)*/
if (!document.hidden) {
/*通过调用this.$_isMobile()来检查当前设备是否为移动设备*/
const isMobile = this.$_isMobile()
/*如果是,则通过调用VueX store的app/toggleDevice方法来将device属性设置为'mobile',否则将其设置为'desktop'。*/
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
/*如果设备是移动设备,则通过调用VueX Store的app/closeSideBar方法来确保侧边栏始终处于关闭状态*/
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
}
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
.....
</div>
</div>
</template>
import AutoResizer from './components/Mixin/index'
<script>
import AutoResizer from './components/Mixin/index'
export default {
name: 'Layout-Vue',
components: {Sidebar, AppMain, Navbar},
mixins: [AutoResizer],
computed: {
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', {withoutAnimation: false})
}
}
}
</script>
// 移动响应
.mobile {
.main-container {
margin-left: 0;
}
.sidebar-container {
transition: transform .28s;
width: $sideBarWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}
}