Skip to content

Instantly share code, notes, and snippets.

@YangShunGit
Last active February 18, 2022 09:59
Show Gist options
  • Save YangShunGit/3f06bd712dce8e4428cd0ea853be6e82 to your computer and use it in GitHub Desktop.
Save YangShunGit/3f06bd712dce8e4428cd0ea853be6e82 to your computer and use it in GitHub Desktop.
react-dnd demo文件,初步实现lowCode拖拽代码
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { Outlet } from "react-router-dom";
import { v4 as uuidv4 } from 'uuid';
import { useDebounceFn } from 'ahooks';
import produce, { setAutoFreeze } from "immer"
import { useDrop } from 'react-dnd'
import SourceComp from './SourceComp';
// close automatically freezes
setAutoFreeze(false)
const divData = {
componentType: 'block',
name: 'box1',
data: {
text: '矩形组件'
},
rect: {
width: 100,
height: 100,
}
}
export default function Courses() {
const holdIndex = useRef<number>(-1);
const oldHoldIndex = useRef<number>(-1);
const [comp, setComp] = useState([]);
let compSource = useRef([]);
const [activeId, setActiveId] = useState('');
const dragEnd = useCallback((item) => {
if (item.id) return
const newItem = JSON.parse(JSON.stringify(item))
newItem.id = 'c' + uuidv4().replace(/-/g, '');
const nextComp = produce(comp, draft => {
if (holdIndex.current === -1) {
// 第一个需要push进入
draft.push(newItem);
} else {
draft[holdIndex.current] = newItem;
}
})
compSource.current = nextComp
setComp(nextComp)
setActiveId(newItem.id)
// 放置完成重置放置下标
holdIndex.current = -1;
oldHoldIndex.current = -1;
}, [comp])
const [{ canDrop, isOver }, drop] = useDrop(() => ({
accept: "box",
drop: (e: any) => {
console.log('e', e);
if (e?.name) {
dragEnd(e)
}
},
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
clientOffset: monitor.getClientOffset(),
initialClientOffset: monitor.getInitialClientOffset()
}),
}), [dragEnd]);
const renderComp = useCallback(() => {
return comp.map((item, index) => {
let compo = null;
switch (item.componentType) {
case 'block':
compo = <SourceComp
activeId={activeId}
setActiveId={setActiveId}
key={item.id}
index={index}
item={item}
comp={comp}
compSource={compSource}
setComp={setComp}
holdIndex={holdIndex}
oldHoldIndex={oldHoldIndex}
// delHoldComp={delHoldComp}
/>;
break;
case 'space':
compo = <div style={{width: '100%', height: 100, border: '1px dashed red'}}></div>
}
return compo
})
}, [comp, activeId, compSource])
const { run } = useDebounceFn(
() => {
if (isOver) {
console.log('enter');
} else {
console.log('leave');
// delHoldComp()
setComp(compSource.current)
}
},
{
wait: 50,
},
);
useEffect(() => {
run()
}, [isOver, run])
return (
<div>
<h2>Courses</h2>
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', height: 200 }}>
<div style={{ width: 200, height: '100%' }}>
<SourceComp comp={comp} setComp={setComp} item={divData} copy="true" dragEnd={dragEnd} />
</div>
<div ref={drop} id="container" style={{ width: 375, height: 667, border: '1px solid #ccc', overflow: 'auto', background: isOver && canDrop ? 'rgba(0, 128, 0, 0.1)' : '' }}>
{renderComp()}
</div>
</div>
<Outlet />
</div>
);
}
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useDrag, useDrop } from 'react-dnd'
import Moveable from "react-moveable";
import produce from "immer"
export default function SourceComp(props) {
const {
item,
copy,
activeId,
setActiveId,
index,
comp,
compSource,
setComp,
holdIndex,
oldHoldIndex,
// delHoldComp,
} = props;
const ref = useRef(null)
const [target, setTarget] = useState('')
const [frame] = useState({
translate: [0, 0],
scale: [1, 1],
});
const [{ isDragging }, drag] = useDrag(() => ({
type: "box",
item,
options: {
dropEffect: copy ? 'copy' : 'move',
},
collect: (monitor) => {
// console.log('monitor',monitor)
return ({
isDragging: monitor.isDragging(),
handlerId: monitor.getHandlerId(),
// ...monitor.getItem()
// data: monitor.data
})
},
}));
const copyComp = useCallback((index, hoverClientY, hoverMiddleY) => {
if (index !== undefined) {
if (hoverClientY < hoverMiddleY) {
if (comp[index - 1] === undefined || (comp[index - 1] && comp[index - 1].componentType !== 'space')) {
console.log('上面放置', holdIndex.current)
oldHoldIndex.current = holdIndex.current;
holdIndex.current = index;
const copySource = produce(compSource.current, draft => {
draft.splice(index, 0, { componentType: 'space' })
})
console.log(JSON.stringify(copySource, null, 2))
setComp(copySource)
}
return
}
// Dragging upwards
if (hoverClientY > hoverMiddleY) {
if (comp[index + 1] === undefined || (comp[index + 1] && comp[index + 1].componentType !== 'space')) {
console.log('下面放置', holdIndex.current)
oldHoldIndex.current = holdIndex.current;
// 下面放置时,需要区分上面是否放置,
// 是 => 则上面放置会被删除,index为放置下标
// 否 => 则放置下标为当前位置的下一位
holdIndex.current = oldHoldIndex.current === -1 ? index + 1 : index;
console.log('放置下标', holdIndex.current);
const copySource = produce(compSource.current, draft => {
draft.splice(holdIndex.current, 0, { componentType: 'space' })
})
console.log(JSON.stringify(copySource, null, 2))
setComp(copySource)
}
return
}
}
}, [comp, setComp, compSource, holdIndex, oldHoldIndex])
const [{ handlerId }, drop] = useDrop({
accept: "box",
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
}
},
hover(item, monitor) {
// console.log('item=', item);
if (!ref.current) {
return
}
// // Determine rectangle on screen
const hoverBoundingRect = ref.current?.getBoundingClientRect()
// // Get vertical middle
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// // Determine mouse position
const clientOffset = monitor.getClientOffset()
// // Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top
if (item.id === undefined) {
copyComp(index, hoverClientY, hoverMiddleY)
} else {
let dragIndex = item.index
const hoverIndex = index
console.log('dragIndex, hoverIndex', dragIndex, hoverIndex)
if (dragIndex === undefined) {
dragIndex = index;
item.index = dragIndex;
}
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
console.log('交换位置')
const copySource = produce(comp, draft => {
draft.splice(hoverIndex, 0, ...draft.splice(dragIndex, 1))
})
console.log('交换位置', copySource)
setComp(copySource)
compSource.current = copySource;
// moveCard(dragIndex, hoverIndex)
item.index = hoverIndex
}
},
}, [copyComp])
// 设置选中目标
useEffect(() => {
setTimeout(() => {
if (activeId === item.id) {
setTarget(document.querySelector(`#container .${activeId}`))
} else {
setTarget(null)
}
}, 10)
// console.log('activeId', activeId)
}, [activeId, item.id])
drag(drop(ref))
return <div
onClick={() => setActiveId && setActiveId(item.id)}
className={item.id}
ref={ref}
data-handler-id={handlerId}
style={{
position: 'relative',
width: item.rect.width,
height: item.rect.height,
background: isDragging ? 'rgba(120,120,120,0.4)' : 'green',
zIndex: target ? 1000 : undefined
}}
>
{item.data.text}
{target && <Moveable
target={target}
resizable={true}
keepRatio={false}
throttleResize={0}
renderDirections={["e", "s", "se"]}
// renderDirections={["nw", "n", "ne", "w", "e", "sw", "s", "se"]}
edge={false}
zoom={1}
origin={false}
padding={{ "left": 0, "top": 0, "right": 0, "bottom": 0 }}
onResizeStart={e => {
e.setOrigin(["%", "%"]);
e.dragStart && e.dragStart.set(frame.translate);
}}
onResize={e => {
const beforeTranslate = e.drag.beforeTranslate;
frame.translate = beforeTranslate;
// e.target.style.width = `${e.width}px`;
// e.target.style.height = `${e.height}px`;
e.target.style.transform = `translate(${beforeTranslate[0]}px, ${beforeTranslate[1]}px)`;
console.log('comp=', comp, index)
const nextComp = produce(comp, draft => {
draft[index].rect.width = e.width;
draft[index].rect.height = e.height;
})
compSource.current = nextComp
setComp(nextComp)
}}
/>
}
</div>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment