1.概述
在Elpis项目领域模型架构主要是以基于 JSON Schema 的 DSL(领域特定语言)数据配置驱动页面渲染的架构。我们以一套标准项目提取出公共的Model配置结合可全自定义化的Project配置继承合并成独立完整项目的JSON Schema配置文件,结合dashboard 模板引擎解析最终生成我们需要的中后台项目。基于这样的DSL配置化领域模型架构下将80%业务逻辑沉淀,提高开发效率。
2.领域模型架构
1.领域模型架构设计

2.领域模型架构流程
1.项目DSL配置文件生成
通过Model基础配置与各个Project独立项目配置合并,形成各个项目完整的项目配置内容,并且在BFF Server层提供Api 获取配置项供给前端使用。
JavaScript
//`Model`基础配置与各个`Project`独立项目配置合并继承
const _ = require('lodash');
// project 继承 model
const projectExtendModel = (model, project) => {
return _.mergeWith({}, model, project, (modelValue, projValue) => {
//处理数组合并的特殊情况
if (Array.isArray(modelValue) && Array.isArray(projValue)) {
let result = []
//因为 project 继承model,所以需要修改和新增内容的情况
//project 有的键值,model也有 => 修改(重载)
//project 有的键值,model没有 => 新增 拓展)
//model 有的键值,project没有 => 保留(继承)
//处理修改和保留
for (let i = 0; i < modelValue.length; i++) {
let modelItem = modelValue[i]
const projItem = projValue.find(projItem => projItem.key === modelItem.key)
// project有的键值,model也有,则递归调用 projectExtendModel 方法覆盖修改
result.push(projItem ? projectExtendModel(modelItem, projItem) : modelItem)
}
//处理新增
for (let i = 0; i < projValue.length; i++) {
let projItem = projValue[i]
const modelItem = modelValue.find(modelItem => modelItem.key === projItem.key)
if (!modelItem) {
result.push(projItem)
}
}
return result
}
});
}
2. Dashboard 模板引擎搭建
先了解下DSL中JSON Schema配置文件字段以便于好了解Dashboard 模板引擎是如何进行解析
JavaScript
{
model: 'Dashboard',//模版类型,不同模版类型对应不一样的模版数据结构
name: "",//模版名称
desc: "",//模版描述
icon: "",//模版图标
homePage: "",//首页路径(项目配置)
//头部菜单
menu: [{
key: '',//菜单key,唯一标识
name: '',//菜单名称
menuType: '',//菜单类型,枚举值,group / module
// 当menuType == group 可填,展示子菜单
subMenu: [{
//可递归menuItem,{ key,name,menuType... }
}, ...]
// 当 menuType == module 可填
moduleType: '',//模块类型,枚举值: sider / iframe / custom/ schema
// 当 moduleType == sider 时
siderConfig={
menu: [{
// 可递归menuItem(除去moduleType == sider)
//也就是说在侧边栏的菜单配置不能再 进行moduleType == sider 配置这样不合理
//多级菜单 menuType=group 配置子菜单
}]
}
// 当 moduleType == iframe 时
iframeConfig: {
path: ''//iframe 路径
},
// 当 moduleType == custom 时
customConfig: {
path: ''//自定义路由路径
},
// 当 moduleType == schema 时
schemaConfig={...}
}]
}
1. 导航栏与侧边栏
在DSL中根据menu字段进行导航栏上的菜单的渲染,在通过menuType字段判断是否导航栏上当前菜单需要子菜单展示,moduleType表示当前菜单在页面上的展示方式。只是moduleType=sider时需要侧边栏菜单。
- header-container(导航栏组件) : 根据
menu菜单配置内容生成导航栏 - sider-container(侧边栏组件) : 根据
siderConfig.menu配置内容生成侧边栏
2. 页面内容展示方式
我们将main-content 插槽下作为页面的核心渲染区域,主要有三种方式展示页面,以配置项moduleType(iframe/custom/schema)值进行展示区分,在前端以划分不同路由分别解析对应的页面组件。
JavaScript
[
// moduleType === iframe 时,处理iframeConfig
{
path: '/view/dashboard/iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue')
},
// moduleType === schema 时,处理schemaConfig
{
path: '/view/dashboard/schema',
component: () => import('./complex-view/schema-view/schema-view.vue')
},
// moduleType === custom 时,处理customConfig
{
path: '/view/dashboard/todo',
component: () => import('./todo/todo.vue')
},
// moduleType === sider 时,处理siderConfig
{
path: '/view/dashboard/sider',
component: () => import('./complex-view/sider-view/sider-view.vue'),
children: [
{
path: 'iframe',
component: () => import('./complex-view/iframe-view/iframe-view.vue')
},
{
path: 'schema',
component: () => import('./complex-view/schema-view/schema-view.vue')
},
{
path: 'todo',
component: () => import('./todo/todo.vue')
},
]
},
//进行sider路由兜底
{
path: '/view/dashboard/sider/:chapters',
component: () => import('./complex-view/sider-view/sider-view.vue'),
},
]
2.1 iframe-view
这种方式主要使用iframe控件加载第三方页面,实现不同页面的集成方案
JavaScript
moduleType: 'iframe',
iframeConfig: {
path: 'https://www.baidu.com/',
}
2.2 custom-view
当然以这种方式进行加载时,没有特定组件进行加载,因为这个属于用户自定义路由方式进行呈现, 这样就给用户在该架构下进行定制化开发。
JavaScript
// 因为在该架构下首屏采用SSR方式加载,后续页面采用CSR模式进行路由跳转
// 当 moduleType == custom 时
moduleType: 'custom',
customConfig: {
path: '/todo' //自定义路由路径
},
//前端routes 路由定义
{
path: '/view/dashboard/todo',
component: () => import('./todo/todo.vue')
},
2.3 schema-view 重点
schema-view解析架构图,以商品管理模块进行展示
商品管理Schema配置如下
JavaScript
{
key: 'product',
name: '商品管理',
menuType: 'module',
moduleType: 'schema',
schemaConfig: {
api: '/api/proj/product',
schema: {
type: 'object',
properties: {
product_id: {
type: 'string',
label: '商品ID',
tableOption: {
width: 300,
"show-overflow-tooltip": true
},
},
product_name: {
type: 'string',
label: '商品名称',
tableOption: {
width: 200,
},
searchOption: {
comType: 'dynamicSelect',
placeholder: '请选择商品名称',
api: '/api/proj/product_enum/list',
}
},
price: {
type: 'number',
label: '价格',
tableOption: {
width: 200,
},
searchOption: {
comType: 'select',
placeholder: '请选择价格',
enumList: [{
value: -999,
label: '全部'
}, {
value: '39.9',
label: '¥39.9'
}, {
value: '199',
label: '¥199'
}, {
value: '699',
label: '¥699'
}]
}
},
inventory: {
type: 'number',
label: '库存',
tableOption: {
width: 200,
},
searchOption: {
comType: 'input',
placeholder: '请输入商品名称'
}
},
create_time: {
type: 'string',
label: '创建时间',
tableOption: {},
searchOption: {
comType: 'dateRange',
}
},
}
},
tableConfig: {
headerButtons: [{
label: '新增商品',
eventKey: 'showComponent',
type: 'primary',
plain: true
}],
rowButtons: [
{
label: "修改",
eventKey: 'showComponent',
type: 'warning',
},
{
label: "删除",
eventKey: 'remove',
eventOption: {
params: {
product_id: "schema::product_id"
}
},
type: 'danger',
}],
}
}
},
在schema-view中search-panel(搜索)与 table-panel(表格)组成,首先我们就要针对schema配置进行拆分找出我们需要的配置内容,给其下的组件进行使用
JavaScript
//通用构建 schema方法
// _schema配置选项, comName=>table / search
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`] })
dtoSchema.properties[key] = dtoProps
}
}
return dtoSchema
}
2.3.1 table-panel
该组件主要完成表格Schema配置页面渲染
- 通过
tableConfig.headerButtons完成表格上面操作新增商品按钮渲染 - 通过
tableConfig.rowButtons完成表格每列操作栏上完成修改,删除按钮,并针对按钮的事件eventKey进行相关的配置eventOption完成业务操作 - 使用
schema-table组件,完成业务接口请求,并通过schema.properties中每一项key完成表格渲染
2.3.2 search-panel
该组件主要完成表单搜索栏,进行对表格数据的筛选搜索功能
- 通过
schema.properties中每一项的searchOption.comType完成对应的动态表单加载展示, - 并使用
tableOption进行属性透传,使用UI组件已有的功能 - 以及针对不同表单类型提供自定义选项或者api请求获取选项数据
JavaScript
//不同表单组件的统一导出
import input from "./complex-view/input/input.vue";
import select from "./complex-view/select/select.vue";
import dynamicSelect from "./complex-view/dynamic-select/dynamic-select.vue";
import dateRange from "./complex-view/date-range/date-range.vue";
const SearchItemConfig = {
input: {
component: input,
},
select: {
component: select,
},
dynamicSelect: {
component: dynamicSelect,
},
dateRange: {
component: dateRange,
}
}
export default SearchItemConfig
//表单组件的引用
import SearchItemConfig from './search-item-config.js'
<!-- 动态组件 -->
<el-form-item v-for="(schemaItem, key) in schema.properties" :key="key" :label="schemaItem.label">
<!-- 子控件展示 -->
<component :ref="handleSearchComList" :is="SearchItemConfig[schemaItem.option?.comType].component"
:key="key" :schemaKey="key" :schema="schemaItem" @loaded="handleLoaded" />
</el-form-item>
3.总结
- 学习了
JsonShema配置化形成领域模型架构思路 - 学会提炼关键业务,从以前的开发代码的编写转变为业务需求的配置定义