├── .eslintignore
├── .prettierignore
├── src
├── react-app-env.d.ts
├── components
│ ├── Chess
│ │ ├── type.ts
│ │ ├── index.scss
│ │ ├── utils.ts
│ │ ├── square.tsx
│ │ ├── index.tsx
│ │ ├── knight.tsx
│ │ ├── board.tsx
│ │ └── boardSquare.tsx
│ ├── listSort
│ │ ├── type.ts
│ │ ├── dragSquare.tsx
│ │ ├── index.scss
│ │ ├── index.tsx
│ │ ├── box.tsx
│ │ ├── dropSquare.tsx
│ │ └── card.tsx
│ ├── arbitrarilyDrag
│ │ ├── type.ts
│ │ ├── Classification.tsx
│ │ ├── index.scss
│ │ ├── word.tsx
│ │ └── index.tsx
│ ├── App.scss
│ ├── cardAssemble
│ │ ├── dragSquare.tsx
│ │ ├── dropSquare.tsx
│ │ ├── dropPlace.tsx
│ │ ├── dragCard.tsx
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── base
│ │ └── nav
│ │ │ ├── index.scss
│ │ │ └── index.tsx
│ ├── multiDrag
│ │ ├── Drop.tsx
│ │ ├── index.tsx
│ │ ├── index.scss
│ │ └── Drag.tsx
│ ├── App.tsx
│ ├── dragPreviewImg
│ │ ├── index.tsx
│ │ ├── index.scss
│ │ └── dragPreviewImg.tsx
│ ├── dragPreviewDom
│ │ ├── index.scss
│ │ ├── index.tsx
│ │ ├── dragPreviewDom.tsx
│ │ └── customDragerLayer.tsx
│ ├── cardSort
│ │ ├── index.scss
│ │ ├── card.tsx
│ │ └── index.tsx
│ └── routes.ts
├── assets
│ ├── 任意拖拽.gif
│ ├── 卡片拼图.gif
│ ├── 批量拖拽.gif
│ ├── apple.png
│ ├── 拖拽卡片排序.gif
│ └── 预置卡片排序.gif
├── setupTests.ts
├── reportWebVitals.ts
└── index.tsx
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── .prettierrc.js
├── .gitignore
├── tsconfig.json
├── README.md
├── package.json
└── .eslintrc
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build/
3 | scripts/
4 | config/
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build/
3 | scripts/
4 | config/
5 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/components/Chess/type.ts:
--------------------------------------------------------------------------------
1 | export const ItemTypes = {
2 | KNIGHT: 'knight'
3 | }
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/assets/任意拖拽.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/任意拖拽.gif
--------------------------------------------------------------------------------
/src/assets/卡片拼图.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/卡片拼图.gif
--------------------------------------------------------------------------------
/src/assets/批量拖拽.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/批量拖拽.gif
--------------------------------------------------------------------------------
/src/assets/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/apple.png
--------------------------------------------------------------------------------
/src/assets/拖拽卡片排序.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/拖拽卡片排序.gif
--------------------------------------------------------------------------------
/src/assets/预置卡片排序.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/预置卡片排序.gif
--------------------------------------------------------------------------------
/src/components/listSort/type.ts:
--------------------------------------------------------------------------------
1 | enum ItemTypes {
2 | Card = 'card'
3 | }
4 |
5 | export default ItemTypes;
--------------------------------------------------------------------------------
/src/components/arbitrarilyDrag/type.ts:
--------------------------------------------------------------------------------
1 | export const WORD_TYPE = {
2 | adj: 'adj',
3 | verb: 'verb',
4 | };
5 |
--------------------------------------------------------------------------------
/src/components/App.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .app {
7 | width: 100%;
8 | height: 100%;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Chess/index.scss:
--------------------------------------------------------------------------------
1 | .chess-container {
2 | width: 400px;
3 | height: 400px;
4 | margin: auto;
5 | border: 1px solid black;
6 | }
7 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | //此处的规则供参考,其中多半其实都是默认值,可以根据个人习惯改写
2 | module.exports = {
3 | printWidth: 80, //单行长度
4 | tabWidth: 2, //缩进长度
5 | useTabs: false, //使用空格代替tab缩进
6 | semi: true, //句末使用分号
7 | singleQuote: true, //使用单引号
8 | };
9 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/components/Chess/utils.ts:
--------------------------------------------------------------------------------
1 | export function canMoveKnight(toX: number, toY: number, knightPosition: [number, number]): boolean {
2 | const [x, y] = knightPosition;
3 |
4 | const dx = toX - x;
5 | const dy = toY - y;
6 |
7 | return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2);
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/dragSquare.tsx:
--------------------------------------------------------------------------------
1 | import DragCard from './dragCard';
2 |
3 | export default function DragSquare({ dragCardList, updateDragAndDrop }: any) {
4 | return (
5 |
6 | {dragCardList.map((each: any, index: any) => (
7 |
8 | ))}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/dropSquare.tsx:
--------------------------------------------------------------------------------
1 | import DropPlace from './dropPlace';
2 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) {
3 |
4 | return (
5 |
6 | {dropCardList.map((each: any, index: number) => (
7 |
8 | ))}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Chess/square.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function Square({ black, children }: any) {
4 | const fill = black ? 'black' : 'white';
5 | const stroke = black ? 'white' : 'black';
6 |
7 | return (
8 |
16 | {children}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/components/base/nav/index.scss:
--------------------------------------------------------------------------------
1 | .nav-container {
2 | height: 80px;
3 | width: 100%;
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | margin: 30px 0;
8 |
9 | .link_btn {
10 | padding: 30px;
11 | border: 1px dashed black;
12 | font-size: 18px;
13 | color: #4f6f95;
14 |
15 | &--active {
16 | background-color: #0d1320;
17 | color: white;
18 | opacity: 0.3;
19 | }
20 | }
21 |
22 | .link_btn + .link_btn {
23 | margin-left: 25px;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/listSort/dragSquare.tsx:
--------------------------------------------------------------------------------
1 | import Box from './box';
2 |
3 | export default function DragSquare({ dragCardList, dropCardList, updateDragAndDrop }: any) {
4 | return (
5 |
6 | {dragCardList.map((each: any, index: any) => (
7 |
14 | ))}
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './components/App';
4 | import reportWebVitals from './reportWebVitals';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
7 | root.render( );
8 | // If you want to start measuring performance in your app, pass a function
9 | // to log results (for example: reportWebVitals(console.log))
10 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
11 | reportWebVitals();
12 |
--------------------------------------------------------------------------------
/src/components/multiDrag/Drop.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useDrop } from 'react-dnd';
3 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) {
4 | const [{ canDrop }, drop] = useDrop({
5 | accept: 'DragDropBox',
6 |
7 | canDrop: (_item, monitor) => {
8 | return true;
9 | },
10 | collect: (monitor) => ({
11 | canDrop: !!monitor.canDrop(),
12 | }),
13 | });
14 |
15 |
16 | return (
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.scss';
3 | import { HashRouter as Router, Route, Routes } from 'react-router-dom';
4 | import { routes } from './routes';
5 | import Nav from './base/nav';
6 |
7 | function App({ ...props }) {
8 | return (
9 |
10 |
11 |
12 |
13 | {routes.map((each) => {
14 | const { Component, url } = each;
15 | return } />;
16 | })}
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/multiDrag/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import DragPreviewDom from './Drag';
5 | import './index.scss';
6 |
7 | class MultiDrag extends React.Component {
8 | constructor(props: any) {
9 | super(props);
10 | this.state = {};
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
17 |
批量拖拽
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default MultiDrag;
26 |
--------------------------------------------------------------------------------
/src/components/dragPreviewImg/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import DragPreviewImg from './dragPreviewImg';
5 | import './index.scss';
6 |
7 | class DragPreview extends React.Component {
8 | constructor(props: any) {
9 | super(props);
10 | this.state = {};
11 | }
12 |
13 | render() {
14 | return (
15 |
16 |
17 |
拖拽图片预览
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default DragPreview;
26 |
--------------------------------------------------------------------------------
/src/components/dragPreviewImg/index.scss:
--------------------------------------------------------------------------------
1 | .drag-preview {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop {
10 | width: 320px;
11 | height: 320px;
12 | border: 1px solid black;
13 | margin-top: 20px;
14 | }
15 |
16 | .drag-group {
17 | margin-top: 20px;
18 | display: flex;
19 | }
20 |
21 | .card_drag_box {
22 | position: relative;
23 | width: auto;
24 | height: auto;
25 | }
26 |
27 | .card_drag {
28 | padding: 24px;
29 | font-size: 18px;
30 | border: 1px solid black;
31 | position: relative;
32 | margin-top: 20px;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/dragPreviewDom/index.scss:
--------------------------------------------------------------------------------
1 | .drag-preview-dom {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop {
10 | width: 400px;
11 | height: 400px;
12 | border: 1px solid black;
13 | margin-top: 20px;
14 | }
15 |
16 | .drag-group {
17 | margin-top: 20px;
18 | display: flex;
19 | }
20 |
21 | .card_drag_box {
22 | position: relative;
23 | width: auto;
24 | height: auto;
25 | }
26 |
27 | .card_drag {
28 | padding: 24px;
29 | font-size: 18px;
30 | border: 1px solid black;
31 | position: relative;
32 | margin-top:40px;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 关于本项目
2 |
3 | 本项目使用 react-dnd 编写了一些 demo,涵盖了多种场景,尽量应用到其全面的 API
4 |
5 | ### 启动本项目
6 |
7 | clone 下来之后
8 |
9 | - npm install
10 | - npm start/npm run build
11 |
12 | ### 用例
13 |
14 | 在 components 目录下,每个文件夹代表一个用例,启动项目之后,通过路由进行访问
15 |
16 | - multiDrag: 批量拖拽,可以选择多个元素进行拖拽
17 | 
18 |
19 | - word: 任意拖拽,记录坐标进行拖拽
20 |
21 | 
22 |
23 | - cardSort: 预置卡片排序
24 |
25 | 
26 |
27 | - cardAssemble: 固定碎片的卡片拼图
28 |
29 | 
30 |
31 | - listSort: 有顺移的列表排序
32 |
33 | 
34 |
35 | - dragPreviewImg: 拖拽图片预览
36 | - dragPreviewDom: 拖拽自定义预览
37 | - chess: 国际象棋,官方用例
--------------------------------------------------------------------------------
/src/components/Chess/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Board from './board';
3 | import './index.scss';
4 |
5 | class ChessDemo extends React.Component {
6 | constructor(props: any) {
7 | super(props);
8 | this.state = {
9 | knightPosition: [2, 3],
10 | };
11 | this.moveKnight = this.moveKnight.bind(this);
12 | }
13 |
14 | moveKnight(toX: number, toY: number) {
15 | this.setState({ knightPosition: [toX, toY] });
16 | }
17 |
18 | render() {
19 | const { knightPosition } = this.state;
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 | }
27 |
28 | export default ChessDemo;
29 |
--------------------------------------------------------------------------------
/src/components/dragPreviewDom/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import DragPreviewDom from './dragPreviewDom';
5 | import { CustomDragLayer } from './customDragerLayer';
6 | import './index.scss';
7 |
8 | class DragPreview extends React.Component {
9 | constructor(props: any) {
10 | super(props);
11 | this.state = {};
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
拖拽Dom预览
19 |
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
27 | export default DragPreview;
28 |
--------------------------------------------------------------------------------
/src/components/dragPreviewImg/dragPreviewImg.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, FC, useRef, useState } from 'react';
2 | import { useDrag, DragPreviewImage } from 'react-dnd';
3 | import apple from '../../assets/apple.png';
4 |
5 | const DragPreviewImg = () => {
6 | const [{ isDragging }, drag, preview] = useDrag({
7 | type: 'DragDropBox',
8 | collect: (monitor) => ({
9 | isDragging: monitor.isDragging(),
10 | }),
11 | });
12 |
13 | return (
14 | <>
15 |
16 |
23 | drag item prview an apple
24 |
25 | >
26 | );
27 | };
28 | export default DragPreviewImg;
29 |
--------------------------------------------------------------------------------
/src/components/base/nav/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useLocation, useNavigate } from 'react-router-dom';
3 | import { routes } from '../../routes';
4 | import './index.scss';
5 |
6 | const Nav = () => {
7 | let navigate = useNavigate();
8 | let location = useLocation();
9 | const handleChangeRoute = (url: string) => {
10 | navigate(url);
11 | };
12 |
13 | return (
14 |
15 | {routes.map((each, index) => (
16 |
handleChangeRoute(each.url)}
19 | key={'link' + index}
20 | >
21 | {each.title}
22 |
23 | ))}
24 |
25 | );
26 | };
27 |
28 | export default Nav;
29 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/dropPlace.tsx:
--------------------------------------------------------------------------------
1 | import { useDrop } from 'react-dnd';
2 | import DragCard from './dragCard';
3 |
4 | export default function DropPlace({ innerDrag, updateCardList, index, id, updateDragAndDrop }: any) {
5 | const [{ isOver }, drop] = useDrop({
6 | accept: 'DragDropBox',
7 | drop: (item: any, monitor) => {
8 | const dragIndex = item.id;
9 | const dropIndex = id;
10 | updateDragAndDrop(dragIndex, dropIndex);
11 | },
12 | collect: (monitor) => ({
13 | isOver: !!monitor.isOver(),
14 | }),
15 | });
16 |
17 | return (
18 |
19 | {innerDrag && }
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/dragCard.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, FC, useRef, useState } from 'react';
2 | import { useDrag, useDrop } from 'react-dnd';
3 |
4 | const DragCard = ({ text, id, index, updateDragAndDrop, isHovering }: any) => {
5 | const [{ isDragging }, drag] = useDrag({
6 | type: 'DragDropBox',
7 | item: { id, index, updateDragAndDrop },
8 | end: (item, monitor) => {
9 | if (!monitor.didDrop()) {
10 | updateDragAndDrop(id);
11 | }
12 | },
13 | collect: (monitor) => ({
14 | isDragging: monitor.isDragging(),
15 | }),
16 | });
17 |
18 | return (
19 |
26 | {text}
27 |
28 | );
29 | };
30 | export default DragCard;
31 |
--------------------------------------------------------------------------------
/src/components/Chess/knight.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC } from 'react';
2 | import { useDrag } from 'react-dnd';
3 | import { ItemTypes } from './type';
4 |
5 | const Knight: FC = () => {
6 | const [{ isDragging }, drag]: any = useDrag(() => ({
7 | type: ItemTypes.KNIGHT,
8 | collect: (monitor) => ({
9 | isDragging: !!monitor.isDragging(),
10 | }),
11 | }));
12 | return (
13 |
29 | ♘
30 |
31 | );
32 | };
33 | export default Knight;
34 |
--------------------------------------------------------------------------------
/src/components/cardSort/index.scss:
--------------------------------------------------------------------------------
1 | .card-container {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop_group {
10 | margin-top: 30px;
11 | height: 240px;
12 | width: 600px;
13 | padding: 20px;
14 | border: 1px solid black;
15 | }
16 |
17 | .card_drag_group {
18 | height: 240px;
19 | padding: 20px;
20 | display: flex;
21 | margin-top: 50px;
22 | border:1px solid black;
23 |
24 | .card_drag {
25 | width: 120px;
26 | height: 240px;
27 | display: flex;
28 | align-items: center;
29 | justify-content: center;
30 | font-size: 24px;
31 | font-weight: bold;
32 | border: 1px solid black;
33 | background-color:orange;
34 | }
35 |
36 | .card_drag + .card_drag {
37 | margin-left: 20px;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/dragPreviewDom/dragPreviewDom.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useDrag } from 'react-dnd';
3 | import { getEmptyImage } from 'react-dnd-html5-backend';
4 |
5 | const DragPreviewDom = () => {
6 | const [{ isDragging }, drag, preview] = useDrag({
7 | type: 'DragDropBox',
8 | collect: (monitor) => {
9 | const isDragging = monitor.isDragging();
10 | return {
11 | isDragging,
12 | };
13 | },
14 | });
15 |
16 | useEffect(() => {
17 | preview(getEmptyImage(), { captureDraggingState: true }); // 隐藏拖拽dom
18 | }, []);
19 |
20 | return (
21 |
22 |
31 | 拖拽Dom预览
32 |
33 |
34 | );
35 | };
36 | export default DragPreviewDom;
37 |
--------------------------------------------------------------------------------
/src/components/arbitrarilyDrag/Classification.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useDrop } from 'react-dnd';
3 |
4 | function Classification({ type, title }: any) {
5 | const [{ isOver, canDrop }, drop] = useDrop(
6 | () => ({
7 | accept: type,
8 | drop(_item: any, monitor: any) {
9 | const delta = monitor.getDifferenceFromInitialOffset();
10 | const left = Math.round(delta.x);
11 | const top = Math.round(delta.y);
12 | return { top, left };
13 | },
14 | canDrop: (_item, monitor) => {
15 | const item = monitor.getItem() as any;
16 | return item.type === type;
17 | },
18 | collect: (monitor) => ({
19 | isOver: !!monitor.isOver(),
20 | canDrop: !!monitor.canDrop(),
21 | }),
22 | }),
23 | [],
24 | );
25 |
26 | return (
27 |
35 | );
36 | }
37 |
38 | export default Classification;
39 |
--------------------------------------------------------------------------------
/src/components/listSort/index.scss:
--------------------------------------------------------------------------------
1 | .scard-move-container {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop_group {
10 | margin-top: 30px;
11 | height: 240px;
12 | width: 150px;
13 | padding: 20px;
14 | border: 1px solid black;
15 | display: flex;
16 | flex-direction: column;
17 |
18 | .card_drag {
19 | width: 150px;
20 | height: 40px;
21 | display: flex;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: 18px;
25 | }
26 |
27 | .card_drop + .card_drop {
28 | margin-top: 20px;
29 | }
30 | }
31 |
32 | .card_drag_group {
33 | height: 240px;
34 | padding: 20px;
35 | display: flex;
36 | margin-top: 50px;
37 |
38 | .card_drag {
39 | width: 150px;
40 | height: 40px;
41 | display: flex;
42 | align-items: center;
43 | justify-content: center;
44 | font-size: 18px;
45 | // font-weight: bold;
46 | border: 1px solid black;
47 | }
48 |
49 | .card_drag + .card_drag {
50 | margin-left: 20px;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/routes.ts:
--------------------------------------------------------------------------------
1 | import Chess from './Chess';
2 | import ArbitrarilyDrag from './arbitrarilyDrag';
3 | import CardSort from './cardSort';
4 | import CardAssemble from './cardAssemble';
5 | import ListSort from './listSort';
6 | import DragPreviewImg from './dragPreviewImg';
7 | import DragPreviewDom from './dragPreviewDom';
8 | import MultiDrag from './multiDrag';
9 | export const routes = [
10 | {
11 | url: '/chess',
12 | title: '官方用例',
13 | Component: Chess,
14 | },
15 | {
16 | url: '/word',
17 | title: '任意拖拽',
18 | Component: ArbitrarilyDrag,
19 | },
20 | {
21 | url: '/cardSort',
22 | title: '预置卡片排序',
23 | Component: CardSort,
24 | },
25 | {
26 | url: '/cardAssemble',
27 | title: '卡片拼图',
28 | Component: CardAssemble,
29 | },
30 | {
31 | url: '/listSort',
32 | title: '列表排序',
33 | Component: ListSort,
34 | },
35 | {
36 | url: '/dragPreviewImg',
37 | title: '图片预览',
38 | Component: DragPreviewImg,
39 | },
40 | {
41 | url: '/dragPreviewDom',
42 | title: '自定义预览',
43 | Component: DragPreviewDom,
44 | },
45 | {
46 | url: '/multiDrg',
47 | title: '批量拖拽',
48 | Component: MultiDrag,
49 | },
50 | ];
51 |
--------------------------------------------------------------------------------
/src/components/arbitrarilyDrag/index.scss:
--------------------------------------------------------------------------------
1 | .word-container {
2 | width: 100%;
3 | height: 500px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .word_drop_group {
10 | width: 700px;
11 | height: 340px;
12 | display: flex;
13 | justify-content: space-between;
14 | margin-top: 30px;
15 |
16 | .word_drop_container {
17 | width: 320px;
18 | height: 350px;
19 | display: flex;
20 | flex-direction: column;
21 |
22 | .word_drop_text {
23 | height: 30px;
24 | text-align: center;
25 | line-height: 30px;
26 | }
27 |
28 | .word_drop {
29 | width: 320px;
30 | height: 320px;
31 | border: 1px solid black;
32 | }
33 | }
34 | }
35 |
36 | .word_drag_group {
37 | margin-top: 100px;
38 | width: 700px;
39 | height: 300px;
40 | display: flex;
41 | flex-wrap: wrap;
42 | padding: 40px;
43 | border: 1px solid black;
44 |
45 | .word_drag {
46 | padding: 24px;
47 | font-size: 18px;
48 | border: 1px solid black;
49 | position: relative;
50 | }
51 |
52 | .word_drag+.word_drag{
53 | margin-left: 20px;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/arbitrarilyDrag/word.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, FC, useRef, useState } from 'react';
2 | import { useDrag } from 'react-dnd';
3 |
4 | const Word: FC = ({ type, text, id, ...props }: any) => {
5 | const [offsetX, setOffsetX] = useState(0);
6 | const [offsetY, setOffsetY] = useState(0);
7 | const [{ isDragging }, drag]: any = useDrag(() => ({
8 | type,
9 | item: { id, type },
10 | end(item, monitor) {
11 | let top = 0,
12 | left = 0;
13 | if (monitor.didDrop()) {
14 | const dropRes = monitor.getDropResult() as any; //获取拖拽对象所处容器的数据
15 | if (dropRes) {
16 | top = dropRes.top;
17 | left = dropRes.left;
18 | }
19 | setOffsetX((offsetX) => offsetX + left);
20 | setOffsetY((offsetY) => offsetY + top);
21 | } else {
22 | setOffsetX(0);
23 | setOffsetY(0);
24 | }
25 | },
26 | collect: (monitor) => ({
27 | isDragging: !!monitor.isDragging(),
28 | }),
29 | }));
30 |
31 | return (
32 |
42 | {text}
43 |
44 | );
45 | };
46 | export default Word;
47 |
--------------------------------------------------------------------------------
/src/components/multiDrag/index.scss:
--------------------------------------------------------------------------------
1 | .multi-drag-container {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop {
10 | width: 400px;
11 | height: 400px;
12 | border: 1px solid black;
13 | margin-top: 20px;
14 | }
15 |
16 | .drag-group {
17 | margin-top: 20px;
18 | display: flex;
19 | }
20 |
21 | .card_drag_box {
22 | position: relative;
23 | width: auto;
24 | margin-top: 30px;
25 | height: 180px;
26 | }
27 |
28 | .card_drag_list {
29 | display: flex;
30 | width: 100%;
31 | height: 100%;
32 | flex-direction: column;
33 | position: relative;
34 | }
35 |
36 |
37 | .card_drag_preview {
38 | background: none;
39 | position: absolute;
40 | width: 100%;
41 | height: 100%;
42 | top: 0;
43 | right: 0;
44 |
45 | .card_drag_preview_inner {
46 | position: relative;
47 | width: 100%;
48 | height: 100%;
49 | }
50 | }
51 |
52 | .card_drag {
53 | height: 48px;
54 | padding: 0 24px;
55 | width: 80px;
56 | font-size: 18px;
57 | border: 1px solid black;
58 | position: relative;
59 | margin-top: 10px;
60 | display: flex;
61 | align-items: center;
62 | justify-content: space-between;
63 | z-index: 11;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/cardSort/card.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties, FC, useRef, useState } from 'react';
2 | import { useDrag, useDrop } from 'react-dnd';
3 |
4 | const Card = ({ text, id, index, changePosition }: any) => {
5 | const ref = useRef(null);
6 | // 因为没有定义收集函数,所以返回值数组第一项不要
7 | const [, drop] = useDrop({
8 | accept: 'DragDropBox',
9 | hover: (item: any, monitor) => {
10 | if (!ref.current) return;
11 | let dragIndex = item.index;
12 | let hoverIndex = index;
13 | if (dragIndex === hoverIndex) return; // 如果回到自己的坑,那就什么都不做
14 | changePosition(dragIndex, hoverIndex); // 调用传入的方法完成交换
15 | item.index = hoverIndex; // 将当前当前移动到Box的index赋值给当前拖动的box,不然会出现两个盒子疯狂抖动!
16 | },
17 | drop: (item, monitor) => {},
18 | });
19 |
20 | const [{ isDragging }, drag] = useDrag({
21 | type: 'DragDropBox',
22 | item: { id, index },
23 | end: () => {},
24 | isDragging: (monitor) => {
25 | return index === monitor.getItem().index;
26 | },
27 | collect: (monitor) => ({
28 | isDragging: monitor.isDragging(),
29 | }),
30 | });
31 |
32 | return (
33 |
40 | {text}
41 |
42 | );
43 | };
44 | export default Card;
45 |
--------------------------------------------------------------------------------
/src/components/Chess/board.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Knight from './knight';
3 | import { DndProvider } from 'react-dnd';
4 | import { HTML5Backend } from 'react-dnd-html5-backend';
5 | import BoardSquare from './boardSquare';
6 |
7 | function renderSquare(i: any, knightPosition: any, moveKnight: any) {
8 | const x = i % 8;
9 | const y = Math.floor(i / 8);
10 | return (
11 |
12 |
13 | {renderPiece(x, y, knightPosition)}
14 |
15 |
16 | );
17 | }
18 |
19 | function renderPiece(x: any, y: any, knightPosition = []) {
20 | const [knightX, knightY] = knightPosition;
21 | if (x === knightX && y === knightY) {
22 | return ;
23 | }
24 | }
25 |
26 | export default function Board({ knightPosition, moveKnight }: any) {
27 | const squares = [];
28 |
29 | for (let i = 0; i < 64; i++) {
30 | squares.push(renderSquare(i, knightPosition, moveKnight));
31 | }
32 |
33 | return (
34 |
35 |
44 | {squares}
45 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/index.scss:
--------------------------------------------------------------------------------
1 | .scard-container {
2 | width: 100%;
3 | height: 700px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | padding-top: 50px;
8 |
9 | .card_drop_group {
10 | margin-top: 30px;
11 | height: 240px;
12 | padding: 20px;
13 | border: 1px solid black;
14 | display: flex;
15 |
16 | .card_drop {
17 | width: 120px;
18 | height: 240px;
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | font-size: 24px;
23 | font-weight: bold;
24 | }
25 |
26 | .card_drag {
27 | width: 120px;
28 | height: 240px;
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | font-size: 24px;
33 | font-weight: bold;
34 | border: 1px solid black;
35 | background-color: orange;
36 | }
37 |
38 | .card_drop + .card_drop {
39 | margin-left: 20px;
40 | }
41 | }
42 |
43 | .card_drag_group {
44 | height: 240px;
45 | padding: 20px;
46 | display: flex;
47 | margin-top: 50px;
48 |
49 | .card_drag {
50 | width: 120px;
51 | height: 240px;
52 | display: flex;
53 | align-items: center;
54 | justify-content: center;
55 | font-size: 24px;
56 | font-weight: bold;
57 | border: 1px solid black;
58 | background-color: orange;
59 | }
60 |
61 | .card_drag + .card_drag {
62 | margin-left: 20px;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.3.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "@types/jest": "^27.5.2",
10 | "@types/node": "^16.11.47",
11 | "@types/react": "^18.0.15",
12 | "@types/react-dom": "^18.0.6",
13 | "immutability-helper": "^3.1.1",
14 | "iscroll": "^5.2.0",
15 | "node-sass": "^7.0.3",
16 | "react": "^18.2.0",
17 | "react-dnd": "^16.0.1",
18 | "react-dnd-html5-backend": "^16.0.1",
19 | "react-dom": "^18.2.0",
20 | "react-router": "^6.4.2",
21 | "react-router-dom": "^6.4.2",
22 | "react-scripts": "5.0.1",
23 | "typescript": "^4.7.4",
24 | "web-vitals": "^2.1.4"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | },
50 | "devDependencies": {
51 | "eslint-config-prettier": "^8.5.0",
52 | "eslint-plugin-prettier": "^4.2.1",
53 | "prettier": "^2.7.1"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/listSort/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import DragSquare from './dragSquare';
5 | import DropSquare from './dropSquare';
6 | import './index.scss';
7 |
8 | const CARD_INIT_ARR = [
9 | { id: 1, text: 'Apple', bg: 'red' },
10 | { id: 2, text: 'Banana', bg: 'yellow' },
11 | { id: 3, text: 'Orange', bg: 'orange' },
12 | { id: 4, text: 'Grape', bg: 'purple' },
13 | { id: 5, text: 'Watermelon', bg: 'green' },
14 | { id: 6, text: 'Peach', bg: 'pink' },
15 | ];
16 |
17 | class CardSort extends React.Component {
18 | constructor(props: any) {
19 | super(props);
20 | this.state = {
21 | dragCardList: CARD_INIT_ARR,
22 | dropCardList: [],
23 | };
24 | this.updateDragAndDrop = this.updateDragAndDrop.bind(this);
25 | }
26 |
27 | updateDragAndDrop(newCardList: any) {
28 | this.setState({ dropCardList: newCardList });
29 | }
30 |
31 | render() {
32 | const { dragCardList, dropCardList } = this.state;
33 | return (
34 |
35 |
36 |
列表排序(数量无限,有顺移)
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | export default CardSort;
46 |
--------------------------------------------------------------------------------
/src/components/Chess/boardSquare.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Square from './square';
3 | import { ItemTypes } from './type';
4 | import { useDrop } from 'react-dnd';
5 | import { canMoveKnight } from './utils';
6 |
7 | function Overlay({ color }: any) {
8 | return (
9 |
21 | );
22 | }
23 |
24 | function BoardSquare({ x, y, knightPosition, children, moveKnight }: any) {
25 | const black = (x + y) % 2 === 1;
26 | const [{ isOver, canDrop }, drop] = useDrop(
27 | () => ({
28 | accept: ItemTypes.KNIGHT,
29 | drop: (item, monitor) => {
30 | moveKnight(x, y);
31 | },
32 | canDrop: () => canMoveKnight(x, y, knightPosition),
33 | collect: (monitor) => ({
34 | isOver: !!monitor.isOver(),
35 | canDrop: !!monitor.canDrop(),
36 | }),
37 | }),
38 | [x, y],
39 | );
40 |
41 | return (
42 |
50 | {children}
51 | {isOver && !canDrop && }
52 | {!isOver && canDrop && }
53 | {isOver && canDrop && }
54 |
55 | );
56 | }
57 |
58 | export default BoardSquare;
59 |
--------------------------------------------------------------------------------
/src/components/listSort/box.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DragSourceMonitor, useDrag } from 'react-dnd';
3 | import ItemType from './type';
4 |
5 | let id = 1;
6 |
7 | const Box = ({ bg, text, dropCardList, updateDragAndDrop }: any) => {
8 | const style: React.CSSProperties = {
9 | background: bg,
10 | };
11 |
12 | const box = {
13 | bg,
14 | text,
15 | };
16 |
17 | const [{ isDragging }, drag] = useDrag({
18 | type: ItemType.Card,
19 | item() {
20 | //在拖动操作开始时触发
21 | const useless = dropCardList.find((item: any) => item.id === -1);
22 | // 拖拽开始时,向 cardList 数据源中插入一个占位的元素,如果占位元素已经存在,不再重复插入
23 | if (!useless) {
24 | updateDragAndDrop([{ bg: 'aqua', text: '放这里', id: -1 }, ...dropCardList]);
25 | }
26 |
27 | return box;
28 | },
29 | end(_: unknown, monitor: DragSourceMonitor) {
30 | const uselessIndex = dropCardList.findIndex((item: any) => item.id === -1);
31 |
32 | /**
33 | * 拖拽结束时,判断是否将拖拽元素放入了目标接收组件中
34 | * 1、如果是,则使用真正传入的 box 元素代替占位元素
35 | * 2、如果否,则将占位元素删除
36 | */
37 |
38 | if (monitor.didDrop()) {
39 | dropCardList.splice(uselessIndex, 1, { ...monitor.getItem(), id: id++ });
40 | } else {
41 | dropCardList.splice(uselessIndex, 1);
42 | }
43 | // 更新 cardList 数据源
44 | updateDragAndDrop(dropCardList);
45 | },
46 | collect: (monitor) => ({
47 | isDragging: monitor.isDragging(),
48 | }),
49 | });
50 | return (
51 |
52 | {text}
53 |
54 | );
55 | };
56 |
57 | export default Box;
58 |
--------------------------------------------------------------------------------
/src/components/cardSort/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import Card from './card';
5 | import './index.scss';
6 |
7 | const CARD_INIT_ARR = [
8 | {
9 | id:0,
10 | text: '000',
11 | },
12 | {
13 | id:1,
14 | text: '111',
15 | },
16 | {
17 | id:2,
18 | text: '222',
19 | },
20 | {
21 | id:3,
22 | text: '333',
23 | },
24 | {
25 | id:4,
26 | text: '444',
27 | },
28 | ];
29 |
30 | class CardSort extends React.Component {
31 | constructor(props: any) {
32 | super(props);
33 | this.state = {
34 | cardList: [],
35 | };
36 | this.changePosition = this.changePosition.bind(this);
37 | }
38 |
39 | componentDidMount() {
40 | this.setState({ cardList: CARD_INIT_ARR });
41 | }
42 |
43 | changePosition(dragIndex: string | number, hoverIndex: string | number) {
44 | let data = this.state.cardList.slice();
45 | let temp = data[dragIndex];
46 | // 交换位置
47 | data[dragIndex] = data[hoverIndex];
48 | data[hoverIndex] = temp;
49 | this.setState({ cardList: data });
50 | }
51 |
52 | render() {
53 | return (
54 |
55 |
56 |
预置卡片排序
57 |
58 | {this.state.cardList.map((each: any, index: any) => (
59 |
60 | ))}
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default CardSort;
69 |
--------------------------------------------------------------------------------
/src/components/arbitrarilyDrag/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import Classification from './Classification';
5 | import './index.scss';
6 | import { WORD_TYPE } from './type';
7 | import Word from './word';
8 |
9 | const WORDS = [
10 | {
11 | text: 'interesting',
12 | type: WORD_TYPE.adj,
13 | },
14 | {
15 | text: 'interest',
16 | type: WORD_TYPE.verb,
17 | },
18 | {
19 | text: 'forget',
20 | type: WORD_TYPE.verb,
21 | },
22 | {
23 | text: 'interested',
24 | type: WORD_TYPE.adj,
25 | },
26 | ];
27 |
28 | const Classifications = [
29 | {
30 | title: '形容词',
31 | type: WORD_TYPE.adj,
32 | },
33 | {
34 | title: '动词',
35 | type: WORD_TYPE.verb,
36 | },
37 | ];
38 |
39 | class WordClassification extends React.Component {
40 | constructor(props: any) {
41 | super(props);
42 | this.state = {};
43 | }
44 |
45 | render() {
46 | return (
47 |
48 |
49 |
任意拖拽
50 |
51 | {Classifications.map((each, index) => (
52 |
53 | ))}
54 |
55 |
56 |
57 | {WORDS.map((each, index) => {
58 | let newEach = { ...each, id: 'drag' + index };
59 | return ;
60 | })}
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default WordClassification;
69 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "parser": "@typescript-eslint/parser",
7 | "parserOptions": {
8 | "project": "./tsconfig.json"
9 | },
10 | "plugins": ["jest", "@typescript-eslint"],
11 | "extends": ["react-app", "react-app/jest", "plugin:prettier/recommended"],// 关闭prettier和eslint有冲突的规则,没有定义的规则以prettiew为准
12 | "rules": {
13 | "linebreak-style": [0, "error", "windows"],
14 | "@typescript-eslint/no-explicit-any": [0],
15 | "@typescript-eslint/no-empty-function": [0],
16 | "@typescript-eslint/no-empty-interface": [0],
17 | "@typescript-eslint/no-unused-vars": [0],
18 | "@typescript-eslint/no-use-before-define": [0],
19 | "@typescript-eslint/explicit-member-accessibility": [0],
20 | "@typescript-eslint/no-var-requires": [0],
21 | "@typescript-eslint/no-non-null-assertion": [0],
22 | "@typescript-eslint/explicit-function-return-type": [0],
23 | "@typescript-eslint/ban-ts-ignore": "off",
24 | "@typescript-eslint/no-this-alias": "off",
25 | "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 5 }],
26 | "camelcase": [0],
27 | "@typescript-eslint/camelcase": [0],
28 | "@typescript-eslint/ban-ts-comment": "off",
29 | "@typescript-eslint/function-paren-newline": "off",
30 | "nonblock-statement-body-position": "off",
31 | "dot-notation": [0, { "allowKeywords": true }],
32 | "arrow-parens": 0
33 | },
34 | "overrides": [
35 | {
36 | "files": ["*.ts", "*.tsx"],
37 | "rules": {
38 | "prefer-const": [0],
39 | "no-var": [2],
40 | "no-constant-condition": [0],
41 | "camelcase": [0],
42 | "@typescript-eslint/camelcase": [0],
43 | "function-paren-newline": "off"
44 | }
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/dragPreviewDom/customDragerLayer.tsx:
--------------------------------------------------------------------------------
1 | import type { CSSProperties, FC } from 'react';
2 | import type { XYCoord } from 'react-dnd';
3 | import { useDragLayer } from 'react-dnd';
4 |
5 | function snapToGrid(x: number, y: number): [number, number] {
6 | const snappedX = Math.round(x / 32) * 32;
7 | const snappedY = Math.round(y / 32) * 32;
8 | return [snappedX, snappedY];
9 | }
10 |
11 | const layerStyles: CSSProperties = {
12 | position: 'fixed',
13 | pointerEvents: 'none',
14 | zIndex: 100,
15 | left: 0,
16 | top: 0,
17 | // width: '100%',
18 | // height: '100%',
19 | };
20 |
21 | function getItemStyles(
22 | initialOffset: XYCoord | null,
23 | currentOffset: XYCoord | null,
24 | isSnapToGrid: boolean
25 | ) {
26 | if (!initialOffset || !currentOffset) {
27 | return {
28 | display: 'none',
29 | };
30 | }
31 |
32 | let { x, y } = currentOffset;
33 |
34 | if (isSnapToGrid) {
35 | x -= initialOffset.x;
36 | y -= initialOffset.y;
37 | [x, y] = snapToGrid(x, y);
38 | x += initialOffset.x;
39 | y += initialOffset.y;
40 | }
41 |
42 | const transform = `translate(${x}px, ${y}px)`;
43 | return {
44 | transform,
45 | WebkitTransform: transform,
46 | };
47 | }
48 |
49 | export const CustomDragLayer = (props: any) => {
50 | const { itemType, isDragging, item, initialOffset, currentOffset } =
51 | useDragLayer((monitor) => ({
52 | item: monitor.getItem(),
53 | itemType: monitor.getItemType(),
54 | initialOffset: monitor.getInitialSourceClientOffset(),
55 | currentOffset: monitor.getSourceClientOffset(),
56 | isDragging: monitor.isDragging(),
57 | }));
58 |
59 | function renderItem() {
60 | switch (itemType) {
61 | case 'DragDropBox':
62 | return 这里是预览样式
;
63 | default:
64 | return null;
65 | }
66 | }
67 |
68 | if (!isDragging) {
69 | return null;
70 | }
71 |
72 | return (
73 |
74 |
77 | {renderItem()}
78 |
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/src/components/listSort/dropSquare.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useDrop } from 'react-dnd';
3 | import Card from './card';
4 | import ItemTypes from './type';
5 | import update from 'immutability-helper';
6 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) {
7 | const [{ canDrop }, drop] = useDrop({
8 | accept: ItemTypes.Card,
9 |
10 | canDrop: (_item, monitor) => {
11 | return true;
12 | },
13 | collect: (monitor) => ({
14 | canDrop: !!monitor.canDrop(),
15 | }),
16 | });
17 |
18 | const moveCard = useCallback(
19 | (dragIndex: number, hoverIndex: number) => {
20 | console.log('dragIndex',dragIndex,'hoverIndex',hoverIndex);
21 |
22 | /**
23 | * 1、如果此时拖拽的组件是 Box 组件,则 dragIndex 为 undefined,则此时修改,则此时修改 cardList 中的占位元素的位置即可
24 | * 2、如果此时拖拽的组件是 Card 组件,则 dragIndex 不为 undefined,此时替换 dragIndex 和 hoverIndex 位置的元素即可
25 | */
26 | if (dragIndex === undefined) {
27 | const lessIndex = dropCardList.findIndex((item: any) => item.id === -1);
28 | updateDragAndDrop(
29 | update(dropCardList, {
30 | $splice: [
31 | [lessIndex, 1],
32 | [hoverIndex, 0, { bg: 'aqua', category: '放这里', id: -1 }],
33 | ],
34 | }),
35 | );
36 | } else {
37 | const dragCard = dropCardList[dragIndex];
38 | updateDragAndDrop(
39 | update(dropCardList, {
40 | $splice: [
41 | [dragIndex, 1],
42 | [hoverIndex, 0, dragCard],
43 | ],
44 | }),
45 | );
46 | }
47 | // eslint-disable-next-line
48 | },
49 | [dropCardList, updateDragAndDrop],
50 | );
51 |
52 | return (
53 |
54 | {dropCardList.length > 0 &&
55 | dropCardList.map((each: any, index: number) => (
56 |
64 | ))}
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/src/components/multiDrag/Drag.tsx:
--------------------------------------------------------------------------------
1 | import { relative } from 'path';
2 | import { useEffect, useState } from 'react';
3 | import { useDrag } from 'react-dnd';
4 | import { getEmptyImage } from 'react-dnd-html5-backend';
5 |
6 | const cardList = [
7 | {
8 | id: 1,
9 | text: 'js',
10 | },
11 | {
12 | id: 2,
13 | text: 'css',
14 | },
15 | {
16 | id: 3,
17 | text: 'html',
18 | },
19 | ];
20 |
21 | const itemHeight = 48;
22 |
23 | const Drag = () => {
24 | let [selectList, setSelectList] = useState(cardList) as [any[], any];
25 | let [isDragBegin, setisDragBegin] = useState(false);
26 | const [{ isDragging }, drag] = useDrag({
27 | type: 'DragDropBox',
28 | item: () => {
29 | setisDragBegin(() => true);
30 | return {};
31 | },
32 | end: () => {
33 | if (isDragBegin) {
34 | setisDragBegin(() => false);
35 | }
36 | },
37 | collect: (monitor) => {
38 | const isDragging = monitor.isDragging();
39 | return {
40 | isDragging,
41 | };
42 | },
43 | });
44 |
45 | const updateList = (each: any) => {
46 | let newSelectList: any[] = [];
47 | newSelectList = selectList.map((item) => {
48 | if (item.id === each.id) {
49 | if (selectList.filter((item) => item.id === each.id && item.selected).length > 0) {
50 | item.selected = false;
51 | } else {
52 | item.selected = true;
53 | }
54 | }
55 | return item;
56 | });
57 | setSelectList(newSelectList);
58 | };
59 |
60 | const isSelectItem = (id: any) => {
61 | return selectList
62 | .filter((item) => item.selected)
63 | .map((item) => item.id)
64 | .includes(id);
65 | };
66 |
67 | return (
68 |
69 |
70 | {cardList.map((each) => (
71 |
72 | {each.text}
73 | updateList(each)} />
74 |
75 | ))}
76 |
77 |
78 |
79 | {selectList.map((each) => (
80 |
89 | {each.text}
90 | updateList(each)} />
91 |
92 | ))}
93 |
94 |
95 |
96 | );
97 | };
98 | export default Drag;
99 |
--------------------------------------------------------------------------------
/src/components/listSort/card.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * filename: Card
3 | * overview: 根据放入 Box 生成的 Card 组件
4 | */
5 |
6 | import React, { useRef, useMemo } from 'react';
7 | import { XYCoord } from 'dnd-core';
8 | import { DragSourceMonitor, DropTargetMonitor, useDrag, useDrop } from 'react-dnd';
9 | import ItemTypes from './type';
10 |
11 | const Card = ({ bg, text, index, moveCard, id, updateDragAndDrop, dropCardList }: any) => {
12 | const ref = useRef(null);
13 |
14 | const [{ isDragging }, drag] = useDrag({
15 | type: ItemTypes.Card,
16 | collect: (monitor: any) => ({
17 | isDragging: monitor.getItem() ? index === monitor.getItem().index : false, // 直接用monitor.isDragging无法监控索引更改
18 | // isDragging: monitor.isDragging()
19 | }),
20 | // item 中包含 index 属性,则在 drop 组件 hover 和 drop 是可以根据第一个参数获取到 index 值
21 | item: { index, id },
22 |
23 | end(item: any, monitor: DragSourceMonitor) {
24 | const idx = item.id;
25 | const uselessIndex = dropCardList.findIndex((item: any) => item.id === idx);
26 |
27 | /**
28 | * 监控是否将元素移出了列表
29 | */
30 | if (!monitor.didDrop()) {
31 | dropCardList.splice(uselessIndex, 1);
32 | }
33 | // 更新 cardList 数据源
34 | updateDragAndDrop(dropCardList);
35 | },
36 | });
37 |
38 | const [, drop] = useDrop({
39 | accept: ItemTypes.Card,
40 | hover(item: any, monitor: DropTargetMonitor) {
41 | if (!ref.current) {
42 | return;
43 | }
44 | const dragIndex = item.index;
45 | const hoverIndex = index;
46 |
47 | // 拖拽元素下标与鼠标悬浮元素下标一致时,不进行操作
48 | if (dragIndex === hoverIndex) {
49 | return;
50 | }
51 |
52 | // 确定屏幕上矩形范围
53 | const hoverBoundingRect = ref.current!.getBoundingClientRect();
54 |
55 | // 获取中点垂直坐标
56 | const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
57 |
58 | // 确定鼠标位置
59 | const clientOffset = monitor.getClientOffset();
60 |
61 | // 获取距顶部距离
62 | const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
63 |
64 | /**
65 | * 只在鼠标越过一半物品高度时执行移动。
66 | *
67 | * 当向下拖动时,仅当光标低于50%时才移动。
68 | * 当向上拖动时,仅当光标在50%以上时才移动。
69 | *
70 | * 可以防止鼠标位于元素一半高度时元素抖动的状况
71 | */
72 |
73 | // 向下拖动
74 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
75 | return;
76 | }
77 |
78 | // 向上拖动
79 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
80 | return;
81 | }
82 |
83 | // 执行 move 回调函数
84 | moveCard(dragIndex, hoverIndex);
85 |
86 | /**
87 | * 如果拖拽的组件为 Box,则 dragIndex 为 undefined,此时不对 item 的 index 进行修改
88 | * 如果拖拽的组件为 Card,则将 hoverIndex 赋值给 item 的 index 属性
89 | */
90 | if (item.index !== undefined) {
91 | item.index = hoverIndex;
92 | }
93 | },
94 | });
95 |
96 | const style: React.CSSProperties = useMemo(
97 | () => ({
98 | // Card 为占位元素是,透明度 0.4,拖拽状态时透明度 0.2,正常情况透明度为 1
99 | opacity: id === -1 ? 0.4 : isDragging ? 1 : 1,
100 | backgroundColor: bg,
101 | }),
102 | [bg, id, isDragging],
103 | );
104 |
105 | /**
106 | * 使用 drag 和 drop 对 ref 进行包裹,则组件既可以进行拖拽也可以接收拖拽组件
107 | */
108 | return (
109 |
110 | {text}
111 |
112 | );
113 | };
114 |
115 | export default Card;
116 |
--------------------------------------------------------------------------------
/src/components/cardAssemble/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DndProvider } from 'react-dnd';
3 | import { HTML5Backend } from 'react-dnd-html5-backend';
4 | import DragSquare from './dragSquare';
5 | import DropSquare from './dropSquare';
6 | import './index.scss';
7 |
8 | const CARD_INIT_ARR = [
9 | {
10 | id: 1,
11 | text: '她',
12 | },
13 | {
14 | id: 2,
15 | text: '我的',
16 | },
17 | {
18 | id: 3,
19 | text: '唯一',
20 | },
21 | {
22 | id: 4,
23 | text: '是',
24 | },
25 | {
26 | id: 5,
27 | text: '现在',
28 | },
29 | ];
30 |
31 | const INIT_DROP = {
32 | innerDrag: null,
33 | } as any;
34 |
35 | const INIT_DROP_LIST = new Array(5).fill(0).map((_item, index) => {
36 | return { ...INIT_DROP, id: index + 1 };
37 | });
38 |
39 | class CardSort extends React.Component {
40 | constructor(props: any) {
41 | super(props);
42 | this.state = {
43 | dragCardList: CARD_INIT_ARR,
44 | dropCardList: INIT_DROP_LIST,
45 | };
46 | this.updateDragAndDrop = this.updateDragAndDrop.bind(this);
47 | }
48 |
49 | updateDragAndDrop(dragId: any, dropId?: any) {
50 | let { dragCardList, dropCardList } = this.state;
51 | let moveDragItem: any = null;
52 | if (!dropId) {
53 | if (dragCardList.length === 5) return;
54 | this.dragItemBack(dragId);
55 | } else {
56 | let beginDragId = -1;
57 | [moveDragItem, dragCardList] = this.removeDragItemById(dragId, dragCardList);
58 |
59 | if (!moveDragItem) {
60 | [moveDragItem, dropCardList, beginDragId] = this.removeDropItemById(dragId, dropCardList);
61 | }
62 |
63 | dropCardList = dropCardList.map((item: { id: any; innerDrag: any }, _index: any, arr: any) => {
64 | if (item.id === dropId) {
65 | if (item.innerDrag) {
66 | arr[beginDragId].innerDrag = item.innerDrag;
67 | }
68 | item.innerDrag = moveDragItem;
69 | }
70 | return item;
71 | });
72 | this.setState({ dragCardList, dropCardList });
73 | }
74 | }
75 |
76 | // 从drop列表将drag拖拽回去
77 | dragItemBack(dragId: number) {
78 | let { dragCardList, dropCardList } = this.state;
79 | let moveDragItem = null;
80 | [moveDragItem, dropCardList] = this.removeDropItemById(dragId, dropCardList);
81 |
82 | dragCardList.push(moveDragItem);
83 | dragCardList = dragCardList.sort((pre: any, next: any) => pre.id - next.id);
84 | this.setState({ dragCardList, dropCardList });
85 | }
86 |
87 | // 根据id从drop列表中删除drop
88 | removeDropItemById(itemId: any, dropCardList: any) {
89 | let moveDragItem: any = null;
90 | let dropId = -1;
91 | let newDropCardList = dropCardList.map((item: { id: any; innerDrag: any }, index: number) => {
92 | if (item.innerDrag && item.innerDrag.id === itemId) {
93 | moveDragItem = item.innerDrag;
94 | dropId = index;
95 | item.innerDrag = null;
96 | }
97 | return item;
98 | });
99 | return [moveDragItem, newDropCardList, dropId];
100 | }
101 |
102 | // 根据id从drag列表中删除drag
103 | removeDragItemById(itemId: any, dragCardList: any) {
104 | let moveDragItem: any = null;
105 | dragCardList = dragCardList.filter((item: any) => {
106 | if (item.id === itemId) {
107 | moveDragItem = item;
108 | return false;
109 | }
110 | return true;
111 | });
112 | return [moveDragItem, dragCardList];
113 | }
114 |
115 | render() {
116 | const { dragCardList, dropCardList } = this.state;
117 | return (
118 |
119 |
120 |
卡片拼图
121 |
122 |
123 |
124 |
125 | );
126 | }
127 | }
128 |
129 | export default CardSort;
130 |
--------------------------------------------------------------------------------