sparrow-js·場(chǎng)景化低代碼搭建·編輯區(qū)塊篇(場(chǎng)景轉(zhuǎn)換代碼)
前言
sparrow-js 提供兩個(gè)重要提升研發(fā)效率的設(shè)計(jì):一個(gè)是編輯區(qū)塊,一個(gè)是搜索組件,本次主要介紹編輯區(qū)塊部分的設(shè)計(jì)思路;采用自問自答的方式說明編輯區(qū)塊的由來。
編輯區(qū)塊是什么?
特定場(chǎng)景功能的代碼片段,通過基礎(chǔ)組件和有特定功能的邏輯組件組合而成,可增刪改;可生成可讀性強(qiáng)的源代碼。
為什么會(huì)有編輯區(qū)塊?
編輯區(qū)塊是為sparrow-js的核心目標(biāo)提效量身定做的,sparrow本身有基本可視化搭建的能力,但是通用的可視化方案提效能力有限,可能只是基礎(chǔ)組件的拼接,操作繁雜,輸出的代碼更側(cè)重UI層面的代碼。前端開發(fā)由UI部分和邏輯部分組成,UI部分通過基礎(chǔ)組件的可視化搭建就可以完成,邏輯部分比如表單上面的刪除、編輯、上下線等操作,這些帶有特定功能邏輯的代碼需找到個(gè)介質(zhì)來承載,編輯區(qū)塊就是為了承載基礎(chǔ)UI和特定邏輯功能的容器。
編輯區(qū)塊是怎樣使用的?
先來張圖片:
選擇界面右邊的工具盒-》編輯區(qū)塊,點(diǎn)擊或者拖拽需要的區(qū)塊即可,點(diǎn)擊視圖區(qū)域即可以配置,刪除,新增等操作。
編輯區(qū)塊是怎樣制作出來的?
編輯區(qū)塊,以高級(jí)表單為例:
文件結(jié)構(gòu)
├── 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.首先需要先寫出完整的靜態(tài)功能區(qū)塊,如下簡(jiǎn)化代碼
<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() { // 獲取數(shù)據(jù) }, newMember () { // 新增 }, toggle (id) { // 編輯 }, cancel (id) { // 取消 }, remove (id) { // 移除 }, saveRow (row) { // 保存 }, }}</script>
- 將1代碼拆解成如上目錄結(jié)構(gòu), 如編輯按鈕,繼承基礎(chǔ)按鈕,定制化配置,在從vue文件取出方法、數(shù)據(jù)、引用等內(nèi)容,為后續(xù)組裝提供信息。
// 動(dòng)態(tài)化按鈕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>
- 將數(shù)據(jù)注冊(cè)到搜索組件,搜索結(jié)果如下圖:
通過點(diǎn)擊、拖拽放到想放的位置即可。
- 視圖區(qū)的操作數(shù)據(jù)傳回server server先生成組件對(duì)象樹,對(duì)2中的template部分、script部分、style部分進(jìn)行組裝,script部分通過babel ast對(duì)相應(yīng)組件進(jìn)行拆解,最后根據(jù)對(duì)象樹重新組裝成想要的代碼。拆解代碼如下:
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; }}
- 最后將文件輸出到對(duì)應(yīng)的項(xiàng)目下,實(shí)時(shí)預(yù)覽
編輯區(qū)塊到底有什么用?
編輯區(qū)塊實(shí)現(xiàn)了把靜態(tài)模版動(dòng)態(tài)化,可以自定義組合、添加想要的功能,可以中心化個(gè)項(xiàng)目中重復(fù)邏輯部分,可以統(tǒng)一風(fēng)格,統(tǒng)一代碼,可以為后續(xù)自動(dòng)生成代碼做鋪墊。
目前提供哪些編輯區(qū)塊
直接上圖:
數(shù)據(jù)面板
介紹面板
卡片詳情
卡片表單
步驟表單
高級(jí)表單
展示行表格
綜合表格
持續(xù)新增ing
編輯區(qū)塊還有什么問題?
目前遺留的todo有
- 邏輯操作不夠清晰;
- 定制化操作沒開放;
- 接口部分還沒開發(fā);
上面說的問題后續(xù)版本解決,目前還不能操作完直接上線,粗估生成的代碼可以覆蓋80%
總結(jié)
sparrow-js 核心思路是中心化場(chǎng)景領(lǐng)域、去中心化項(xiàng)目工程,編輯區(qū)塊是理論的具體落地,后續(xù)產(chǎn)品路線大致方向?yàn)榈谝徊綄?shí)現(xiàn)中心化前端代碼(目前在做),第二步實(shí)現(xiàn)數(shù)據(jù)綁定,插件化,第三步實(shí)現(xiàn)自動(dòng)生成代碼;感興趣可關(guān)注、可交流、可star、未來可期,
git地址
https://github.com/sparrow-js/sparrow
部分圖片和樣式直接使用的開源項(xiàng)目,如有任何疑問可以聯(lián)系我哦