阅读视图

发现新文章,点击刷新页面。

【Fantastic-admin 技术揭秘】妙用Vue动态组件,让客制化更容易

《Fantastic-admin 技术揭秘》系列将带你了解 Fantastic-admin 这款框架各种功能的设计与实现。通过了解这些技术细节,你不光可以更轻松地使用 Fantastic-admin 这款框架,也可以在其他项目中使用这些技术。

你可以点击 这里 查看本系列的所有文章,也欢迎你在评论区留言告诉我你感兴趣的内容,或许下一篇文章就会带你揭秘其中的奥秘。

需求分析

作为一款面向开发者的后台系统框架,一直在尽可能满足各种业务场景的需求,但也确实没办法做到将各种业务需求都集成进去。既然很多功能框架无法直接满足,那就得思考如何降低客制化的成本,让开发者可以更轻松的进行扩展。

image.png

比如有这样一个需求,需要在左侧导航区域 Logo 下方增加一个组织切换的按钮,大部分开源的后台系统的解决方案就是找到导航对应的文件 → 阅读源码 → 编写业务代码 → 测试功能,这就涉及到了几个难点:

  • 需要阅读并修改系统源码。对开发者能力有要求,不求能看懂源码,但至少不能改出bug
  • 系统功能和业务功能的代码耦合。维护成本增加,后续维护的人也得重复第1点,还得区分出哪些是业务代码
  • 系统稳定性。任何直接对系统源码的修改,都无法100%保证绝对没问题,除非你完全熟悉系统源码

实现方案

使用Vue的动态组件,加上Vue插槽的设计理念,就可以很巧妙的解决这个问题。

只需在对应位置提供预留的“插槽”(这里的“插槽”实际上是Vue动态组件),然后按照约定的文件名去创建组件,然后在组件内就可以写业务代码,这个组件就会出现在对应的“插槽”位置上。

核心代码如下:

// src/slots/index.ts

import { pascalCase } from 'scule'

type Slots =
  'header-start' | 'header-after-logo' | 'header-after-menu' | 'header-end' |
  'main-sidebar-top' | 'main-sidebar-after-logo' | 'main-sidebar-after-menu' | 'main-sidebar-bottom' |
  'sub-sidebar-top' | 'sub-sidebar-after-logo' | 'sub-sidebar-after-menu' | 'sub-sidebar-bottom' |
  'tabbar-start' | 'tabbar-end' |
  'toolbar-start' | 'toolbar-end' |
  'free-position'

function tryLoadComponent(name: Slots) {
  const componentMap = import.meta.glob('./*/index.vue', { eager: true })
  const path = `./${pascalCase(name as unknown as string)}/index.vue`
  const component = componentMap[path as keyof typeof componentMap]
  if (!component) {
    return {
      default: defineComponent({
        name: 'SlotsInvalidComponent',
        render: () => null,
      }),
    }
  }
  return component
}

export function useSlots(name: Slots) {
  const component = tryLoadComponent(name)
  return defineComponent((component as any).default)
}

然后在需要的地方使用:

<component :is="useSlots('main-sidebar-after-logo')" />

这样一个基于插槽理念的动态组件就完成了,当需要使用这个插槽的时候,只需要创建 src/slots/MainSidebarAfterLogo/index.vue 文件即可。

Fantastic-admin 中,提供了许多这样的插槽,比如:

实现原理

阅读上面的核心代码,就可以发现其实现原理并不复杂,主要就是通过 import.meta.glob 动态导入 Vue 组件,然后通过 useSlots 函数返回组件。

同时需要注意下,当组件文件不存在时,会返回一个空的组件,这样不会影响到布局。

❌