1 代码
B站有若依源码解析课程,本人亲自录制,已小白思路讲解
当前文件夹里面存放的都是关于布局的组件
这个文件是 Vue 项目里的 根布局组件(通常叫 Layout.vue) ,简单说就是整个项目的「页面框架模板」——所有页面都会套在这个框架里展示,核心作用是统一管理页面的公共部分(侧边栏、顶部导航、标签页等) ,避免每个页面重复写这些结构。
2 这个组件的核心作用
- 统一框架:所有页面都用这个模板,保证整个项目的界面风格一致(比如所有页面的侧边栏、顶部导航都长一样);
- 分离公共部分:侧边栏、顶部导航这些每个页面都有的内容,不用重复写,只在 Layout 里写一次;
- 动态控制:通过 Vuex 控制侧边栏显隐、主题色、标签页开关等,让布局能根据用户操作或设备变化(电脑/手机)动态调整。
简单说,你可以把它理解成「项目的统一信纸」—— 所有页面内容(比如信的正文)都写在这张信纸(Layout)上,信纸自带的页眉(顶部导航)、页边距(侧边栏)都是固定的,不用每次写信都画一遍。
3 代码解读
...mapState({
theme: state => state.settings.theme,
sideTheme: state => state.settings.sideTheme,
sidebar: state => state.app.sidebar,
device: state => state.app.device,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
这段代码是 Vue 组件中 计算属性 + Vuex 辅助函数 mapState 的典型用法,核心作用是「将 Vuex 中的全局状态映射为组件的计算属性」,让组件能通过 this.xxx 直接访问全局状态,无需每次写 this.$store.state.xxx。
先看完整代码拆解:
computed: {
// 展开mapState的返回值,将Vuex状态映射为组件计算属性
...mapState({
// 1. 映射主题色:Vuex中settings模块的theme状态
theme: state => state.settings.theme,
// 2. 映射侧边栏主题:Vuex中settings模块的sideTheme状态
sideTheme: state => state.settings.sideTheme,
// 3. 映射侧边栏状态:Vuex中app模块的sidebar对象(包含opened/hide等)
sidebar: state => state.app.sidebar,
// 4. 映射设备类型:Vuex中app模块的device状态(mobile/desktop)
device: state => state.app.device,
// 5. 映射是否显示标签页:Vuex中settings模块的tagsView状态(布尔值)
needTagsView: state => state.settings.tagsView,
// 6. 映射是否固定头部:Vuex中settings模块的fixedHeader状态(布尔值)
fixedHeader: state => state.settings.fixedHeader
}),
}
逐字段解读(结合后台系统场景):
| 映射后的计算属性 | 对应Vuex路径 | 含义 | 实际作用 |
|---|---|---|---|
theme | state.settings.theme | 全局主题色 | 控制按钮、标签页激活状态等的背景色(比如你之前标签页的激活样式用了这个值) |
sideTheme | state.settings.sideTheme | 侧边栏主题 | 控制侧边栏的样式(比如浅色/深色侧边栏、是否开启暗色模式) |
sidebar | state.app.sidebar | 侧边栏状态对象 | 包含 opened(是否展开)、hide(是否隐藏)、withoutAnimation(是否禁用动画),ResizeMixin 中会用到 this.sidebar.opened |
device | state.app.device | 设备类型 | 值为 mobile(移动端)/ desktop(桌面端),ResizeMixin 核心判断依据 |
needTagsView | state.settings.tagsView | 是否显示标签页 | 控制页面顶部的标签页组件(TagsView)是否展示(true=显示,false=隐藏) |
fixedHeader | state.settings.fixedHeader | 是否固定头部 | 控制导航栏/头部是否固定在页面顶部(滚动页面时头部不随内容滚动) |
核心语法解析(新手必懂):
-
...mapState({ ... }):mapState是 Vuex 辅助函数,返回一个「计算属性对象」;...是 ES6 扩展运算符,把mapState返回的对象展开到computed中,相当于给组件添加了theme、sidebar等计算属性。
-
state => state.settings.theme:- 箭头函数的参数
state是 Vuex 的根状态对象; state.settings表示访问 Vuex 中settings模块的状态;state.settings.theme表示取settings模块下theme字段的值。
- 箭头函数的参数
三、和 ResizeMixin 的配合逻辑(关键)
这段代码是 ResizeMixin 能正常工作的前提 —— ResizeMixin 中用到的 this.device、this.sidebar,本质就是这里映射的计算属性:
// ResizeMixin中的代码
watch: {
$route(route) {
// this.device → 对应这里映射的state.app.device
// this.sidebar.opened → 对应这里映射的state.app.sidebar.opened
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
}
完整联动流程:
- 组件通过
mapState把 Vuex 的device、sidebar映射为自身计算属性; - 组件混入 ResizeMixin,Mixin 中的
this.device/this.sidebar指向这些计算属性; - 窗口变化时,ResizeMixin 更新 Vuex 中的
device/sidebar; - 计算属性响应式更新,组件和 Mixin 能实时拿到最新的
device/sidebar值。
四、等价手动写法(理解本质)
mapState 是语法糖,这段代码等价于手动写以下计算属性(新手可对比理解):
computed: {
theme() {
return this.$store.state.settings.theme
},
sideTheme() {
return this.$store.state.settings.sideTheme
},
sidebar() {
return this.$store.state.app.sidebar
},
device() {
return this.$store.state.app.device
},
needTagsView() {
return this.$store.state.settings.tagsView
},
fixedHeader() {
return this.$store.state.settings.fixedHeader
}
}
- 用
mapState的优势:代码更简洁,尤其是映射多个状态时,避免重复写this.$store.state.xxx。
五、总结
- 核心作用:通过
mapState将 Vuex 中app、settings模块的全局状态映射为组件的计算属性,组件可通过this.xxx快速访问全局状态; - 关键配合:为 ResizeMixin 提供了
this.device、this.sidebar等属性,是 Mixin 能正常工作的基础; - 语法本质:
mapState是手动编写计算属性的语法糖,...扩展运算符将映射的属性“平铺”到computed中,实现响应式访问全局状态。
4 默认配置
/**
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
*/
sideTheme: 'theme-dark',
// 存储主题色:优先使用本地存储(storageSetting)中保存的theme值,
// 若本地存储无该值(或为falsy),则默认使用蓝色(#409EFF,Element UI默认主题色)
theme: storageSetting.theme || '#409EFF',
默认侧边栏是open 开启
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
device: 'desktop', 默认设备是桌面
刚进这个页面,最外层框架渲染的结果
就上面这个代码渲染的结果
<div class="app-wrapper openSidebar" style="--current-color: #409EFF;">
<!-- 你的页面内容 -->
</div>
这个div的style里写--current-color: #ffba00;,相当于已经把变量「存好了」(变量本身是生效的),但因为当前div没写「调用变量的样式」,所以视觉上看不到效果。
核心拆解:
-
变量本身是「生效」的只要写在
style里,这个--current-color变量就已经存在于这个div的作用域中(自身+后代都能访问),不是「没生效」,只是没被用起来。 -
当前div「没显示效果」的原因因为你只定义了变量,但没给这个div写「用变量的样式」。比如想让这个div自己显示这个颜色,得加一行调用代码:
<!-- 定义变量 + 调用变量,当前div才会显示效果 --> <div class="app-wrapper opensidebar" style="--current-color: #ffba00; background: var(--current-color);" > ... </div>加上
background: var(--current-color);后,这个div的背景就会变成#ffba00(变量生效的视觉体现)。
一句话总结:
✅ 变量定义了就「生效(可被访问)」,但需要用var(--变量名)调用,才会在样式上显示效果;当前div只是「存了变量但没调用」,所以视觉上没变化~
5 侧边栏
<sidebar class="sidebar-container"/> 的逻辑,和你写 <div class="box"/> 完全一模一样:
使用了自定义的组件,其实就是使用了这个组件里面的最外层的div ,在使用的自定义的组件上加样式,其实是加在这个组件里面的最外层的div 上。
这个是这个组件的最外层
使用之后渲染的结果
因为样式已经在入口出全部都引入系统,全局引入,任何文件可以直接使用,所以这个
这些已经全局引入,里面的样式可以直接使用,sidebar-container 找到这个样式
这是 CSS 样式优先级铁律,优先级排序为:👉 行内style样式 > class类样式,无论 class 里的样式写在什么位置、有没有其他配置,行内 style 的颜色都会「盖掉」class 里的颜色。
对于侧边栏的div position: fixed;
6 右边内容
也就是整个页面,除了侧边栏,右边就分为3部分,一个是顶部导航,一个是标签页,一个是主要内容区域
根据代码可以知道,右边的顶部导航,标签页,主要内容区域,其实是包在一个大的div 里面,所以我们先看最外边的div 样式是啥样
6.1 大的div
根据代码可以知道,最外层大的div就是这个,其中有一个动态的样式,就是是否显示标签页,是否隐藏左边侧边栏,这个都是根据仓库获取信息
刚进页面,展示左边侧边栏,所以最后这个div 的展示是
接下来我们就根据这个样式,看最外层的div咋布局
- 这段代码的核心是适配侧边栏布局,通过
margin-left避开侧边栏,transition实现平滑过渡; height: 100%保证主内容区全屏占高,position: relative为子元素定位提供基准;$base-sidebar-width是 SCSS/LESS 变量,实现 “一处定义、多处使用”,方便统一修改侧边栏宽度。
如果需要适配移动端(侧边栏收起),只需通过 JS 修改.main-container的margin-left为 0 即可,配合transition仍能保持动画效果。
margin-left 通过这个动态的获取这个大的div 和左边的距离
6.2 导航与标签页
通过代码可以知道,在大的div里面又分为3部分,其中头与标签页又放到一个div里面,那么这两个就是一组,我们看这两个一组的div样式是啥
这个样式也是动态的,如果设置固定头,那么这个div的样式就是
如果选择不固定
现在我们先看固定头的样式
我们可以看到也是脱离流,固定在顶部,并且宽度是计算得来的
这两个位置就是固定的了,顶部这个位置的高度就是这个组合div里面内容的高度了
关于标签是否展示,也是在仓库里面设置,这里判断是否展示,接下来不管是展示还是不展示,反正都在这个div里面,并且没有额外的样式,都是直接使用的自定义组件
6.2.1 导航
直接看 navbar 这个组件的内容
6.2.2 标签
这个位置控制的是
也就是说这个是一个组件,通过设置面板里面的按钮,控制是否展示这个组件,那么对于这个来说,就是单独看这个组件内容就可以,说白了就是一个组件的显示影藏,位置的话,已经设定
6.3 app-main
现在就有一个问题,为什么切换不同页面,他只是在这个组件里面进行替换,不会在其他地方进行替换,还有一个问题,那就是点击不同按钮,走路由的话,会根据路由渲染不同的页面,那么为什么不同的页面都在这个组件里面进行渲染