Layout布局

11/7/2022

# 一、背景与问题

很多框架都有多种布局形态,即 菜单在左侧、顶部;菜单是展开形态、菜单是传统折叠菜单 等等,但是呢,很多框架layout这块封装的就特别复杂,事情为什么变得复杂呢,我想有以下几个原因:

  • 1、多种布局相同的组件很多,所以会抽象的组件较多
  • 2、因为要提供配置页面,所以布局要和状态管理vuex挂钩
  • 3、多种布局,大部分是layout的位置(即layout组件的属性变化)
  • 4、炫技各种抽象 or 不炫技代码堆积到了一起

以上的以上,很多写的就很复杂,一个layout文件弄几千行,或者超级多的变量,不敢想象

# 二、架构思想

1) layout 种类大概就3,4中,索性我们就为每个layout 设计一个 layout.vue文件,作为入口就可以了 2) 对于大类的公共组件,抽出来放到 layout/components 中 3) 对于多个layout中相同代码,不再抽象,保持独立,给予最大的扩展性

以上三点虽然 可能会有相同的代码,但是并不会太多,好处是 大大的降低了复杂度,提高的阅读性和维护性。
这也是可以满足我们的好的代码原则的。好的代码

1)满足业务需要:代码是来实现业务的,如果业务都实现不了,代码也就没什么价值了
2)代码尽可能的清晰明了:就是让小白也能看懂你的代码
3)代码尽可能的少:在保证清晰明了的前提下,能少一行少一行,能少一个类少一个类,能少一行注释少一行注释
4)代码尽可能复用性和模块化:在保证清晰明了和尽可能少的前提下,能复用的代码尽量复用,能模块的尽量模块

# 三、具体实现

目录结构如下:

| layout /                       布局目录
| --- smart-layout.vue           引入文件(在这里vuex判断是哪种具体的layout)
| --- smart-side-layout.vue          传统菜单布局layout
| --- smart-side-expand-layout.vue   展开菜单布局layout
| --- components/                    公共组件
1
2
3
4
5

smart-layout.vue 中代码, 使用if 直接判断是哪一种类型的 layout,很清晰

<template>
  <!--左侧菜单 模式-->
  <SmartSideLayout v-if="layout === LAYOUT_ENUM.SIDE.value" />
  <!--左侧展开菜单 模式-->
  <SmartSideExpandLayout v-if="layout === LAYOUT_ENUM.SIDE_EXPAND.value" />
</template>
<script setup>
  import { computed } from 'vue';
  import SmartSideExpandLayout from './smart-side-expand-layout.vue';
  import { LAYOUT_ENUM } from '/@/constants/layout-const';
  import SmartSideLayout from '/@/layout/smart-side-layout.vue';
  import { useAppConfigStore } from '/@/store/modules/system/app-config';

  const layout = computed(() => useAppConfigStore().$state.layout);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

传统菜单layout,smart-side-layout.vue

