突然间可视化拖拽的风好像在前端的各个角落吹起,自己也鼓捣了一下,代码基本开发完毕,做一下整理。
github项目地址:taro-designer
在线体验地址:taro-desiger
主要涉及技术点如下:
-
背景
-
技术栈
-
拖拽
-
包装组件
-
数据结构
-
编辑器
-
单个组件操作
-
生成taro的源码
-
预览和下载源码
背景
公司有一部分业务是做互动的开发,比如签到、礼品兑换等。由于互动的业务需要快速迭代,并且需要支持H5、微信小程序、以及淘宝小程序,因此前端采用了taro作为基础框架来满足多端的需求。因此我们思考是不是采用可视化的方式对基础的组件进行拖拉拽,直接生成页面布局,提高开发效率。
面对项目的种种局限,采用的是taro2.x库,以及taro自带的组件库,非taro-ui。因为taro支持的属性参差不齐,和业务方讨论之后,我们取tarojs组件库支持的h5和微信小程序的交集进行属性编辑。
技术栈
react、mobx、cloud-react、tarojs
拖拽
从左侧可选择的组件拖拽元素到编辑器中,在编辑器里面进行二次拖拽排序,解决拖拽位置错误,需要删除重新拖拽的问题。
我们采用react-dnd作为拖拽的基础库,具体用法讲解单独有项目实践和文章说明,在此不做赘述。
项目代码: react-dnd-nested
demo地址:react-dnd-nested-demo
包装组件
这里包装的是taro的组件,也可以为其他的第三方组件。每个组件包含index.js
用于包装组件的代码 和config.json
文件用于组件配置数据, 举个 Switch
组件的例子:
// Switch index.js
import React, {
Component } from 'react';
import PropTypes from 'prop-types';
import {
Switch } from '@tarojs/components/dist-h5/react';
export default class Switch1 extends Component {
render() {
const {
style, ...others } = this.props;
return <Switch style={
style} {
...others} />;
}
}
Switch1.propTypes = {
checked: PropTypes.bool,
type: PropTypes.oneOf(['switch', 'checkbox']),
color: PropTypes.string,
style: PropTypes.string
};
Switch1.defaultProps = {
checked: false,
type: 'switch',
color: '#04BE02',
style: ''
};
// config.json
{
// 组件类型标识
"type": "Switch",
// 组件名称
"name": "开关选择器",
// 是否可放置其他组件
"canPlace": false,
// 默认的props数据,与index.js中的 defaultProps 基本保持一致
"defaultProps": {
"checked": false,
"type": "switch",
"color": "#04BE02"
},
// 默认样式
"defaultStyles": {
},
// props字段的具体配置
"config": [
{
// key值标识
"key": "checked",
// 配置时候采用的组件:大概有Input、Radio、Checkbox、Select 等
"type": "Radio",
// 文案显示
"label": "是否选中"
},
{
"key": "type",
"type": "Select",
"label": "样式类型",
// 下拉数据源配置
"dataSource": [
{
"label": "switch",
"value": "switch"
},
{
"label": "checkbox",
"value": "checkbox"
}
]
},
{
"key": "color",
"label": "颜色",
"type": "Input"
}
]
}
预置脚本
永远坚信代码比人更加高效、准确、靠谱。
生成组件模板脚本
每个组件都是包装taro对应的组件,因此我们预置index.js
和config.json
文件的代码,代码中设置一个__ComponentName__
的特殊字符为组件名称,执行生成脚本,从用户的输入读取进来再正则替换,即可生成基础的代码。这块可以查看具体代码,生成脚本如下:
const path = require('path');
const fs = require('fs');
const readline = require('readline').createInterface({
input: process.stdin,