Last active
February 18, 2022 09:59
-
-
Save YangShunGit/3f06bd712dce8e4428cd0ea853be6e82 to your computer and use it in GitHub Desktop.
react-dnd demo文件,初步实现lowCode拖拽代码
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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