前端开发那些事儿每天学一点Vue3

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

2021-08-22  本文已影响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
      }
    }

定义菜单的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,就可以使用了。

上一篇下一篇

猜你喜欢

热点阅读