基于 vue3 完成动态组件库建设

概述

本阶段,在上一阶段完成的DSL领域模型配置的基础上,完成了动态组件的拓展。在领域模型中配置新增组件的相关配置,通过组件解析器,解析成弹窗,表单,抽屉等等各类组件。

动态组件设计

1.组件设计思路

定义组件时可以参考这个思路

  1. 接收什么属性(props)
  2. 返回什么事件(emit)
  3. 暴露什么方法(defineExpose)

2.DSL中动态组件配置

  • 注册组件:在 schemaConfigcomponentConfig 项中,定义你所需的动态组件
  • 关联配置:在 schema.properties 的每个字段里,添加与组件同名的 Option 配置,用于精细化控制字段在组件中的表现
javascript 复制代码
module.exports = {
    schemaConfig: {
        api: '/api/proj/product',
        schema: {
            type: 'object',
            properties: {
                product_name: {
                    type: 'string',
                    label: '商品名称',
                    // json-schenma
                    maxLength: 10, // 最大程度
                    minLength: 3, // 最小长度
                    tableOption: {},
                    // 字段在不同动态 component 中的相关配置, 前缀对用 componentConfig 中的键值
                    // 如: componentConfig.createForm, 这里对应 createFormOption
                    // 字段在 createForm 中相关配置
                    editFormOption: {
                        ...eleComponentConfig, // 标准 el-component 配置
                        comType: '', // 控件类型 input/select/......
                        visible: true, // 是否展示 (true/false), 默认为 true
                        disabled: false, // 是否禁用 (true/false), 默认为 false
                        default: '', // 默认值

                        // comType === 'select' 时生效
                        enumList: [], // 下拉框和选项
                        
                        // comType === 'dynamicSelect'
                        api: ''
                    },
                    detailPanelOption: {},
                    createFormOption: {}
                },
                ...{}
            },
            required: ['product_name']
        },
        tableConfig: {
            headerButtons: [],
            rowButtons: [{
                label: '修改',
                eventKey: 'showComponent',
                eventOption: {
                    comName: 'editForm'
                },
                type: 'warning',
            },
            ]
        },
        componentConfig: {
            createForm: {},
            editForm: {
                mainKey: 'product_id',
                title: '修改商品',
                saveBtnText: '修改商品',
            },
            detailPanel: {}
        },
    }
}

3.组件配置解析器

javascript 复制代码
import { useMenuStore } from '@store/menu';
import { cloneDeep } from 'lodash/fp';
import { computed, reactive, toRefs, watchEffect } from 'vue';
import { useRoute } from 'vue-router';

export const useSchema = () => {
  const route = useRoute();
  const menuStore = useMenuStore();

  // 使用 reactive 管理相关状态
  const state = reactive({
    components: {},
  });

  // 通用构建 schema 方法, 不返回无关的字段
  const buildToSchema = (_schema, comName) => {
    if (!_schema?.properties) return {};

    const dtoSchema = {
      type: 'object',
      properties: {},
    };

    // 提取有效 schema 字段信息
    for (const key in _schema.properties) {
      const props = _schema.properties[key];
      if (props[`${comName}Option`]) {
        let dtoProps = {};
        // 提取 props 中非 option 的部分,存放到 dtoProps 中
        for (const pKey in props) {
          if (pKey.indexOf('Option') < 0) {
            dtoProps[pKey] = props[pKey];
          }
        }
        // 处理 comName Option
        dtoProps = Object.assign({}, dtoProps, { option: props[`${comName}Option`] });

        // 处理 required 字段
        const { required } = _schema;
        if (required && required.find((pk) => pk === key)) {
          dtoProps.option.required = true;
        }

        dtoSchema.properties[key] = dtoProps;
      }
    }

    return dtoSchema;
  };

  watchEffect(() => {
    const { key, sider_key } = route.query;
    const menuItem = menuStore.findMenuItem({
      key: 'key',
      value: sider_key ?? key,
    });

    if (!menuItem?.schemaConfig) return;

    const { schemaConfig: sConfig } = menuItem;
    const configSchema = cloneDeep(sConfig.schema);

    // 构造 components = { componentA: { schema, config } }
    const { componentConfig } = sConfig;
    if (componentConfig && Object.keys(componentConfig).length > 0) {
      const dtoComponents = {};

      for (const comName in componentConfig) {
        dtoComponents[comName] = {
          schema: buildToSchema(configSchema, comName),
          config: componentConfig[comName],
        };
      }

      state.components = dtoComponents;
    }
  });

  return {
    ...toRefs(state),
  };
};

经过 hook 解析后,最终 components 的数据结构如下:

javascript 复制代码
components = {
  comKey: { // 组件名称
    schema: {}, //  schema
    config: {} // 组件对应的 componentConfig 配置
  } 
}

配置组件映射,通过组件名渲染相应的组件,具体代码如下:

javascript 复制代码
import InputNumber from './complex-view/input-number/input-number.vue';
import Input from './complex-view/input/input.vue';
import Select from './complex-view/select/select.vue';

const FormItemConfig = {
  input: {
    component: Input,
  },
  inputNumber: {
    component: InputNumber,
  },
  select: {
    component: Select,
  },
};

使用动态组件

html 复制代码
<script setup>
import FormItemConfig from './form-item-config';
import { provide, ref, toRefs } from 'vue';

const props = defineProps({
  schema: {
    type: Object,
    default: () => {},
  },
  model: {
    type: Object,
    default: () => {},
  },
});
const { schema, model } = toRefs(props);

const FormComList = ref([]);
</script>

<template>
  <el-row v-if="schema && schema.properties" class="schema-form">
    <template v-for="(schemaItem, key) in schema.properties">
      <component
        :is="FormItemConfig[schemaItem?.option?.comType].component"
        v-show="schemaItem.option.visible !== false"
        ref="FormComList"
        :schema="schemaItem"
        :schema-key="key"
        :model="model ? model[key] : undefined"
      />
    </template>
  </el-row>
</template>

总结

动态组件机制是 DSL 驱动系统的核心,它通过解耦视图与业务逻辑,实现了组件的灵活创建与管理。该架构彻底升级了 Dashboard 的开发范式:视图层现可通过声明式配置直接定义复杂交互组件(如弹窗、表单、抽屉),这意味着开发重心从繁琐的视图编码转向高效的配置化设计。这不仅是开发效率的提升,更是可维护性与扩展性的质的飞跃,从而为复杂中后台应用的快速迭代与规模化发展,提供了强有力的底层支撑。

相关推荐
xhxxx8 小时前
Vite + React 黄金组合:打造秒开、可维护、高性能的现代前端工程
前端·react.js·vite
用户8168694747258 小时前
深入 useState、useEffect 的底层实现
前端·react.js
Tzarevich8 小时前
React 中的 JSX 与组件化开发:以函数为单位构建现代前端应用
前端·react.js·面试
李香兰lxl8 小时前
A I时代如何在研发团队中展现「前端」的魅力
前端
本末倒置1838 小时前
解决 vue2.7使用 pnpm 和 pinia 2.x报错
前端
CoderLiz8 小时前
Flutter中App升级实现
前端
Mintopia8 小时前
⚛️ React 17 vs React 18:Lanes 是同一个模型,但跑法不一样
前端·react.js·架构
李子烨8 小时前
吃饱了撑的突发奇想:TypeScript 类型能不能作为跑业务逻辑的依据?(纯娱乐)
前端·typescript
AAA简单玩转程序设计8 小时前
救命!Java小知识点,基础党吃透直接起飞
java·前端