低代码初学

157 阅读3分钟

以前常看见有的工具可以直接实现简单页面的布局,不是纯敲代码的那种,而是通过拖拽的方式,分分钟就能完成一个基本页面的那种。比如说我们常见的表单,报表等。那么炫酷的操作背后是如何实现的呢?

基础知识

在开始撸代码之前,需要了解部分基础知识点,这里以react来实现:

  • html5的draggable属性,ondragstart 、ondragend、ondragover、ondrop原生拖拽方法
  • e.dataTransfer的setData和getData方法:用语记录拖拽元素的存取
  • react中:useState,useContext,事件如何绑定,表单受控组件

代码思路

image.png

区域划分:固定组件区域(左)、页面编辑区域(中)、属性设置区域(右)
固定组件区域:提供各个组件的展示,每个组件可拖拽到编辑区
页面编辑区域:拖拽到该区域的组件,可单独点击设置详细属性
属性设置区域:点击编辑区域某个组件时,显示具体的可配置项,属性值的改变会影响组件在编辑区域的显示

代码

文字描述太过抽象,直接上代码:

在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提升至根组件进行传递