sparrow-js·场景化低代码搭建-编辑区块篇

tech2024-12-06  9

前言

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>([\s\S])*<\/template>/g)[0];    const style = this.vueStr.match(/(?<=<style[\s\S]*>)[\s\S]*(?=<\/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>)[\s\S]*(?=<\/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

部分图片和样式直接使用的开源vue-某-pro(借鉴参考),如有任何疑问可以联系我哦

最新回复(0)