elpis 动态组件扩展设计

动态组件扩展设计

动态组件DSL设计

javascript
// schemaConfig.schema.properties[key] 对每一个key 都有对应的配置
// 字段在不同动态 component 中的相关配置,前缀对应 componentConfig 中的键值
// 如: componentConfig.createForm, 这里对应 createFormOption
// 字段在 createForm 中的相关配置
createFormOption: {
    // ...eleComponentConfig, // 标准 el-component 配置
    comType: '', // 控件类型 input/select/input-number
    visible: true, // 是否展示(true/false), 默认为 true
    disabled: false, // 是否禁用(true/false), 默认为 false
    default: '', // 默认值
    // commType === 'select' 时生效
    enumList: [], // 枚举列表
}

// tableConfig.headerButtons
// 按钮事件具体配置
eventOption: {
  // 当 eventKey === 'showComponent'
  comName: '' // 组件名称
}

// tableConfig.rowButtons
// 按钮事件具体配置
eventOption: {
    // 当 eventKey === 'showComponent'
    comName: '',// 组件名称

    // 当 eventKey === 'remove'
    params: {
      // paramKey = 参数键
      // rowValueKey = 参数值(格式为 schema::tableKey ,到 table 中找相应的字段, 比如 user_id: schema::user_id)
      paramKey: rowValueKey
    }
},

// 动态组件 相关配置
componentConfig: {
   // create-form 表单相关配置
   createForm: {
       title: '', // 表单标题
       saveBtnText: '', // 保存按钮文案
   }
   // ...支持用户动态扩展
}

useSchema 处理 components 数据

javascript
export const useSchema = function () {
  const components = ref({});

  // ....

  // 构造 components: { comKey: { schema: {}, config: {} } }
  const { componentConfig } =  sConfig;
  if (componentConfig && Object.keys(componentConfig).length > 0) {
    const dtoComponents = {};
    for (const comName in componentConfig) {
      dtoComponents[comName] = {
        schema: buildDtoSchema(configSchema, comName),
        config: componentConfig[comName]
      }
    }
    components.value = dtoComponents;
  }

}

处理 required 字段

javascript
// 通用构建 schema 方法(清除噪音)
const buildDtoSchema = (_schema, comName) => {
  // ...
  // 提取有效 schema 字段信息
  for (const key in _schema.properties) {
    // ...
    // 处理 required 字段
    const { required } = _schema;
    if (required && required.find(pKey => pKey === key)) {
      dtoProps.option.required = true;
    }
  }
}

动态组件交互

javascript
// schema-vie/components/组件/组件.vue

// schema-vie/components/component-config.js
import createForm from './create-form/create-form.vue';

const ComponentConfig = {
  createForm: {
    component: createForm
  }
}

export default ComponentConfig;
// 可以在上面的代码中无限拓展组件

// 在 schema-view.vue 中使用
// 向子孙组件提供数据
import ComponentConfig from './components/component-config.js';
const { components } = useSchema();

provide('schemaViewData', {
  components
});

// table 事件映射
const EventHandlerMap = {
  showComponent: showComponent
}
// table 按钮操作映射
const onTableOperate = ({ btnConfig, rowData}) => {
  const { eventKey } = btnConfig;
  if (EventHandlerMap[eventKey]) {
    // 展示动态组件
    EventHandlerMap[eventKey]({ btnConfig, rowData });
  }
}

// showComponent 展示动态组件
function showComponent({ btnConfig, rowData}) {
  const { comName } = btnConfig.eventOption;
  if (!comName) {
    console.error('没配置组件名')
    return;
  }

  const comRef = comListRef.value.find(item => item.name === comName);
  if (!comRef || typeof comRef?.show !== 'function') {
    console.error(`找不到组件:${comName}`)
    return;
  }
  // 显示组件
  comRef?.show(rowData);
}

// 模板中加载动态组件 :is="ComponentConfig[key]?.component"
// <component
//   v-for="(item, key) in components"
//   :key="key"
//   :is="ComponentConfig[key]?.component"
//   ref="comListRef"
//   @command="onComponentCommand"
//   ></component>

// 动态组件暴露的方法
const show = (rowData) => {
  isShow.value = true;
}

//

动态组件 schema-form

vue
// widgets/schema-from/schema-form.vue
<script setup>
import { ref, toRefs, provide } from 'vue';
import FormItemConfig from "./form-item-config";

const Ajv = require('ajv');
const ajv = new Ajv();
provide('ajv', ajv);

const props = defineProps({
  /**
   * schema 配置, 结构如下:
   * {
   *   type: 'object',
   *   properties: {
   *       key: {
   *           label: '', // 字段的中文名
   *           type: '', // 字段类型
   *           option: {
   *               // ...eleComponentConfig, // 标准 el-component 配置
   *               comType: '', // 控件类型 input/select/input-number
   *               visible: true, // 是否展示(true/false), 默认为 true
   *               disabled: false, // 是否禁用(true/false), 默认为 false
   *               default: '', // 默认值
   *               // commType === 'select' 时生效
   *               enumList: [], // 枚举列表
   *               required: false, // 表单项是否必填,默认 false
   *           },
   *       },
   *       // ...
   *   },
   * }
   */
  schema: Object,

  /**
   * 表单数据
   */
  model: Object
})
const { schema } = toRefs(props);

const formComList = ref([]);

// 表单校验
const validate = () => {
  return formComList.value.every(component => component?.validate());
}
// 获取表单值
const getValue = () => {
  return formComList.value.reduce((dtoObj, component) => {
    return {
      ...dtoObj,
      ...component?.getValue()
    }
}, {});
}

defineExpose({
  validate,
  getValue
});
</script>

<template>
  <el-row
    v-if="schema && schema.properties"
    class="schema-form"
  >
    <template v-for="(itemSchema, key) in schema.properties">
      <component
        v-show="itemSchema.option.visible !== false"
        ref="formComList"
        :is="FormItemConfig[itemSchema.option?.comType]?.component"
        :schema-key="key"
        :schema="itemSchema"
        :model="model ? model[key] : undefined"
        >
      </component>
  </template>
</el-row>
</template>
js
// 在 widgets/schema-form/complex-view/组件/组件名字.vue 中编写组件,如input, input-number, select
// 在 widgets/schema-form/form-item-config.js 中注册组件
import input from './complex-view/input/input.vue';
import inputNumber from './complex-view/input-number/input-number.vue';
import select from './complex-view/select/select.vue';

const FormItemConfig = {
  input: {
    component: input
  },
  inputNumber: {
    component: inputNumber
  },
  select: {
    component: select
  }
}

export default FormItemConfig;

可以在这里无限拓展组件

抽离并发布 elpis npm包
elpis 框架设计理念(领域模型架构建设)
欢迎来到前端练习生ZM的小站