vue3+ts组件库同时兼容多种ui框架

背景

基于之前组件库的基础上,其实,我们所有的schema配置都与ui层面没有关系,并且发现ant和element框架api大同小异,对于基于element开发出来的schema,初步核对了一下,ant上也是可行的;如下配置,完全与ui无关,与element和ant api初步核对确实可行:

const config = reactive<formConfig>({
  // colNum: 6,
  labelWidth: '100px',
  disabled: false,
  loading: false,
  isExport: true,
  columns: [
    {
      label: '业务域',
      prop: 'businessDomain',
      type: 'select',
      required: false,
      disabled: false,
      options: {
        type: 'api',
        getData: () => {
          return new Promise(async(resolve) => {
            const api: MonitoringPlatform = new MonitoringPlatform()
            const res = await api.monitoringPlatformConfigQueryBusinessDomain()
            if (res?.success) {
              resolve(res?.data.map((item: string) => ({
                label: item,
                value: item,
              })) ?? [])
            } else {
              resolve([])
            }
          })
        },
      },
      change: async(e: any) => {
        const options = await getBusinessScenarioOptions(e.value)
        // 清除业务场景&问题场景表单值
        form.value.businessScenario = ''
        form.value.errorType = ''
        // 修改业务场景options / 启禁用
        const businessScenarioItem = config.columns[getColumnIndex(config.columns, 'businessScenario')]  as selectProps
        businessScenarioItem.options = options
        businessScenarioItem.disabled = !e.value
        // 清空问题场景 option / 启禁用
        const errorTypeItem = config.columns[getColumnIndex(config.columns, 'errorType')]  as selectProps
        errorTypeItem.options = []
        errorTypeItem.disabled = !form.value.businessScenario
      },
      placeholder: '请选择',
    },
    {
      label: '业务场景',
      prop: 'businessScenario',
      type: 'select',
      required: false,
      disabled: true,
      options: [],
      change: async(e: any) => {
        // 清除问题场景表单值
        form.value.errorType = ''
        const options = await getErrorTypeOptions(e.value)
        // 清空问题场景options
        const errorTypeItem = config.columns[getColumnIndex(config.columns, 'errorType')] as selectProps
        errorTypeItem.options = options
        errorTypeItem.disabled = !e.value
      },
      placeholder: '请先选择业务域',
    },
    {
      label: '问题场景',
      prop: 'errorType',
      type: 'select',
      required: false,
      disabled: true,
      options: [],
      placeholder: '请先选择业务场景',
    },
    {
      label: '预警对象',
      prop: 'alertTarget',
      type: 'input',
      placeholder: '请输入',
    },
    {
      label: '重要程度',
      prop: 'importance',
      type: 'select',
      multiple: true,
      options: {
        type: 'dic',
        key: 'ReconciliationWarningImportantEnum',
      },
      labelKey: 'desc',
      valueKey: 'name',
      placeholder: '请选择',
    },
    {
      label: '配置人',
      prop: 'createUserName',
      type: 'input',
      placeholder: '请输入',
    },
  ],
})

目标

近期:基础组件表单及表格组件通过npm包发布之后,不管是基于elementplus项目还是基础ant的项目,npm下来之后可以直接使用,并且分别兼容各自ui框架的原生api

最终:分别形成一套vue2、vue3、react技术栈的基础组件,类似于ant Design vue 和 ant Design react形式;eg:nowayForm-vue、nowayForm-vue3、nowayForm-react

现在正在实施完成nowayForm-vue3

实施

思路:通过一个全局变量的方式来动态确认使用elementplus组件还是ant组件,转换器代码如下:

/*
 * @Author: 陈宇环
 * @Date: 2023-06-05 15:12:47
 * @LastEditTime: 2023-06-14 10:30:49
 * @LastEditors: 陈宇环
 * @Description:
 */
