若依vue 前端 layout 详解

0 阅读4分钟

1 代码

B站有若依源码解析课程,本人亲自录制,已小白思路讲解

当前文件夹里面存放的都是关于布局的组件

这个文件是 Vue 项目里的 根布局组件(通常叫 Layout.vue ,简单说就是整个项目的「页面框架模板」——所有页面都会套在这个框架里展示,核心作用是统一管理页面的公共部分(侧边栏、顶部导航、标签页等) ,避免每个页面重复写这些结构。

2 这个组件的核心作用

  1. 统一框架:所有页面都用这个模板,保证整个项目的界面风格一致(比如所有页面的侧边栏、顶部导航都长一样);
  2. 分离公共部分:侧边栏、顶部导航这些每个页面都有的内容,不用重复写,只在 Layout 里写一次;
  3. 动态控制:通过 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路径含义实际作用
themestate.settings.theme全局主题色控制按钮、标签页激活状态等的背景色(比如你之前标签页的激活样式用了这个值)
sideThemestate.settings.sideTheme侧边栏主题控制侧边栏的样式(比如浅色/深色侧边栏、是否开启暗色模式)
sidebarstate.app.sidebar侧边栏状态对象包含 opened(是否展开)、hide(是否隐藏)、withoutAnimation(是否禁用动画),ResizeMixin 中会用到 this.sidebar.opened
devicestate.app.device设备类型值为 mobile(移动端)/ desktop(桌面端),ResizeMixin 核心判断依据
needTagsViewstate.settings.tagsView是否显示标签页控制页面顶部的标签页组件(TagsView)是否展示(true=显示,false=隐藏)
fixedHeaderstate.settings.fixedHeader是否固定头部控制导航栏/头部是否固定在页面顶部(滚动页面时头部不随内容滚动)
核心语法解析(新手必懂):
  1. ...mapState({ ... })

    • mapState 是 Vuex 辅助函数,返回一个「计算属性对象」;
    • ... 是 ES6 扩展运算符,把 mapState 返回的对象展开到 computed 中,相当于给组件添加了 themesidebar 等计算属性。
  2. state => state.settings.theme

    • 箭头函数的参数 state 是 Vuex 的根状态对象
    • state.settings 表示访问 Vuex 中 settings 模块的状态;
    • state.settings.theme 表示取 settings 模块下 theme 字段的值。

三、和 ResizeMixin 的配合逻辑(关键)

这段代码是 ResizeMixin 能正常工作的前提 —— ResizeMixin 中用到的 this.devicethis.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 })
    }
  }
}
完整联动流程:
  1. 组件通过 mapState 把 Vuex 的 devicesidebar 映射为自身计算属性;
  2. 组件混入 ResizeMixin,Mixin 中的 this.device/this.sidebar 指向这些计算属性;
  3. 窗口变化时,ResizeMixin 更新 Vuex 中的 device/sidebar
  4. 计算属性响应式更新,组件和 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

五、总结

  1. 核心作用:通过 mapState 将 Vuex 中 appsettings 模块的全局状态映射为组件的计算属性,组件可通过 this.xxx 快速访问全局状态;
  2. 关键配合:为 ResizeMixin 提供了 this.devicethis.sidebar 等属性,是 Mixin 能正常工作的基础;
  3. 语法本质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没写「调用变量的样式」,所以视觉上看不到效果。

核心拆解:

  1. 变量本身是「生效」的只要写在style里,这个--current-color变量就已经存在于这个div的作用域中(自身+后代都能访问),不是「没生效」,只是没被用起来。

  2. 当前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咋布局

  1. 这段代码的核心是适配侧边栏布局,通过margin-left避开侧边栏,transition实现平滑过渡;
  2. height: 100%保证主内容区全屏占高,position: relative为子元素定位提供基准;
  3. $base-sidebar-width是 SCSS/LESS 变量,实现 “一处定义、多处使用”,方便统一修改侧边栏宽度。

如果需要适配移动端(侧边栏收起),只需通过 JS 修改.main-containermargin-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

现在就有一个问题,为什么切换不同页面,他只是在这个组件里面进行替换,不会在其他地方进行替换,还有一个问题,那就是点击不同按钮,走路由的话,会根据路由渲染不同的页面,那么为什么不同的页面都在这个组件里面进行渲染