本文为可视化编程的初尝试,仅实现基本核心功能。以配置后台表单功能页为主。
功能演示
配置页面:
生成代码:
import React, { FC, useEffect, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import Components from '@/components';
import { getStudent } from '@/services'
const { Card, Form, Row, FormItemInput, SearchBtn, FormItemButton, MyTable } = Components;
const layout = {
labelCol: {
span: 8,
},
wrapperCol: {
span: 16,
},
};
const App: FC<any> = (props: any) => {
const columns: any = [
{
title: '姓名',
key: 'name',
width: 20,
dataIndex: 'name',
},
{
title: '学号',
key: 'id',
width: 20,
dataIndex: 'id',
},
{
title: '学校',
key: 'school',
width: 20,
dataIndex: 'school',
},
];
const [params, setParam] = useState<any>({
name: '',
id: '',
school: '',
pageNo: 1,
pageSize: 10,
});
const [data, setData] = useState([]);
const [dataTotal, setTotal] = useState<number>(0);
function getData(pageNo?: number) {
getStudent({ ...params, pageNo: pageNo || 1 }).then((res: any) => {
setData(res.data.records);
setTotal(res.data.total);
});
}
useEffect(() => { }, []);
return (
<Form {...layout} hideRequiredMark>
<PageContainer>
<Card bordered={false}>
<Row>
<FormItemInput
label="姓名"
name="name"
onChange={(e: any) => {
setParam({ ...params, name: e.target.value });
}}
></FormItemInput>
<FormItemInput
label="学号"
name="id"
onChange={(e: any) => {
setParam({ ...params, id: e.target.value });
}}
></FormItemInput>
<FormItemInput
label="学校"
name="school"
onChange={(e: any) => {
setParam({ ...params, school: e.target.value });
}}
></FormItemInput>
<FormItemButton>
<SearchBtn onClick={getData}></SearchBtn>
</FormItemButton>
</Row>
<MyTable columns={columns} data={data} dataTotal={dataTotal} onChange={getData}></MyTable>
</Card>
</PageContainer>
</Form>
);
};
export default App;
复制代码
程序架构
传统技能,上图:
程序的数据流使用Redux进行统一管理,声明了3个变量:ifDrap、domList、selectDom。
程序设计思想为:通过拖拽实现将选择的组件数据录入到domList中。组件树框提供了选中编辑,点击需要编辑的组件既可以设置selectDom,在属性设置中设置相应属性。
数据流
Redux数据结构:
export interface StateType {
ifDrap: boolean,
domList: any,
selectDom: any
}
export interface LoginModelType {
namespace: string;
state: StateType;
effects: {
ifDrap: Effect;
domList: Effect
selectDom: Effect
};
reducers: {
changeIfDrap: Reducer<StateType>;
changeDomList: Reducer<StateType>
changeSelectDom: Reducer<StateType>
};
}
复制代码
ifDrap
拖拽插件使用的react-dnd,因需要实现多层的拖拽,插件本身并不提供。因ReactDND监听事件是冒泡事件,故需此参数判断拖拽的层级。
const [_, droper] = useDrop({
accept: accept,
collect: (minoter: DropTargetMonitor) => {
},
drop: (item) => {
if (!userLogin.ifDrap) {
dispatch({
type: 'login/login',
payload: {
ifDrap: true,//修改ifDrap,因拖拽为冒泡事件,后续的拖拽事件将不会触发
domList: [...]
},
});
}
},
})
复制代码
domList
[
{
childDom: (4)[{ … }, { … }, { … }, { … }]
prop: { style: { … } }
type: "Row"
},
{
prop: { style: { … }, columns: Array(3), data: Array(0), dataTotal: "0", onChange: ƒ }
type: "MyTable"
}
]
复制代码
domList存储了从reactCategory里拖拽过来的组件JSON,整个程序的核心数据。
ReactDnd
ReactDnd的api较为简单,可以前往官网自行学习。但ReactDND视乎并没有提供多层嵌套的标识,故需自定义如上的ifDrop作为标识。
reactCategory 组件的JSON合集
...
MyTable: {
components: {
MyTable: {
props: [
{ style: { backgroundColor: '#fff' }, columns: [], data: [], dataTotal: '0', onChange: () => { } }
]
}
}
}
...
复制代码
props为组件的属性,setProps操作对象。
JSON生成组件
function renderDragItem() {
let renderChild = prop.children
if (childDom) {
renderChild = childDom.map((item: any, index: number) => {
let temp = cloneDeep(level)
temp.push(index)
return <DropItem type={item.type} level={temp} prop={item.prop} childDom={item.childDom}></DropItem>
})
}
return createElement(get(MyCom, type, type), {
...prop,
}, renderChild)
}
复制代码
- level:例:[0,0]。level是组件的深度位置,标识出组件在domList中的位置。
exportFun 导出代码
导出代码的设计为:
getNewComponentNode 数组JSON to AST
// 组件属性处理
let propStr = map(prop, (item, key) => {
if (noConfigPro.get(key)) {
return noConfigPro.get(key)
}
if (Object.prototype.toString.call(item) === '[object Object]') {
if (key === 'style') {
return ''
}
if (key === 'children') {
return children?.push(item)
}
let objPropStr = map(item, (propItem, propKey) => {
return `${propKey}: "${propItem}"`
}).join(',')
return `${key}={{${objPropStr}}} `
} else if (Object.prototype.toString.call(item) === '[object Array]') {
if (key !== 'data') {
stateConst(item, key)
}
return `${key}={${key}} `
} else if (Object.prototype.toString.call(item) === '[object String]') {
if (key === 'children') {
let temp = cloneDeep(children)
temp?.push(item)
children = temp
return ''
}
return `${key}="${item}" `
}
}).join(' ')
复制代码
组件的属性有多种,通过 Object.prototype.toString.call进行判断做各种解析。考虑到需要加入自定义组件,一些自定义的组件的属性需要特殊解析,故加了componyAddParsePros。
componyAddParsePros: 组件附加解析规则
const componyAddParsePros = new Map()
componyAddParsePros.set('MyTable', MyTableParse)
function MyTableParse(ast?: any, noAction?: boolean, prop?: any) {
let noConfigPro = new Map()
traverse(ast, {
XXX(node: any) {
...
}
})
noConfigPro.set(xxx, xxxx)
return noConfigPro
}
复制代码
componyAddParsePros返回Map,每次解析数组JSON的属性都会匹配map,如果存在自定义的解析规则则使用匹配到的自定义解析规则。
小结
通过AST+ babel实现组件拖拽生成代码,组件库可为自定义组件也可为开源组件库。组件的特殊属性配置也可自定义解析规则。生成的代码可读性高,二次修改也较为简单。本次开发功能集中于管理后台的重复表格操作功能页,但也可适用于H5的活动页生成。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END