一.前言
我们在第三阶段中学会了如何通过Schema配置与Dashboard 模板引擎实现了页面的自动渲染流程,但是缺乏对数据进行操作的功能,所以在本阶段重点就是Schema配置与动态操作组件配合来实现数据的增删改查操作
二.DSL中Schema 动态组件配置
在基于第三章中的Schema配置中增加componentConfig配置,来描述不同的操作加载不同的动态组件
JavaScript
componentConfig: {
// create-from 表单相关配置
createFrom: {
title: '',// 表单标题
saveBtnText: '',// 保存按钮文本
},
//edit-from 表单相关配置
editFrom: {
mainKey: '', //表单主键,用于唯一标识要修改的数据对象
title: '',// 表单标题
saveBtnText: '',// 保存按钮文本
},
// detailPanel 详情相关
detailPanel: {
mainKey: '', //表单主键,用于唯一标识要修改的数据对象
title: '',// 标题
},
},
动态组件加载触发加载的条件:
tableConfig配置项中的表格头部操作和表格操作栏按钮增加eventKey(触发事件)和eventOption.comName触发加载的组件名称- 删除操作特殊处理
eventOption.params中配置指定key字段进行删除操作
JavaScript
tableConfig: {
headerButtons: [{
label: '新增商品',
eventKey: 'showComponent',
eventOption: {
comName: 'createForm'
},
type: 'primary',
plain: true
}],
rowButtons: [
{
label: "查看详情",
eventKey: 'showComponent',
type: 'primary',
eventOption: {
comName: 'detailPanel'
},
},
{
label: "修改",
eventKey: 'showComponent',
type: 'warning',
eventOption: {
comName: 'editForm'
},
},
{
label: "删除",
eventKey: 'remove',
eventOption: {
params: {
product_id: "schema::product_id"
}
},
type: 'danger',
}],
},
三. 动态组件的渲染机制
1. schema-view 动态组件的引用
在 schema-view中通过对component-config.js中动态组件引入关系灵活组件管理
JavaScript
// component-config.js
import createForm from './create-form/create-form.vue'
import editForm from './edit-form/edit-form.vue'
import detailPanel from './detail-panel/detail-panel.vue'
const ComponentConfig = {
createForm: {
component: createForm,
},
editForm: {
component: editForm,
},
detailPanel: {
component: detailPanel,
},
export default ComponentConfig
2.动态组件的表单控件
针对于createForm和editForm动态组件进行的通用的schema-form widgets组件封装,对于schema-form表单实现结合shema中每个字段中配置的comType进行不同的表单控件渲染
同样通过form-item-config.js单独配置来管理表单控件的引入关系,便于后续的自定义表单控件的开发
JavaScript
// 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
进行表单组件封装时,我们要考虑如何校验表单与获取表单内容,在表单控件组件中我们都会导出name属性(控件名称),validate函数(校验函数),getValue函数(获取值)
JavaScript
// 以 input 控件为例子
// 校验函数 ,通过ajv校验当前的字段的shema中配置字段
// 例如input 中以字符串为主, 可能在schema中添加maxLength,minLength进行长度的要求
const validate = () => {
validTips.value = null
const { type } = schema
//校验是否必填
if (schema.option?.required && !dtoValue.value) {
validTips.value = '不能为空'
return false
}
//ajv 校验schema
if (dtoValue.value) {
const validate = ajv.compile(schema)
const vaild = validate(dtoValue.value)
if (!vaild && validate.errors && validate.errors[0]) {
const { keyword, params } = validate.errors[0]
if (keyword === 'type') {
validTips.value = `类型必须为${type}`
} else if (keyword === 'maxLength') {
validTips.value = `最大长度应为:${params.limit}`
} else if (keyword === 'minLength') {
validTips.value = `最小长度应为:${params.limit}`
} else if (keyword === 'pattern') {
validTips.value = `格式不正确`
} else {
console.log(validate.errors[0]);
validTips.value = '不符合要求'
}
return false
}
}
return true
}
//schema 配置
product_name: {
type: 'string',
label: '商品名称',
maxLength: 10, //最大长度校验条件
minLength: 3, //最小长度校验条件
tableOption: {
width: 200,
},
searchOption: {
comType: 'dynamicSelect',
placeholder: '请选择商品名称',
api: '/api/proj/product_enum/list',
},
createFormOption: {
comType: 'input', //在不同组件中该字段以什么样的表单控件进行展示
},
editFormOption: {
comType: 'input',
default: '测试一下'
},
detailPanelOption: {},
},
四. schema 解析Hook
通过schema配置转化为可视化界面中最重要的解析工具,从schema中将各个不同配置进行抽离出去形成新的option给组件进行页面渲染。
JavaScript
import { ref, watch, onMounted, nextTick } from 'vue'
import { useRoute } from 'vue-router'
import { useMenuStore } from '$store/menu.js'
export const useSchema = () => {
const route = useRoute()
const menuStore = useMenuStore()
const api = ref('')
const tableSchema = ref({})
const tableConfig = ref()
const searchSchema = ref({})
const searchConfig = ref()
const components = ref({})
//构造 schemaConfig 相关配置,,输送给 shemaView解释
const buildData = () => {
const { key, sider_key: siderKey } = route.query
const mItem = menuStore.findMenuItem({
key: 'key',
value: siderKey ?? key
})
if (mItem && mItem.schemaConfig) {
const { schemaConfig: sConfig } = mItem
const configSchema = JSON.parse(JSON.stringify(sConfig.schema))
api.value = sConfig.api ?? ''
tableSchema.value = {}
tableConfig.value = undefined
searchSchema.value = {}
searchConfig.value = undefined
components.value = {}
nextTick(() => {
//构造 tableSchema 和 tableConfig
tableSchema.value = buildDtoSchema(configSchema, 'table')
tableConfig.value = sConfig.tableConfig
//构造 searchSchema 和 searchConfig
const dtoSearchSchema = buildDtoSchema(configSchema, 'search')
for (const key in dtoSearchSchema.properties) {
if (route.query[key] !== undefined) {
dtoSearchSchema.properties[key].option.default = route.query[key]
}
}
searchSchema.value = dtoSearchSchema
searchConfig.value = sConfig.tableConfig
//构造 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
}
})
}
}
//通用构建 schema方法
const buildDtoSchema = (_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
}
watch([
() => route.params.key,
() => route.params.sider_key,
() => menuStore.menuList,
], () => {
buildData()
}, { deep: true })
onMounted(() => {
buildData()
})
return {
api,
tableSchema,
tableConfig,
searchSchema,
searchConfig,
components,
}
}
五. 总结
-
在动态组件封装中,使用配置文件统一组件引入管理,保持封装的灵活和扩展性
-
完善
schema配置,以componentConfig加载不同组件,增加不同组件的xxxxxOption配置完成该组件下不同的控件显示,最终形成我们所需要的交互动作