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