美文网首页前端开发那些事儿每天学一点Vue3
封装第三方组件(22)重构一下后台管理的页面(组件)关系

封装第三方组件(22)重构一下后台管理的页面(组件)关系

作者: 自然框架 | 来源:发表于2021-08-22 13:13 被阅读0次

    之前用 vue-router 做了一个后台管理的页面,虽然实现了功能,但是感觉比较麻烦。

    原版

    我做了一个menu.json,然后依据这个json来绑定菜单,同时还要去设置路由。

    这个就比较麻烦,而且为了可以保持组件的状态,同时还有支持组件的扩展性,所以代码结构也比较绕,容易绕晕的那种。

    于是想重构一下。

    页面结构

    想了一下,对于后台管理来说,路由不是太重要,原本我也只是做了一级路由,再下一层的干脆把路由给抛掉了,既然这样,不如干脆不用 router 了。自己做一个简易版的就可以了。

    页面结构

    中规中矩的设计,上面是横向导航和其他信息,比如登录、用户状态等。
    左面是菜单,右面是“工作区”。
    工作区上面是动态tab,下面是列表组件。
    列表组件包含:查询、按钮、列表、分页等,每个部分都是单独的组件。

    菜单与动态tab

    一般点击菜单会触发路由,然后由路由加载对应的组件。
    现在不用路由了,那么怎么办呢?

    tab状态

    我们先定义一个状态来存放 tab 相关的各种数据

        // tab 的状态
        tabState () {
          return {
            trigger: '', // 触发者,菜单触发,要恢复到初始状态,tab触发,保持状态,还有url触发
            menu: [], // 菜单的数据
            tabData: [], // 存放 tab 信息的数组,绑定tab用
            activeModuleId: 0 // 当前激活的 menu/tab 的 id
          }
        }
    
    • tabData
      v-for 的方式创建 tab 。

    • activeModuleId
      当前的激活的tab,也是点击了哪个菜单。默认使用同样的标识,也就是模块ID。

    • menu
      存放 menu.json 的内容,备用。避免每次都读取json文件,好吧我知道有缓存,但是不还得import 嘛。

    • trigger
      这是一个要不要重置列表状态的问题。

    定义菜单的meta

    用 json 文件表达一个菜单:

    {
      "title": "演示项目",
      "menu": [
        {
          "id": 100,
          "parentId": 0,
          "title": "系统管理",
          "componentKind": "group",
          "icon": "el-icon-location"
        },
        {
          "id": 101,
          "parentId": 0,
          "title": "支持平台",
          "kind": "group",
          "componentKind": "group",
          "icon": "el-icon-location"
        },
        {
          "id": 120,
          "parentId": 100,
          "title": "角色管理",
          "componentKind": "list",
          "icon": "el-icon-location"
        },
        {
          "id": 121,
          "parentId": 100,
          "title": "操作日志",
          "componentKind": "list",
          "icon": "el-icon-location"
        },
        {
          "id": 122,
          "parentId": 100,
          "title": "变更日志",
          "componentKind": "list",
          "icon": "el-icon-location"
        }
      ]
    }
    

    没有采用嵌套的方式,而是基于关系型数据库做的,因为打算放在数据库里面保存。

    绑定菜单

    使用 el-menu 实现菜单功能。只支持二级。

      <el-menu
        :default-active="index"
        class="el-menu-vertical-demo"
        @select="select"
      >
        <el-submenu
          v-for="item in menu.filter(a => a.parentId===0)"
          :key="'menu_' + item.id"
          :index="item.id"
        >
          <template #title>
            <i :class="item.icon"></i>
            <span>{{item.title}}</span>
          </template>
          <el-menu-item-group>
            <el-menu-item
              v-for="item2 in menu.filter(a => a.parentId===item.id)"
              :key="'submenu_' + item2.id"
              :index="item2.id"
            >{{item2.title}}
            </el-menu-item>
          </el-menu-item-group>
        </el-submenu>
      </el-menu>
    

    设置单击事件。

    // 二级菜单被选中
    const select = (index, indexPath) => {
      // 设置状态,交给 tab 组件处理,tab 会监听
      tabState.trigger = 'menu'
      tabState.activeModuleId = parseInt(index)
      location.hash = '#' + tabState.activeModuleId
      isMenuSetHash = true
    }
    

    单击菜单,记入 tab 的状态,等待 tab 的监听。顺便做个 hash 的简易路由。

    创建tab

      // 监听 单击的菜单,即模块ID
      watch(() => tabState.activeModuleId, (moduleId) => {
        // 判断是否已经生成标签
        if (tabData.findIndex((item) => item.id === moduleId) === -1) {
          // 没有标签,创建新标签
          const m = tabState.menu.filter(a => a.id === moduleId)[0]
          if (typeof m !== 'undefined') {
            tabData.push({
              id: moduleId, // 模块ID
              componentKind: m.componentKind, // 组件字典类型
              title: m.title // 标签
            })
          }
        }
      },
      { immediate: true })
    

    监听模块ID,然后判断是否已经创建了tab,然后嘛,不用多说了。

      <el-tabs
        v-model="tabState.activeModuleId"
        type="card"
        @tab-click="tabClick"
      >
        <el-tab-pane label="桌面" :name="0">
          桌面
        </el-tab-pane>
        <el-tab-pane
          v-for="(item, index) in tabState.tabData"
          :key="'tab_' + item.id"
          :label="item.title"
          :name="item.id"
        >
          <template #label>
            <span>{{item.title}} &nbsp; &nbsp;
              <i class="el-icon-error" @click.stop="tabRemove(item.id, index)" ></i>
            </span>
          </template>
          <component
            :is="componentKind[item.componentKind]"
            :moduleId="item.id"
          >
          </component>
        </el-tab-pane>
      </el-tabs>
    

    比较简单的循环。

    动态组件与异步组件

    tab 搞定后,对应的组件如何加载呢?vue提供了动态组件和异步组件,这是一个非常漂亮的功能。

    异步组件

    我们先定义几个异步组件:

    import { defineAsyncComponent } from 'vue'
    
    /**
     * 注册动态组件
     */
    export default {
      master: defineAsyncComponent(() => import('../base-page/list-form.vue')),
      list: defineAsyncComponent(() => import('../base-page/list.vue')),
      mod: defineAsyncComponent(() => import('../base-page/form.vue')),
      form: defineAsyncComponent(() => import('../base-page/form.vue'))
    }
    

    然后做一个动态组件

    <component
      :is="componentKind[item.componentKind]"
      :moduleId="item.id"
    >
    

    这样就搞定了,支持扩展组件,需要啥组件加入啥组件即可。

    这个看着是不是有点眼熟?

    对,就是 router 里面路由设置时使用的动态路由,去掉 defineAsyncComponent 就一模一样的。

    为啥不放到菜单里面呢?放在菜单里面,那不相当于我又自己做了一个路由嘛。

    好吧,菜单首先是一个json,没法放这个异步组件,然后不方便复用,菜单加载的大多都是 list,菜单里面直接写list很方便,如果写路径,那就太麻烦了。

    最后,我真的不想自己做一个路由。

    支持扩展组件

    默认是使用固定的几个组件,但是也可以使用其他组件,只需要在动态组件那里注册一下,设定一个key,就可以使用了。

    相关文章

      网友评论

        本文标题:封装第三方组件(22)重构一下后台管理的页面(组件)关系

        本文链接:https://www.haomeiwen.com/subject/vomfiltx.html