美文网首页
React拖拽组件react-grid-layout实现表单设计

React拖拽组件react-grid-layout实现表单设计

作者: kongxx | 来源:发表于2024-04-02 08:50 被阅读0次

    最近在看在线表单设计,找了一些现成的产品和库,今天就看看怎样使用 React-Grid-Layout 实现表单设计。

    React-Grid-Layout是一个基于 react 的网格布局系统,可实现基于表格的拖拽功能。

    创建工程

    npx create-react-app myapp
    

    安装依赖库

    npm install react-grid-layout
    

    另外例子还使用了boostrap做渲染,因此还需要安装 boostrap 和 react-bootstrap。

    npm install bootstrap
    npm install react-bootstrap
    

    代码实现(最后附完整 App.js 实现代码)

    看一下要实现的功能和布局:

    • 左边是个控件列表,这里只放了三个控件:input, password和select;这里的控件需要增加 draggable 属性,标识控件可拖拽,比如:
    <Button variant="primary" name="input"
        draggable={true}
        unselectable="on"
        onDragStart={onDragStartForDraggable}>
        Input
    </Button>
    
    • 右边是个布局区域,可以在上面拖拽摆放控件位置,使用 react-grid-layout 的 Responsive 实现。当每个左侧控件拖到这个区域后,将根据具体类型,展示位具体样式。

    下面看一下代码实现,首先初始化三个控件,用来默认摆放着右侧的布局区域内

      # 初始化三个控件
      let items = ["input", "password", "select"];
      
      # 初始布局,其中i对应上面的三个控件的名字,x表示横向位置,y表示纵向位置,w表示宽度,h表示高度
      let layout = [
        { i: "input", x: 0, y: 0, w: 5, h: 1, isResizable: false },
        { i: "password", x: 0, y: 1, w: 5, h: 1, isResizable: false },
        { i: "select", x: 0, y: 1, w: 5, h: 1, isResizable: false },
      ];
    
    • 定义右侧拖拽区域(GridLayout控件)的默认属性
      const defaultProps = {
        className: "layout",
        rowHeight: 40,
        onLayoutChange: function(data) {},
        breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
        cols: { lg: 1, md: 1, sm: 1, xs: 1, xxs: 1 },
        layouts: { lg: layout }
      };
    
    • 定义GridLayout控件参数
      const [state, setState] = useState({
        currentBreakpoint: "lg",
        compactType: "vertical",
        mounted: false,
        items: items,
        layouts: { lg: layout }
      });
    
    • 实现左侧控件的拖拽事件,主要是记住当前拖拽的是那个控件
      const onDragStartForDraggable = (e) => {
        currentDraggable = e.target.name + "_" + Date.now();
        e.dataTransfer.setData("text/plain", "");
      };
    
    • 实现右侧区域的的拖拽事件,主要是根据当前拖拽控件,设置名字和位置
      const onDrop = (layout, layoutItem, _event) => {
        layout = layout.toSorted((a, b) => a.y - b.y); 
        let newItems = [];
        layout.forEach((item) => {
          if (item.i === '__dropping-elem__') {
            item.i = currentDraggable;
          }
          item.isResizable = false;
          newItems.push(item.i);
        });
        layoutItem.i = currentDraggable;
        layoutItem.isResizable = false;
    
        setState({
          ...state,
          items: newItems,
          layouts: { lg: layout }
        });
        console.log("onDrop layout: ", layout);
        console.log("onDrop layoutItem: ", layoutItem);
      };
    
    • 控件标签代码
    <ResponsiveGridLayout
        {...defaultProps}
        className="layout"
        rowHeight={40}
        width={800}
        onDrop={onDrop}
        onLayoutChange={onLayoutChange}
        measureBeforeMount={false}
        useCSSTransforms={state.mounted}
        compactType={state.compactType}
        preventCollision={!state.compactType}
        isDroppable={true}
        >
    {state.items.map(el => {
        if (el.startsWith('input')) {
            ...
        } if (el.startsWith('password')) {
            ...
        } else if (el.startsWith('select')) {
            ...
        } else {
            ...
        }
    })}
    </ResponsiveGridLayout>
    

    完整 App.js 代码如下:

    import { useState } from 'react';
    import { Form, Container, Stack, Row, Col, Button, Accordion } from "react-bootstrap";
    import { Responsive as ResponsiveGridLayout } from "react-grid-layout";
    
    import logo from './logo.svg';
    import './App.css';
    import "bootstrap/dist/css/bootstrap.min.css";
    import "react-grid-layout/css/styles.css";
    import "react-resizable/css/styles.css";
    
    function App() {
    
      let items = ["input", "password", "select"];
    
      let layout = [
        { i: "input", x: 0, y: 0, w: 5, h: 1, isResizable: false },
        { i: "password", x: 0, y: 1, w: 5, h: 1, isResizable: false },
        { i: "select", x: 0, y: 1, w: 5, h: 1, isResizable: false },
      ];
    
      let currentDraggable = '';
    
      const [state, setState] = useState({
        currentBreakpoint: "lg",
        compactType: "vertical",
        mounted: false,
        items: items,
        layouts: { lg: layout }
      });
      
      const defaultProps = {
        className: "layout",
        rowHeight: 40,
        onLayoutChange: function(data) {},
        breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
        cols: { lg: 1, md: 1, sm: 1, xs: 1, xxs: 1 },
        layouts: { lg: layout }
      };
    
      const onLayoutChange = (layout, layouts) => {
        console.log("onLayoutChange layout: ", layout);
        console.log("onLayoutChange layouts: ", layouts);
      };
    
      const onDrop = (layout, layoutItem, _event) => {
        layout = layout.toSorted((a, b) => a.y - b.y); 
        let newItems = [];
        layout.forEach((item) => {
          if (item.i === '__dropping-elem__') {
            item.i = currentDraggable;
          }
          item.isResizable = false;
          newItems.push(item.i);
        });
        layoutItem.i = currentDraggable;
        layoutItem.isResizable = false;
    
        setState({
          ...state,
          items: newItems,
          layouts: { lg: layout }
        });
        console.log("onDrop layout: ", layout);
        console.log("onDrop layoutItem: ", layoutItem);
      };
    
      const onDragStartForDraggable = (e) => {
        currentDraggable = e.target.name + "_" + Date.now();
        e.dataTransfer.setData("text/plain", "");
      };
    
      return (
        <div className="App">
          <Container>
            <Row>
              <Col sm>
                <Accordion defaultActiveKey="0">
                  <Accordion.Item eventKey="0">
                    <Accordion.Header>Components</Accordion.Header>
                    <Accordion.Body>
                      <Stack gap={2}>
                        <Button variant="primary" name="input"
                          draggable={true}
                          unselectable="on"
                          onDragStart={onDragStartForDraggable}>
                          Input
                        </Button>
                        <Button variant="primary" name="password"
                          draggable={true}
                          unselectable="on"
                          onDragStart={onDragStartForDraggable}>
                          Password
                        </Button>
                        <Button variant="primary" name="select"
                          draggable={true}
                          unselectable="on"
                          onDragStart={onDragStartForDraggable}>
                          Select
                        </Button>
                      </Stack>
                    </Accordion.Body>
                  </Accordion.Item>
                </Accordion>
              </Col>
              <Col sm={9}>
                <Accordion defaultActiveKey="0">
                  <Accordion.Item eventKey="0">
                    <Accordion.Header>Form</Accordion.Header>
                    <Accordion.Body>
                    <Form>
                      <ResponsiveGridLayout
                        {...defaultProps}
                        className="layout"
                        rowHeight={40}
                        width={800}
                        onDrop={onDrop}
                        onLayoutChange={onLayoutChange}
                        measureBeforeMount={false}
                        useCSSTransforms={state.mounted}
                        compactType={state.compactType}
                        preventCollision={!state.compactType}
                        isDroppable={true}
                      >
                        {state.items.map(el => {
                          if (el.startsWith('input')) {
                            return <div key={el} className="d-grid gap-2 react-resizable-hide">
                              <Form.Group as={Row} className="mb-3" controlId={el}>
                                <Form.Label column sm="3">
                                  {el[0].toUpperCase()}{el.substring(1)}
                                </Form.Label>
                                <Col sm="9">
                                  <Form.Control type="text" placeholder="" />
                                </Col>
                              </Form.Group>
                            </div>
                          } if (el.startsWith('password')) {
                            return <div key={el} className="d-grid gap-2 react-resizable-hide">
                              <Form.Group as={Row} className="mb-3" controlId={el}>
                                <Form.Label column sm="3">
                                  {el[0].toUpperCase()}{el.substring(1)}
                                </Form.Label>
                                <Col sm="9">
                                  <Form.Control type="password" placeholder="" />
                                </Col>
                              </Form.Group>
                            </div>
                          } else if (el.startsWith('select')) {
                            return <div key={el} className="d-grid gap-2 react-resizable-hide">
                              <Form.Group as={Row} className="mb-3" controlId={el}>
                                <Form.Label column sm="3">
                                  {el[0].toUpperCase()}{el.substring(1)}
                                </Form.Label>
                                <Col sm="9">
                                <Form.Select aria-label="Default select example">
                                  <option>Select ...</option>
                                  <option value="1">value 1</option>
                                  <option value="2">value 2</option>
                                  <option value="3">value 3</option>
                                </Form.Select>
                                </Col>
                              </Form.Group>
                            </div>
                          } else {
                            return <div key={el} className="d-grid gap-2 react-resizable-hide">
                              <Button variant="primary" className="drag-handler">
                                {el[0].toUpperCase()}{el.substring(1)}
                              </Button>
                            </div>
                          }
                        })}
                      </ResponsiveGridLayout>
                    </Form>
                    </Accordion.Body>
                  </Accordion.Item>
                </Accordion>
              </Col>
            </Row>
          </Container>
        </div>
      );
    }
    
    export default App;
    

    相关文章

      网友评论

          本文标题:React拖拽组件react-grid-layout实现表单设计

          本文链接:https://www.haomeiwen.com/subject/uwhwtjtx.html