0
点赞
收藏
分享

微信扫一扫

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

小磊z 04-04 12:30 阅读 1

最近在看在线表单设计,找了一些现成的产品和库,今天就看看怎样使用 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;

运行效果如下图:

运行效果

举报

相关推荐

0 条评论