import React , { memo , useState , useEffect , useCallback } from 'react' ; import { EditOutlined , MinusCircleOutlined , MenuOutlined } from '@ant-design/icons' ; import { Button } from 'antd' ; import { DragSource , DropTarget , DndProvider , ConnectDropTarget , DragSourceSpec , DropTargetConnector , DragSourceMonitor , DragSourceConnector , DropTargetSpec , ConnectDragSource , ConnectDragPreview , } from 'react-dnd' ; import { HTML5Backend } from 'react-dnd-html5-backend' ; import EditorModal from './editorModal' ; import { uuid } from '@/utils/tool' ; import styles from './index.less' ; import { TDataListDefaultType , TDataListDefaultTypeItem } from '../FormEditor/types' ; type ListItemProps = DndItemProps & { isDragging : boolean ; connectDragSource : ConnectDragSource ; connectDragPreview : ConnectDragPreview ; connectDropTarget : ConnectDropTarget ; } ; function ListItem ( props ) { const { title , desc , onDel , onEdit , isDragging , connectDragSource , connectDragPreview , connectDropTarget , } = props ; const opacity = isDragging ? 0.5 : 1 ; return connectDropTarget ( connectDragPreview ( < div className = { styles . listItem } style = { Object . assign ( { } , { opacity } ) } > < div className = { styles . tit } > { title } < /div> < div className = { styles . desc } > { desc } < /div> < div className = { styles . actionBar } > < span className = { styles . action } onClick = { ( ) => onEdit ( ) } > < EditOutlined /> < /span> < span className = { styles . action } onClick = { ( ) => onDel ( ) } > < MinusCircleOutlined /> < /span> { connectDragSource ( < span className = { styles . action } > < MenuOutlined /> < /span>) / / 拖动图标作为 Drag 对象 } < /div> < /div>)); } type DndItemProps = TDataListDefaultTypeItem & { onDel : Function ; onEdit : Function ; key : number ; find : Function ; move : Function ; type ? : number ; } ; const type = 'item' ; type DragObject = { id : string ; originalIndex : number ; } ; const dragSpec = { beginDrag : props => ( { id : props . id , originalIndex : props . find ( props . id ) . index , } ) , endDrag ( props , monitor ) { const { id : droppedId , originalIndex } = monitor . getItem ( ) ; const didDrop = monitor . didDrop ( ) ; if ( ! didDrop ) { return props . move ( droppedId , originalIndex ) ; } } , } ; const dragCollect = ( connect , monitor ) => ( { connectDragSource : connect . dragSource ( ) , connectDragPreview : connect . dragPreview ( ) , isDragging : monitor . isDragging ( ) , } ) ; const dropSpec = { canDrop : ( ) => false , hover ( props , monitor ) { const { id : draggedId } = monitor . getItem ( ) ; const { id : overId } = props ; if ( draggedId !== overId ) { const { index : overIndex } = props . find ( overId ) ; props . move ( draggedId , overIndex ) ; } } , } ; const dropCollect = ( connect ) => ( { connectDropTarget : connect . dropTarget ( ) , } ) ; const DndItem = DropTarget ( type , dropSpec , dropCollect ) ( DragSource ( type , dragSpec , dragCollect ) ( ListItem ) ) ; export type DataListMemo = { onChange ? : ( v : TDataListDefaultType ) => void ; value ? : TDataListDefaultType ; cropRate : number | undefined ; } ; export type DataListType = DataListMemo & { connectDropTarget : ConnectDropTarget ; } ; const List = function ( props ) { const { onChange , value , connectDropTarget , cropRate } = props ; const [ list , setList ] = useState ( value ) ; const [ visible , setVisible ] = useState ( false ) ; const [ curItem , setCurItem ] = useState < TDataListDefaultTypeItem > ( ) ; const handleDel = ( id ) => { if ( value && onChange ) { let newVal = value . filter ( item => id !== item . id ) ; onChange ( newVal ) ; } } ; const find = ( id ) => { const item = list ! . find ( c => `${ c . id }` === id ) ! ; return { item , index : list ! . indexOf ( item ! ) , } ; } ; const move = ( id , toIndex ) => { const { item , index } = find ( id ) ; const oldList = [ ... list ! ] ; oldList . splice ( index , 1 ) ; oldList . splice ( toIndex , 0 , item ) ; if ( onChange ) { onChange ( oldList ) ; return ; } setList ( oldList ) ; } ; const handleCancel = useCallback ( ( ) => { setVisible ( false ) ; } , [ ] ) ; const handleEdit = useCallback ( ( item ) => { setVisible ( true ) ; setCurItem ( item ) ; } , [ ] ) ; const handleSave = useCallback ( ( item ) => { setVisible ( false ) ; if ( onChange ) { onChange ( list ! . map ( p => ( p . id === item . id ? item : p ) ) ) ; return ; } setList ( prev => prev ! . map ( p => ( p . id === item . id ? item : p ) ) ) ; } , [ curItem ] ) ; const handleAdd = ( ) => { const item = { title : '新增项标题' , desc : '新增项描述' , id : uuid ( 8 , 10 ) , imgUrl : [ ] , link : '' , } ; if ( onChange ) { onChange ( [ ... list ! , item ] ) ; return ; } setList ( [ ... list ! , item ] ) ; } ; useEffect ( ( ) => { setList ( value ) ; } , [ value ] ) ; return connectDropTarget ( < div className = { styles . dataList } > { ! ! ( list && list . length ) && list . map ( ( item , i ) => ( < DndItem { ... item } onDel = { ( ) => handleDel ( item . id ) } onEdit = { ( ) => handleEdit ( item ) } key = { i } id = { `${ item . id }` } find = { find } move = { move } / > ) ) } < div style = { { marginTop : '10px' } } > < Button type = "primary" onClick = { handleAdd } block > 添加 < /Button> < /div> < EditorModal visible = { visible } onCancel = { handleCancel } item = { curItem } onSave = { handleSave } cropRate = { cropRate } / > < /div>); } ; const DndList = DropTarget ( type , { } , connect => ( { connectDropTarget : connect . dropTarget ( ) , } ) ) ( List ) ; export default memo ( ( props ) => { return ( < DndProvider backend = { HTML5Backend } > < DndList { ... props } / > < /DndProvider>); } ) ;