sparrow-js·场景化低代码搭建·编辑区块篇(场景转换代码)
前言
sparrow-js 提供两个重要提升研发效率的设计:一个是编辑区块,一个是搜索组件,本次主要介绍编辑区块部分的设计思路;采用自问自答的方式说明编辑区块的由来。
编辑区块是什么?
特定场景功能的代码片段,通过基础组件和有特定功能的逻辑组件组合而成,可增删改;可生成可读性强的源代码。
为什么会有编辑区块?
编辑区块是为sparrow-js的核心目标提效量身定做的,sparrow本身有基本可视化搭建的能力,但是通用的可视化方案提效能力有限,可能只是基础组件的拼接,操作繁杂,输出的代码更侧重UI层面的代码。前端开发由UI部分和逻辑部分组成,UI部分通过基础组件的可视化搭建就可以完成,逻辑部分比如表单上面的删除、编辑、上下线等操作,这些带有特定功能逻辑的代码需找到个介质来承载,编辑区块就是为了承载基础UI和特定逻辑功能的容器。
编辑区块是怎样使用的?
先来张图片:
选择界面右边的工具盒-》编辑区块,点击或者拖拽需要的区块即可,点击视图区域即可以配置,删除,新增等操作。
编辑区块是怎样制作出来的?
编辑区块,以高级表单为例:
文件结构
├── AdvancedTable│ ├── AddButton│ │ ├── index.ts│ │ └── index.vue│ ├── CancelButton│ │ ├── index.ts│ │ └── index.vue│ ├── DeleteButton│ │ ├── index.ts│ │ └── index.vue│ ├── EditButton│ │ ├── index.ts│ │ └── index.vue│ ├── NewButton│ │ ├── index.ts│ │ └── index.vue│ ├── SaveButton│ │ ├── index.ts│ │ └── index.vue│ ├── index.ts│ └── init.ts
1.首先需要先写出完整的静态功能区块,如下简化代码
<template> <div class="app-container"> <el-table v-loading="listLoading" :data="list" > <el-table-column label="Title"> <template slot-scope="scope"> <el-input></el-input> <template v-else> {{ scope.row.title }}</template> </template> </el-table-column> <el-table-column label="操作" width="110" align="center"> <template slot-scope="scope"> <span v-if="scope.row.editable"> <span v-if="scope.row.isNew"> <a @click="saveRow(scope.row)">添加</a> <el-button slot="reference">删除</el-button> </span> <span v-else> <a @click="saveRow(scope.row)">保存</a> <a @click="cancel(scope.row.id)">取消</a> </span> </span> <span v-else> <a @click="toggle(scope.row.id)">编辑</a> <el-button slot="reference">删除</el-button> </span> </template> </el-table-column> </el-table> <el-button @click="newMember">新增</el-button> </div></template><script>import { getList } from '@/api/table'export default { data() { return { list: null, listLoading: true, tableItem: { id: '7100001', title: 'hello world', }, } }, created() { this.fetchData() }, methods: { fetchData() { // 获取数据 }, newMember () { // 新增 }, toggle (id) { // 编辑 }, cancel (id) { // 取消 }, remove (id) { // 移除 }, saveRow (row) { // 保存 }, }}</script>
- 将1代码拆解成如上目录结构, 如编辑按钮,继承基础按钮,定制化配置,在从vue文件取出方法、数据、引用等内容,为后续组装提供信息。
// 动态化按钮export default class EditButton extends Button{ name: string = 'EditButton'; vueParse: any; constructor (params: any) { super(params) this.config.model.custom.label = '编辑'; this.config.model.attr.size = 'mini'; this.config.model.attr.type = 'primary'; this.config.model.attr['@click'] = 'toggle(row.id)'; this.setAttrsToStr(); this.init(); } private init () { const fileStr = fsExtra.readFileSync(path.join(Config.templatePath, 'EditBlock/AdvancedTable/EditButton', 'index.vue'), 'utf8'); this.vueParse = new VueParse(this.uuid, fileStr); }}// 按钮VUE 文件,存放逻辑<template> <div> <el-button class="filter-item" style="margin-left: 10px;" typ e="primary" icon="el-icon-edit" @click="handleCreate"> 新增 </el-button> </div></template><script>export default { methods: { toggle (id) { const target = this.list.find(item => item.id === id) target._originalData = { ...target }; target.editable = !target.editable }, }}</script>
- 将数据注册到搜索组件,搜索结果如下图:
通过点击、拖拽放到想放的位置即可。
- 视图区的操作数据传回server server先生成组件对象树,对2中的template部分、script部分、style部分进行组装,script部分通过babel ast对相应组件进行拆解,最后根据对象树重新组装成想要的代码。拆解代码如下:
import * as cheerio from 'cheerio';import * as parser from '@babel/parser';import traverse from '@babel/traverse';import generate from '@babel/generator';import * as _ from 'lodash';export default class VueParse{ template: string = ''; data: any = []; methods: any = []; components: any = []; importDeclarations: any = []; uuid: string = ''; vueStr: string = ''; vueScript: string = ''; $: any; scriptAst: any; style: string = ''; created: any; constructor (uuid: string, vueStr: string) { this.uuid = uuid; this.vueStr = vueStr.replace(/_unique/g, this.uuid); this.init(); } private init () { const template = this.vueStr.match(/<template>([sS])*</template>/g)[0]; const style = this.vueStr.match(/(?<=<style[sS]*>)[sS]*(?=</style>)/g); if (style) { this.style = style[0]; } this.$ = cheerio.load(template, { xmlMode: true, decodeEntities: false }); this.template = this.$('.root').html(); this.vueScript = this.vueStr.match(/(?<=<script>)[sS]*(?=</script>)/g)[0]; this.scriptAst = parser.parse(this.vueScript, { sourceType: 'module', plugins: [ "jsx", ] }); this.data = this.getData() || []; this.methods = this.getMethods() || []; this.components = this.getComponents() || []; this.getImport(); this.created = this.getCreated(); } public getData () { let data = []; traverse(this.scriptAst, { ObjectMethod: (path) => { const { node } = path; if (node.key && node.key.name === 'data') { path.traverse({ ReturnStatement: (pathData) => { data = pathData.node.argument.properties } }) } } }); return data; } public setData (data: string) { const dataAst = parser.parse(data, { sourceType: 'module', plugins: [ "jsx", ] }); traverse(dataAst, { ObjectExpression: (path) => { if (path.parent.type === 'VariableDeclarator') { const {node} = path; this.data = node.properties; } } }); } public getFormatData () { const dataAst = parser.parse(`var data = { id: [] }`, { sourceType: 'module', plugins: [ "jsx", ] }); traverse(dataAst, { ObjectExpression: (path) => { if (path.parent.type === 'VariableDeclarator') { const {node} = path; node.properties = this.data; } } }) return generate(dataAst).code; } public getMethods () { let methods = []; traverse(this.scriptAst, { ObjectProperty: (path) => { const {node} = path; if (node.key.name === 'methods') { methods = node.value.properties; } } }); return methods; } public getComponents () { let components = []; traverse(this.scriptAst, { ObjectProperty: (path) => { const {node} = path; if (node.key.name === 'components') { components = node.value.properties; } } }); return components; } public getImport () { const body = _.get(this.scriptAst, 'program.body') || []; body.forEach(item => { if (item.type === 'ImportDeclaration') { this.importDeclarations.push({ path: _.get(item, 'source.value'), node: item }); } }); } public getCreated () { let created = null; traverse(this.scriptAst, { ObjectMethod: (path) => { const {node} = path; if (node.key.name === 'created') { created = node; } } }); return created; }}
- 最后将文件输出到对应的项目下,实时预览
编辑区块到底有什么用?
编辑区块实现了把静态模版动态化,可以自定义组合、添加想要的功能,可以中心化个项目中重复逻辑部分,可以统一风格,统一代码,可以为后续自动生成代码做铺垫。
目前提供哪些编辑区块
直接上图:
数据面板
介绍面板
卡片详情
卡片表单
步骤表单
高级表单
展示行表格
综合表格
持续新增ing
编辑区块还有什么问题?
目前遗留的todo有
- 逻辑操作不够清晰;
- 定制化操作没开放;
- 接口部分还没开发;
上面说的问题后续版本解决,目前还不能操作完直接上线,粗估生成的代码可以覆盖80%
总结
sparrow-js 核心思路是中心化场景领域、去中心化项目工程,编辑区块是理论的具体落地,后续产品路线大致方向为第一步实现中心化前端代码(目前在做),第二步实现数据绑定,插件化,第三步实现自动生成代码;感兴趣可关注、可交流、可star、未来可期,
git地址
https://github.com/sparrow-js/sparrow
部分图片和样式直接使用的开源项目,如有任何疑问可以联系我哦