赞
踩
基于react+antd实现可视化拖拽表单,因为公司项目是低代码平台,需要自定义拖拽表单。
现在有很多优秀的拖拽布局工具,基于react的比较少,UI是Ant Design。
废话不多说,直接贴图
布局分为左中右,左侧为自定义组件,中间为工作区,右侧为组件的各种属性。
工作区从左侧拖拽添加删除
右侧可以给当前组件自定义属性。
当前实现功能较少,只提供思路,贴部分代码…
import React, { Component } from 'react'; import { Rate, Input, DatePicker, Tag, Icon, Form } from 'antd'; import Sortable from 'react-sortablejs'; import uniqueId from 'lodash/uniqueId'; import update from 'immutability-helper' import styles from './index.css' import { indexToArray, getItem, setInfo, isPath, getCloneItem, itemRemove, itemAdd } from './utils' // import find from 'find-process'; const { MonthPicker, RangePicker, WeekPicker } = DatePicker; const GlobalComponent = { Rate, Input, MonthPicker, RangePicker, WeekPicker, Tag } const soundData = [ { name: '日期', attr: {} }, { name: 'RangePicker', attr: {} }, { name: 'WeekPicker', attr: {} }, { name: 'Input', attr: { size: 'large', value: '第一个' } }, { name: 'Tag', attr: { value: '11111' } }, { name: 'Containers', attr: { style: { background: '#fff', border: '1px #e5e5e5e5 solid' } }, } ] class Index extends Component { constructor(props) { super(props); this.state = { Data: [{ name: 'Input', attr: { size: 'large', value: '第一个' } }], checkedData: null }; } // 拖拽的添加方法 sortableAdd = evt => { console.log(evt) // 组件名或路径 const nameOrIndex = evt.clone.getAttribute('data-id'); // 父节点路径 const parentPath = evt.path[1].getAttribute('data-id'); // 拖拽元素的目标路径 const { newIndex } = evt; // 新路径 为根节点时直接使用index const newPath = parentPath ? `${parentPath}-${newIndex}` : newIndex; // 判断是否为路径 路径执行移动,非路径为新增 if (isPath(nameOrIndex)) { // 旧的路径index const oldIndex = nameOrIndex; // 克隆要移动的元素 const dragItem = getCloneItem(oldIndex, this.state.Data) // 比较路径的上下位置 先执行靠下的数据 再执行考上数据 if (indexToArray(oldIndex) > indexToArray(newPath)) { // 删除元素 获得新数据 let newTreeData = itemRemove(oldIndex, this.state.Data); // 添加拖拽元素 newTreeData = itemAdd(newPath, newTreeData, dragItem) // 更新视图 this.setState({ Data: newTreeData }) return } // 添加拖拽元素 let newData = itemAdd(newPath, this.state.Data, dragItem) // 删除元素 获得新数据 newData = itemRemove(oldIndex, newData); this.setState({ Data: newData }) return } // 新增流程 创建元素 => 插入元素 => 更新视图 const id = nameOrIndex const newItem = _.cloneDeep(soundData.find(item => (item.name === id))) // 为容器或者弹框时增加子元素 if (newItem.name === 'Containers') { // const ComponentsInfo = _.cloneDeep(GlobalComponent[newItem.name]) // 判断是否包含默认数据 newItem.children = [] } const Data = itemAdd(newPath, this.state.Data, newItem) // console.log(this.state.Data); this.setState({ Data }) } // 拖拽的排序方法 sortableUpdate = evt => { // 交换数组 const { newIndex, oldIndex } = evt; // 父节点路径 const parentPath = evt.path[1].getAttribute('data-id'); // 父元素 根节点时直接调用data let parent = parentPath ? getItem(parentPath, this.state.Data) : this.state.Data; // 当前拖拽元素 const dragItem = parent[oldIndex]; // 更新后的父节点 parent = update(parent, { $splice: [[oldIndex, 1], [newIndex, 0, dragItem]], }); // 最新的数据 根节点时直接调用data const Data = parentPath ? setInfo(parentPath, this.state.Data, parent) : parent // 调用父组件更新方法 this.setState({ Data }) } render() { // 递归函数 const loop = (arr, index) => ( arr.map((item, i) => { const indexs = index === '' ? String(i) : `${index}-${i}`; if (item.children) { return <div className={styles.itemDiv} {...item.attr} data-id={indexs} onClick={() => { this.setState({ checkedData: item }) }} > <span>{item.name}</span> <Icon type="delete" onClick={() => { // 删除元素 获得新数据 let newTreeData = itemRemove(indexs, this.state.Data); // 更新视图 this.setState({ Data: newTreeData }) }} /> <Sortable key={uniqueId()} style={{ minHeight: 100, margin: 10, }} ref={c => c && (this.sortable = c.sortable)} options={{ ...sortableOption, onUpdate: evt => (this.sortableUpdate(evt)), onAdd: evt => (this.sortableAdd(evt)), }} > {loop(item.children, indexs)} </Sortable> </div> } const ComponentInfo = GlobalComponent[item.name] return <div className={styles.itemDiv} data-id={indexs} onClick={(e) => { e.stopPropagation() this.setState({ checkedData: item }) }}> <div> <span>{item.name}</span> <Icon type="delete" onClick={() => { // 删除元素 获得新数据 let newTreeData = itemRemove(indexs, this.state.Data); // 更新视图 this.setState({ Data: newTreeData }) }} /> </div> <ComponentInfo {...item.attr} /> </div> }) ) const sortableOption = { animation: 150, fallbackOnBody: true, swapThreshold: 0.65, group: { name: 'formItem', pull: true, put: true, }, } return ( <div className={styles.homePage}> <div className={styles.leftDiv}> <Sortable options={{ group: { name: 'formItem', pull: 'clone', put: false, }, sort: false, }} > { soundData.map(item => { return <div data-id={item.name}><span className={styles.itemTag}>{item.name}</span></div> }) } </Sortable> </div> <div className={styles.content}> <Sortable ref={c => c && (this.sortable = c.sortable)} options={{ ...sortableOption, onUpdate: evt => (this.sortableUpdate(evt)), onAdd: evt => (this.sortableAdd(evt)), }} key={uniqueId()} > {loop(this.state.Data, '')} </Sortable> </div> <div className={styles.rigthDiv}> <Form > <Form.Item label='名称'> <Input placeholder='请输入控件名称' value={this.state.checkedData ? this.state.checkedData.name : ''} /> </Form.Item> <Form.Item label='默认值'> <Input placeholder='请输入默认值' /> </Form.Item> </Form> </div> </div> ); } } export default Index;
可以自己优化更新更多功能!!!
下载地址:
https://download.csdn.net/download/weixin_48356265/13287663
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。