<template>
  <a-layout class="admin-layout" style="min-height: 100%">
    <!-- 侧边菜单 side-menu -->
    <a-layout-sider class="side-menu" :width="sideMenuWidth" :collapsed="collapsed" :theme="theme">
      <!-- 左侧菜单 -->
      <SideMenu :collapsed="collapsed" />
    </a-layout-sider>

    <!--中间内容,一共三部分:1、顶部;2、中间内容区域;3、底部(一般是公司版权信息);-->
    <a-layout id="smartAdminMain" :style="`height: ${windowHeight}px`" class="admin-layout-main">
      <!-- 顶部头部信息 -->
      <a-layout-header class="layout-header">
        <a-row class="layout-header-user" justify="space-between">
          <a-col class="layout-header-left">
            <!-- 菜单收缩 -->
            <span class="collapsed-button">
              <menu-unfold-outlined v-if="collapsed" class="trigger" @click="() => (collapsed = !collapsed)" />
              <menu-fold-outlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
            </span>
            <!-- 首页 按钮 -->
            <a-tooltip placement="bottom">
              <template #title>首页</template>
              <span class="home-button" @click="goHome">
                <home-outlined class="trigger" />
              </span>
            </a-tooltip>
            <!-- 面包屑 -->
            <span class="location-breadcrumb">
              <MenuLocationBreadcrumb />
            </span>
          </a-col>
          <!---用戶操作区域:搜索、消息、国际化、我的-->
          <a-col class="layout-header-right">
            <HeaderUserSpace />
          </a-col>
        </a-row>
        <PageTag />
      </a-layout-header>

      <!--中间内容-->
      <a-layout-content id="smartAdminLayoutContent" class="admin-layout-content">
        <!--不keepAlive的iframe使用单个iframe组件-->
        <IframeIndex v-if="iframeNotKeepAlivePageFlag" :key="route.name" :name="route.name" :url="route.meta.frameUrl" />
        <!--keepAlive的iframe 每个页面一个iframe组件-->
        <IframeIndex
          v-for="item in keepAliveIframePages"
          v-show="route.name == item.name"
          :key="item.name"
          :name="item.name"
          :url="item.meta.frameUrl"
        />
        <!--非iframe使用router-view-->
        <div v-show="!iframeNotKeepAlivePageFlag && keepAliveIframePages.every((e) => route.name != e.name)">
          <router-view v-slot="{ Component }">
            <keep-alive :include="keepAliveIncludes">
              <component :is="Component" :key="route.name" />
            </keep-alive>
          </router-view>
        </div>
      </a-layout-content>

      <!-- footer 版权公司信息 -->
      <a-layout-footer class="layout-footer" v-show="footerFlag">
        <smart-footer />
      </a-layout-footer>
      <!--- 回到顶部 -->
      <a-back-top :target="backTopTarget" :visibilityHeight="80" />
    </a-layout>
    <!-- 右侧帮助文档 help-doc -->
    <a-layout-sider v-show="helpDocFlag" theme="light" :width="180" class="help-doc-sider" :trigger="null" style="min-height: 100%">
      <SideHelpDoc />
    </a-layout-sider>
  </a-layout>
</template>

<script setup>
  import 具体的导入;
  
  //菜单宽度
  const sideMenuWidth = computed(() => useAppConfigStore().$state.sideMenuWidth);
  //主题颜色
  const theme = computed(() => useAppConfigStore().$state.sideMenuTheme);
  //是否显示标签页
  const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
  // 是否显示帮助文档
  const helpDocFlag = computed(() => useAppConfigStore().$state.helpDocFlag);
  // 是否显示页脚
  const footerFlag = computed(() => useAppConfigStore().$state.footerFlag);
  //是否隐藏菜单
  const collapsed = ref(false);

  //页面初始化的时候加载水印
  onMounted(() => {
    watermark.set('smartAdminLayoutContent', useUserStore().actualName);
  });

  //回到顶部
  const backTopTarget = () => {
    return document.getElementById('smartAdminMain');
  };

  const router = useRouter();
  function goHome() {
    router.push({ name: HOME_PAGE_NAME });
  }

  const windowHeight = ref(window.innerHeight);
  window.addEventListener('resize', function () {
    windowHeight.value = window.innerHeight;
  });

  // ----------------------- keep-alive相关 -----------------------
  let { route, keepAliveIncludes, iframeNotKeepAlivePageFlag, keepAliveIframePages } = smartKeepAlive();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

展开菜单,具体可见 smart-side-expand-layout.vue 文件,具体请见 (opens new window)


# 联系我们

1024创新实验室-主任:卓大 (opens new window),混迹于各个技术圈,研究过计算机,熟悉点 java,略懂点前端。
1024创新实验室(河南·洛阳) (opens new window) 致力于成为中原领先、国内一流的技术团队,以技术创新为驱动,合作各类项目。

加 主任 “卓大” 微信
拉你入群,一起学习
关注 “小镇程序员”
分享代码与生活、技术与赚钱
请 “1024创新实验室” 喝咖啡
支持我们的开源与分享

告白气球 (钢琴版)
JESSE T