注:此文章仅是个人学习记录,如有错误,欢迎指出。
背景
在前后端开发中管理后台存在高度相似性,开发者每天进行大量重复性工作,如重复编写和使用类似的组件。DSL(Domain-Specific Language 领域特定语言)配置方案为了减少这种重复性劳动,提高开发效率,将公共模块进行抽离和沉淀。通过这种方式,可以实现对特定领域后台的快速搭建和拓展。
DSL 配置方面:
例如:DSL 用于描述指示板 dashboard 的模板页。通过 json-schema + js 对象 来进行配置。
头部菜单配置:最外层 menu 字段描述头部菜单,通过 menuType 区分是有子菜单的 group 还是菜单项的 module 。
菜单项配置:当 menuType 为 module 时,可展示侧边栏 sider 、数据页面 schema-view 或者定制化页面 custom-view或内嵌页面 iframe-view ,并分别有相应的具体配置。
整体的 DSL 配置总结:包括模板类型、名称、描述、图标、首页等信息,以及详细的菜单配置。
例如电商系统基类的配置:
module.exports = {
model: 'dashboard',
name: '电商系统',
menu: [ ... // 具体的菜单配置项 ]
};
而延伸的电商系统模型配置:
module.exports = {
name: '某宝',
desc: '某宝电商系统',
homePage: '/schema?proj_key=taobao&key=product',
menu: [ ... // 具体的菜单配置项 ]
};
当 moduleType 为 module 时,延展的自定义配置:
// 当 moduleType == sider 时
siderConfig: {
menu: [{
// 可递归 menuItem (除 moduleType === sider)
}, ...],
},
// 当 moduleType == iframe 时
iframeConfig: {
path: "", // iframe 路径
},
// 当 moduleType == custom 时
customConfig: {
path: "", // 自定义路由路径
,
在 schema-view,也可自定义配置表格或者搜索框等等,提高可扩展性
// 当 moduleType == schema 时
schemaConfig: {
api: "", // 数据源 API (遵循 RESTFUL 规范)
schema: { // 板块数据结构
type: "object",
properties: {
key: {
...schema, // 标准 schema 配置
type: "", // 字段类型
label: "", // 字段中文名
// 字段在 table 中的相关配置
tableOption: {
...elTableColumnConfig, // 标准 el-table-column 配置
toFixed: 0, // 保留小数点后几位
visiable: true, // 默认为 true (false 时,表示不在表单中显示)
},
// 字段在 search-bar 中的相关配置
searchOption: {
...eleComponentConfig, // 标准 el-component-columu 配置
comType: "", // 配置组件类型 input/select/...
default: "", // 默认值
// 当 comType === "select"
enumList: [], // 下拉框可选项
// comType === "dynamicSelect"
api: ""
}
},
...
}
},
// table 相关配置
tableConfig: {
headerButtons: [{
label: "", // 按钮中文名
eventKey: "", // 按钮事件名
eventOption: {}, // 按钮事件具体配置
..elButtonConfig // 标准 el-button 配置
, ...],
rowButtons: [{
label: "", // 按钮中文名
eventKey: "", // 按钮事件名
eventOption: {
// 当 eventKey === "remove"
params: {
// paramKey = 参数的键值
// rowValueKey = 参数值(当格式为 schema:: 且是tableKey 的时候,到 table 中找相应的字段)
paramKey: rowValueKey
}
}, // 按钮事件具体配置
...elButtonConfig // 标准 el-button 配置
}, ...]
},
searchConfig: {}, // search-bar 相关配置
components: {}, // 模块组件
},
}
DSL 解析方面
通过继承基类配置,获取整个要渲染的数据结构,方便后续页面搭建使用
module.exports = (app) => {
const modelList = [];
// 遍历当前文件夹,构造模型数据结构,挂在到 modelList 上
const modelPath = path.resolve(app.baseDir, `.${sep}model`);
const fileList = glob.sync(path.resolve(modelPath, `.${sep}**${sep}**.js`));
fileList.forEach((file) => {
if (file.indexOf("index.js") > -1) return;
// 区分配置类型(model / project)
const type = file.indexOf(`${sep}project${sep}`) > -1 ? "project" : "model";
if (type === "project") {
const modelKey = file.match(/\/model\/(.*?)\/project/)?.[1];
const projKey = file.match(/\/project\/(.*?)\.js/)?.[1];
let modelItem = modelList.find((item) => item.model?.key === modelKey);
if (!modelItem) {
// 初始化 model 数据结构
modelItem = {};
modelList.push(modelItem);
}
if (!modelItem.project) {
// 初始化 project 数据结构
modelItem.project = {};
}
modelItem.project[projKey] = require(path.resolve(file));
modelItem.project[projKey].key = projKey; // 注入projKey
modelItem.project[projKey].modelKey = modelKey; // 注入modelKey
}
if (type === "model") {
const modelKey = file.match(/\/model\/(.*?)\/model\.js/)?.[1];
let modelItem = modelList.find((item) => item.model?.key === modelKey);
if (!modelItem) {
// 初始化 model 数据结构
modelItem = {};
modelList.push(modelItem);
}
modelItem.model = require(path.resolve(file));
modelItem.model.key = modelKey; // 注入 modelKey
}
});
// 数据进一步整理: project => 继承 model
modelList.forEach((item) => {
const { model, project } = item;
for (const key in project) {
project[key] = projectExtendModel(model, project[key]);
}
});
return modelList;
};
总结
DSL 配置具有一定的复杂性,是需要一些约定的。DSL 配置需要对各种数据结构和配置项有清晰的理解,例如 json-schema 规范、菜单配置、模块类型配置、表格配置等。
对于需要频繁对接产品、后端、UI、测试,且存在大量 CRUD 工作的系统。例如电商系统等,通过配置 DSL 描述的模板页和基于领域模型,可以快速搭建大部分页面,提高开发效率。