以前常看见有的工具可以直接实现简单页面的布局,不是纯敲代码的那种,而是通过拖拽的方式,分分钟就能完成一个基本页面的那种。比如说我们常见的表单,报表等。那么炫酷的操作背后是如何实现的呢?
基础知识
在开始撸代码之前,需要了解部分基础知识点,这里以react来实现:
- html5的draggable属性,ondragstart 、ondragend、ondragover、ondrop原生拖拽方法
- e.dataTransfer的setData和getData方法:用语记录拖拽元素的存取
- react中:useState,useContext,事件如何绑定,表单受控组件
代码思路
区域划分:固定组件区域(左)、页面编辑区域(中)、属性设置区域(右)
固定组件区域:提供各个组件的展示,每个组件可拖拽到编辑区
页面编辑区域:拖拽到该区域的组件,可单独点击设置详细属性
属性设置区域:点击编辑区域某个组件时,显示具体的可配置项,属性值的改变会影响组件在编辑区域的显示
代码
文字描述太过抽象,直接上代码:
在left.js中写入
import './index.css'
import { componentsList } from '../../components/index'
const handleDragStart = (e, name) => {
console.log(">>>start", e, name)
e.dataTransfer.setData("text/plain", name);
}
const handleDragEnd = () => {
console.log("》》》结束")
}
function Material() {
return (
<div className="material">
<div>组件区域 </div>
{
componentsList.map((item, index) => (<div
key={index}
draggable
onDragStart={(e) => handleDragStart(e, item.name)}
onDragEnd={handleDragEnd}
className="material-item"
>{item.name}</div>))
}
</div>
);
}
export default Material;
在center.js中写入:
import './index.css'
import { componentsList } from '../../components/index'
import { useContext } from 'react'
import { PageContext } from '../../store'
import { v4 } from "uuid"; //这里借用uuid库,生成随机唯一id
function Editor() {
const { list, setList, setCurrentConfigId } = useContext(PageContext)
const handleDragOver = (e) => {
e.preventDefault();
};
const handleDrop = (e) => {
const droppedElementName = e.dataTransfer.getData("text/plain");
console.log(">>>droppedElementName", droppedElementName);
setList(
list.concat({
id: v4(),
name: droppedElementName,
props: {},
style: {},
children: []
})
)
}
const handleConfig = (id) => {
setCurrentConfigId(id)
}
const getComponent = (name) => {
return componentsList.find(findItem => findItem.name === name)
}
const renderComponents = () => {
return list.map(item => {
const Component = getComponent(item.name).component
return (
<span onClick={() => handleConfig(item.id)} key={item.id}> <Component {...item.props} /></span>
)
})
}
return (
<div className="editor">
<div
className="editor-container"
onDragOver={handleDragOver}
onDrop={handleDrop}
>
{renderComponents()}
</div>
</div>
);
}
export default Editor;
在right.js中写入
import { useContext } from 'react';
import './index.css'
import { PageContext } from '../../store';
import { componentsList } from '../../components';
import { cloneDeep } from "lodash";
function Config() {
const { currentConfigId, setList, list } = useContext(PageContext)
const renderConfig = () => {
if (!currentConfigId) {
return <div>请点击需要配置的组件</div>
}
const listItem = list.find(item => item.id === currentConfigId)
const component = componentsList.find(item => item.name === listItem.name)
const allProp = component.propsType
const currentProp = listItem.props;
const handleChange = (e, name) => {
const value = e.target.value
//这里将一个list覆盖,需要先进行深拷贝,然后修改其属性再进行覆盖(不能直接对list内容进行修改)
const cloneList = cloneDeep(list)
const target = cloneList.find(item => item.id === currentConfigId)
target.props[name] = value
console.log(cloneList)
setList(cloneList)
}
return (
<div>
<div>当前组件:{listItem.name}</div>
<div>
{
allProp.map((item, index) => {
return <div key={index}>
<span>{item.name + ':'}</span>
{/* 这里和编辑区要有一个互动的效果,所以需要设置受控组件 */}
<input
value={currentProp[item.name]}
onChange={(e) => handleChange(e, item.name)}
/>
</div>
})
}
</div>
</div>
)
}
return (
<div className="config">
<div>属性配置区域</div>
{renderConfig()}
</div>
);
}
export default Config;
main.js中,综合布局如下:
import Material from '../martial/index'
import Editor from '../editor'
import Config from '../config'
import './index.css'
import { PageContext } from '../../store'
import { useState } from 'react'
function Contanier() {
//低代码拖拽知识点
//1.ondragstart 、ondragend、ondragover、ondrop原生拖拽方法,元素draggable的属性
//2.e.dataTransfer的setData和getData方法
//3.context的使用,hook中的useState,useContext
//4.事件的绑定 ok
//5.表单受控组件的使用 ok
const [list, setList] = useState([]) //从left拖拽到center中的组件列表
const [currentConfigId, setCurrentConfigId] = useState(''); //当前编辑组件的id(配置对应属性)
return (
<PageContext.Provider value={{ list, setList, currentConfigId, setCurrentConfigId }}>
<div className="container">
<Material />
<Editor />
<Config />
</div>
</PageContext.Provider>
)
}
export default Contanier
这里指的注意的是,center和right区域是有交互的,center中点击某个组件,right中要渲染对应组件的属性配置;而right中对属性内容进行更改时,center中对应组件会实时更新;所以考虑将list和currentConfigId提升至根组件进行传递