// 自定义动态组件
export class CustomDynamicComponent {
  // 定义this属性的类型
  [x: string]: JSX.Element | ((type: string) => JSX.Element)
  // 默认element-plus样式,在window下面进行注册
  static language = window.uiLanguage || 'ele'
  // element-plus对比基准
  static eleLanguage = 'ele'
  // ant-design-vue对比基准
  static antLanguage = 'ant'
  // 多种ui定义字典值
  static dicts: {
    [x in 'ele' | 'ant']: () => {
      [x in string]: JSX.Element
    }
  } = {
    ant: () => {
      return {
        row: <a-row/>,
        col: <a-col/>,
        form: <a-form/>,
        formItem: <a-form-item/>,
        button: <a-button/>,
        table: <a-table/>,
        tableColumn: <a-table-column/>,
        radio: <a-radio/>,
        pagination: <a-pagination/>,
        input: <a-input/>,
        password: <a-input-password/>,
        textarea: <a-textarea/>,
        number: <a-input-number/>,
        radioGroup: <a-radio-group/>,
        radioButton: <a-radio-button/>,
        select: <a-select/>,
        selectOption: <a-select-option/>,
        switch: <a-switch/>,
        cascader: <a-cascader/>,
        checkBox: <a-checkbox/>,
        checkBoxGroup: <a-checkbox-group/>,
        datePicker: <a-date-picker/>,
        checkBoxButton: <div/>,
        popconfirm: <a-popconfirm/>,
      }
    },
    ele: () => {
      return {
        row: <el-row />,
        col: <el-col />,
        form: <el-form />,
        formItem: <el-form-item />,
        button: <el-button />,
        table: <el-table />,
        tableColumn: <el-table-column />,
        radio: <el-radio />,
        pagination: <el-pagination />,
        input: <el-input />,
        password: <el-input />,
        textarea: <el-input />,
        number: <el-input-number />,
        radioGroup: <el-radio-group />,
        radioButton: <el-radio-button />,
        select: <el-select />,
        selectOption: <el-option />,
        switch: <el-switch />,
        cascader: <el-cascader />,
        checkBox: <el-checkbox />,
        checkBoxGroup: <el-checkbox-group />,
        datePicker: <el-date-picker />,
        checkBoxButton: <el-checkbox-button />,
        popconfirm: <el-popconfirm/>,
      }
    },
  }
  constructor() {
    // 首字母大写函数
    function ucFirst(str:string):string {
      const str1 = str[0].toUpperCase() + str.slice(1)
      return str1
    }
    const components:string[] = ['row', 'col', 'form', 'formItem', 'button', 'table', 'tableColumn', 'radio', 'pagination', 'input', 'password', 'textarea', 'number', 'radioGroup', 'radioButton', 'select', 'selectOption', 'switch', 'cascader', 'checkBox', 'checkBoxGroup', 'datePicker', 'checkBoxButton', 'popconfirm']
    components.forEach((item) => {
      // 根据组件名称自动注册组件
      this['dynamic' + ucFirst(item)] = this.getComponent(item)
    })
  }
  // 获取动态组件函数
  getComponent(type: string): JSX.Element {
    const methodMap = CustomDynamicComponent.dicts[window?.uiLanguage ?? 'ele']
    // 判断是否有ui主题组件库
    if (methodMap) {
      // 判断是否有定义的组件类型,没有返回div
      return methodMap()[type] || <div />
    }
    return <div />
  }
}

在window中定义全局变量

window.uiLanguage = 'ele'

使用

这里以input为例:

import { CustomDynamicComponent } from '@/components/CustomDynamicComponent'
const { dynamicInput } = new CustomDynamicComponent()

....
<dynamicInput
  class="input"
  type='text'
  model-value={props.modelValue}
  placeholder={props.config.placeholder || `请输入${props.config.label}`}
  disabled={!!props.config.disabled}

  /** ant-design-vue && ele 统一封装 - start */
  clearable={props.config.clearable !== false}  // ele 特有属性
  allowClear={props.config.allowClear ?? props.config.clearable !== false} // ant-design-vue特有属性
  /** ant-design-vue && ele 统一封装 - end */

  onInput={updateValue}
  {...props.config.nativeProps}
/>

缺点

需要使用项目全局引入ui框架(或者我们组件暴露出去必须要引入的组件)

存在小部分冗余代码

源码及实现浅析

https://juejin.cn/post/7246190449311940669

作者:快落的小海疼

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容