概述
本阶段,在上一阶段完成的DSL领域模型配置的基础上,完成了动态组件的拓展。在领域模型中配置新增组件的相关配置,通过组件解析器,解析成弹窗,表单,抽屉等等各类组件。
动态组件设计
1.组件设计思路
定义组件时可以参考这个思路
- 接收什么属性(props)
- 返回什么事件(emit)
- 暴露什么方法(defineExpose)
2.DSL中动态组件配置
- 注册组件:在 schemaConfig 的 componentConfig 项中,定义你所需的动态组件
- 关联配置:在 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 的开发范式:视图层现可通过声明式配置直接定义复杂交互组件(如弹窗、表单、抽屉),这意味着开发重心从繁琐的视图编码转向高效的配置化设计。这不仅是开发效率的提升,更是可维护性与扩展性的质的飞跃,从而为复杂中后台应用的快速迭代与规模化发展,提供了强有力的底层支撑。