react-dnd实现表单动态定点拖拽排序
文章目录
效果如下:
react-dnd使用方法介绍
DndProvider注入:将需要拖拽源和拖放目标组件置入其内
backend:必需。React DnD后端。除非您正在编写自定义的,否则您可能希望使用React DnD附带的HTML5后端。
import { HTML5Backend } from 'react-dnd-html5-backend'
import { DndProvider } from 'react-dnd'
export default function MyReactApp() {
return (
<DndProvider backend={HTML5Backend}>
/* your drag-and-drop application */
</DndProvider>
)
}
useDrag:用于将当前组件用作拖动源的钩子
import { useDrag } from 'react-dnd'
function DraggableComponent(props) {
const [collected, drag, dragPreview] = useDrag(() => ({
type,
item: { id }
}))
return collected.isDragging ? (
<div ref={dragPreview} />
) : (
<div ref={drag} {...collected}>
...
</div>
)
}
参数解释:
- collected:包含从收集函数收集的属性的对象
- drag:拖曳源的连接器功能。这必须附加到DOM的可拖动部分。
- dragPreview:用于拖动预览的连接器功能。这可以附加到DOM的预览部分。
useDrop:用于将当前组件成为拖放目标的钩子
import { useDrop } from 'react-dnd'
function myDropTarget(props) {
const [collectedProps, drop] = useDrop(() => ({
accept
}))
return <div ref={drop}>Drop Target</div>
}
参数解释:
- collectedProps:包含从收集函数收集的属性的对象
- drop:拖放目标的连接器功能。这必须附加到DOM的拖放目标部分。
实战:
为了更加好的拓展性,将其提取为公共组件
实现一个拖动时,透明度变化
效果
这部分代码:保证了定点推拽功能
ref={drag}保证了只有在点击span元素,才能拖动
SortCard.tsx完整代码:
import type { Identifier } from 'dnd-core';
import type { FC } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { DragOutlined } from '@ant-design/icons';
export interface CardProps {
ItemTypes: string;
id: any;
children: React.ReactNode;
index: number;
moveCard: (dragIndex: number, hoverIndex: number) => void;
style?: React.CSSProperties;
}
interface DragItem {//拖动源的属性
index: number;
id: string;
type: string;
}
export const SortCard: FC<CardProps> = ({
ItemTypes,//区别不同场景的拖拽组件
id,
children,
index,
moveCard,
style = {},
}) => {
const [{ handlerId }, drop] = useDrop<
DragItem,
void,
{ handlerId: Identifier | null }
>({
accept: ItemTypes,
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
};
},
drop: (item: any) => {
if (item.index === index) return;
moveCard(item.index, index);
item.index = index;
},
});
const [{ isDragging }, drag, preview] = useDrag({
type: ItemTypes,
item: () => {
return { id, index };
},
collect: (monitor: any) => ({
isDragging: monitor.isDragging(),
}),
});
return (
<div ref={drop} data-handler-id={handlerId}>
<div
ref={preview}
className="flex"
style={{ opacity: isDragging ? 0.5 : 1, ...style }}
>
<span
ref={drag}
style={{ marginTop: 30, width: 30 }}
className="cursor-pointer"//用户鼠标移到定点时,光标变成手
>
<DragOutlined />//antd图标
</span>
{children}
</div>
</div>
);
};
公共组件封装好调用它:
定义一下moveCard方法:
具体:先对表单数据拷贝一份,拖拽后,更新表单数据顺序,重新赋值给表单
对需要拖拽源和拖拽目标做处理,看一个例子
<DndProvider backend={HTML5Backend}>//DndProvider注入
<Form.List name="bannerList" initialValue={[{}]}>
{(fields, { remove }) => (
<>
{fields.map(({ key, name, ...restField }, index: number) => (
<SortCard//用到刚刚定义的公共组件
ItemTypes="bannerCode"
index={index}
id={key}
moveCard={(dragIndex, hoverIndex) =>
moveCard(dragIndex, hoverIndex, 'bannerList')//调用moveCard
}
style={{
border: '1px solid #ccc',
padding: 10,
marginBottom: 10,
}}
>
<div className="flex flex-start">
<Form.Item
{...restField}
name={[name, 'imagesourcesrc1']}
label="图片资源链接:"
>
<Input />
</Form.Item>
<MinusCircleOutlined
style={{ padding: '38px 0 0 10px' }}
onClick={() => remove(name)}
/>
</div>
</SortCard>
))}
</>
)}
</Form.List>
</DndProvider>
完整代码:
import { SortCard } from '@/components/SortList/SortCard';
import { MinusCircleOutlined } from '@ant-design/icons';
import { Button, Form, Input, Typography } from 'antd';
import React, { FC } from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { DndProvider } from 'react-dnd';
import './walletModalForm.scoped.scss';
interface Props {
ctrlRef: any;
formValues: any;
channelConfigList: any[];
method: '1' | '2';
}
const { Title } = Typography;
const WForm: FC<Props> = () => {
const [form] = Form.useForm();
function onFinish() {}
function moveCard(dragIndex: number, hoverIndex: number, name: string) {
const list = form.getFieldValue(name);
const listClone = [...list];
listClone.splice(hoverIndex, 0, listClone.splice(dragIndex, 1)[0]);
form.setFieldsValue({
[name]: listClone,
});
}
return (
<DndProvider backend={HTML5Backend}>
<div className="walletModalForm">
<Form
name="dynamic_form_nest_item"
onFinish={onFinish}
autoComplete="off"
form={form}
layout="vertical"
>
<Title level={4}>天天向上</Title>
<Form.List name="FormList" initialValue={[{}]}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index: number) => (
<SortCard
ItemTypes="channelCode"
index={index}
id={key}
moveCard={(dragIndex, hoverIndex) =>
moveCard(dragIndex, hoverIndex, 'FormList')
}
style={{
border: '1px solid #ccc',
padding: 10,
marginBottom: 10,
}}
>
<div className="flex flex-1 justify-around">
<Form.Item
{...restField}
name={[name, 'showname']}
label="showname:"
style={{ display: 'inline-block' }}
>
<Input placeholder="showname" />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'imagesourcesrc']}
label="imagesourcesrc:"
>
<Input placeholder="imagesourcesrc" />
</Form.Item>
<MinusCircleOutlined
style={{ padding: '38px 0 0 10px' }}
onClick={() => remove(name)}
/>
</div>
</SortCard>
))}
<Form.Item name="add">
<div
onClick={() => add({})}
className="inline-block"
style={{
color: '#3F90F7',
padding: '10px',
}}
>
添加一条
</div>
</Form.Item>
</>
)}
</Form.List>
<Title level={4}>好好学习</Title>
<Form.List name="bannerList" initialValue={[{}]}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index: number) => (
<SortCard
ItemTypes="bannerCode"
index={index}
id={key}
moveCard={(dragIndex, hoverIndex) =>
moveCard(dragIndex, hoverIndex, 'bannerList')
}
style={{
border: '1px solid #ccc',
padding: 10,
marginBottom: 10,
}}
>
<div className="flex flex-start">
<Form.Item
{...restField}
name={[name, 'imagesourcesrc1']}
label="第一个数据:"
>
<Input />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'inconsourcesrc1']}
label="第二个数据:"
style={{ padding: '0 0 0 10px' }}
>
<Input placeholder="inconsourcesrc1" />
</Form.Item>
<MinusCircleOutlined
style={{ padding: '38px 0 0 10px' }}
onClick={() => remove(name)}
/>
</div>
</SortCard>
))}
<Form.Item name="add2">
<div
onClick={() => add({})}
className="inline-block"
style={{
color: '#3F90F7',
padding: '10px',
}}
>
添加一条
</div>
</Form.Item>
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</div>
</DndProvider>
);
};
export default WForm;
码字不易,如果对你有帮助,帮小编点个赞,关注一波。
如有错误,请大佬们指出。尽力改正