• react-dnd 拖拽


     

     Index.js:

    import React from 'react'
    import { connect } from 'react-redux'
    import { withRouter } from 'react-router-dom'
    import { Form, Button, Collapse, Col, Row } from 'antd'
    import Header from './Header'
    import useList from './useList'
    import { DndProvider } from 'react-dnd'
    import { HTML5Backend } from 'react-dnd-html5-backend'
    import { Icon } from '../../../../components/light'
    import { getComponentArr, getAttrFields } from './config'
    import List from './List'
    import BtnField from './BtnField'
    
    const { Panel } = Collapse
    
    function Index(props) {
      const {
        applicationTitle,
        dataSource,
        form,
        formForAttr,
        initValues,
        initValuesForAttr,
        tableId,
        cardActiveId,
        moveCard,
        handleFinish,
        handleFinishFailed,
        handleAdd,
        handleSave,
        handleCardActiveId,
        handleValuesChange,
        handleDelete,
      } = useList(props)
    
      return (
        <div className="m-admin-content">
          <Header
            applicationTitle={applicationTitle}
            tableId={tableId}
            onSave={handleSave}
          ></Header>
          <div className="m-design-wrap">
            <div className="m-design-sidebar">
              <Collapse defaultActiveKey={['1', '2', '3']}>
                <Panel header="通用字段" key="1">
                  <Row gutter={[2, 2]}>
                    <DndProvider backend={HTML5Backend}>
                      {getComponentArr().map((fieldInfo, index) => (
                        <BtnField key={index} fieldInfo={fieldInfo} onAdd={handleAdd} />
                      ))}
                    </DndProvider>
                  </Row>
                </Panel>
                <Panel header="联系信息字段" key="2">
                  <Row gutter={[2, 2]}>
                    <Col span={8}>
                      <div className="m-component-item">
                        <div></div>
                        <div>敬请期待</div>
                      </div>
                    </Col>
                  </Row>
                </Panel>
                <Panel header="商品字段" key="3">
                  <Row gutter={[2, 2]}>
                    <Col span={8}>
                      <div className="m-component-item">
                        <div></div>
                        <div>敬请期待</div>
                      </div>
                    </Col>
                  </Row>
                </Panel>
              </Collapse>
            </div>
            <div className="m-design-content">
              <Form
                form={form}
                labelCol={{ span: 4 }}
                wrapperCol={{ span: 17 }}
                initialValues={{ ...initValues }}
                onFinish={handleFinish}
                onFinishFailed={handleFinishFailed}
              >
                <DndProvider backend={HTML5Backend}>
                  <List
                    dataSource={dataSource}
                    cardActiveId={cardActiveId}
                    moveCard={moveCard}
                    handleCardActiveId={handleCardActiveId}
                    handleDelete={handleDelete}
                  />
                </DndProvider>
                <Form.Item
                  wrapperCol={{ offset: 4, span: 17 }}
                  className="m-design-footer"
                >
                  <Button type="primary" htmlType="submit" className="m-space">
                    <Icon name="submit" className="m-tool-btn-icon"></Icon>
                    提交
                  </Button>
                  <Button
                    className="m-space"
                    onClick={() => {
                      form.resetFields()
                    }}
                  >
                    <Icon name="reset" className="m-tool-btn-icon"></Icon>
                    重置
                  </Button>
                </Form.Item>
              </Form>
            </div>
            <div className="m-design-attr">
              <Form
                form={formForAttr}
                labelCol={{ span: 8 }}
                wrapperCol={{ span: 15 }}
                initialValues={{ ...initValuesForAttr }}
                scrollToFirstError={true}
                onValuesChange={handleValuesChange}
                id="m-set-application-modal-form"
                className="m-set-application-modal-form"
              >
                {getAttrFields()}
              </Form>
            </div>
          </div>
        </div>
      )
    }
    
    const mapStateToProps = (state) => {
      return {}
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        onSetState(key, value) {
          dispatch({ type: 'SET_LIGHT_STATE', key, value })
        },
        onDispatch(action) {
          dispatch(action)
        },
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Index))
    

    BtnField.js:

    import { useDrag } from 'react-dnd'
    import { ItemTypes } from './ItemTypes'
    import { Icon } from '../../../../components/light'
    import { Col } from 'antd'
    
    export default function BtnField({ fieldInfo, onAdd }) {
      const [{ isDragging }, drag] = useDrag(() => ({
        type: ItemTypes.BTN_FIELD,
        item: { ...fieldInfo },
        end: (item, monitor) => {
          const dropResult = monitor.getDropResult()
          if (item && dropResult) {
            console.log(`${item.title} 加入 ${dropResult.name}`)
            onAdd({fieldInfo})
          }
        },
        collect: (monitor) => ({
          isDragging: monitor.isDragging(),
          handlerId: monitor.getHandlerId(),
        }),
      }))
      const opacity = isDragging ? 0.4 : 1
      return (
        <Col span={8}>
          <div
            className="m-component-item"
            ref={drag}
            style={{ opacity }}
            data-testid={`box-${fieldInfo.title}`}
            onClick={() => onAdd({fieldInfo})}
          >
            <div>
              <Icon name={fieldInfo.icon}></Icon>
            </div>
            <div>{fieldInfo.title}</div>
          </div>
        </Col>
      )
    }
    

    config.js:

    import { Form, Input, Button } from 'antd'
    import { FieldRequired } from '../../../../components/light'
    
    //表格列字段
    const getColumns = (props) => {
      return [
        {
          title: 'ID',
          dataIndex: 'id',
        },
        {
          title: '字段名称',
          dataIndex: 'title',
        },
        {
          title: '英文名称',
          dataIndex: 'dataIndex',
        },
        {
          title: '表单组件名',
          dataIndex: 'formComponentName',
          render: (text) => {
            return text ? text : '无'
          },
        },
        {
          title: '渲染函数名',
          dataIndex: 'renderFunName',
          render: (text) => {
            return text ? text : '无'
          },
        },
        {
          title: '字段必填',
          dataIndex: 'rules',
          render: (text) => {
            const result = Array.isArray(text) && text.length > 0 && text[0]
            return result ? (result.required ? '是' : '否') : '否'
          },
        },
        {
          title: '表格展示',
          dataIndex: 'isColumn',
          render: (text) => {
            return text ? '是' : '否'
          },
        },
        // {
        //   title: '搜索',
        //   dataIndex: 'isSearch',
        //   render: (text) => {
        //     return text ? '是' : '否'
        //   },
        // },
        {
          title: '添加/编辑',
          dataIndex: 'isModalField',
          render: (text) => {
            return text ? '是' : '否'
          },
        },
        {
          title: '顺序号',
          dataIndex: 'orderIndex',
          render: (text) => {
            return typeof text === 'number' ? text : '无'
          },
        },
        {
          title: '操作',
           220,
          render: (record) => {
            if (record.isSystem) {
              return '系统字段'
            } else {
              return (
                <div className="m-action">
                  <Button
                    className="m-action-btn"
                    size="small"
                    danger
                    onClick={() => props.onDelete(record)}
                  >
                    删除
                  </Button>
                  <Button
                    className="m-action-btn"
                    size="small"
                    onClick={() => props.onCheck(record)}
                  >
                    查看
                  </Button>
                  <Button
                    className="m-action-btn"
                    size="small"
                    onClick={() => props.onEdit(record)}
                  >
                    编辑
                  </Button>
                </div>
              )
            }
          },
        },
      ]
    }
    
    //组件元素
    const getComponentArr = () => {
      return [
        {
          icon: 'input',
          title: '单行文本',
          formComponentName: "Input",
          dataIndex: 'input',
          renderFunName: "renderSpan"
        },
        {
          icon: 'textarea',
          title: '多行文本',
          formComponentName: "TextArea",
          dataIndex: 'textArea',
          renderFunName: "renderSpan"
        },
        {
          icon: 'number-input',
          title: '数字',
          formComponentName: "InputNumber",
          dataIndex: 'inputNumber ',
          renderFunName: "renderSpan"
        },
      ]
    }
    
    //添加编辑查看对话框表单字段
    const getAttrFields = () => {
      return (
        <>
          <Form.Item
            label="字段名称"
            name="title"
            rules={[
              {
                required: true,
                message: '请输入字段名称!',
              },
            ]}
          >
            <Input />
          </Form.Item>
          <Form.Item
            label="英文名称"
            name="dataIndex"
            rules={[
              {
                required: true,
                message: '请输入字段名称!',
              },
            ]}
          >
            <Input />
          </Form.Item>
          <Form.Item label="字段必填" name="rules">
            <FieldRequired></FieldRequired>
          </Form.Item>
        </>
      )
    }
    
    export { getColumns, getComponentArr, getAttrFields }
    

    Header.js:

    import React from 'react'
    import { Button } from 'antd'
    import { withRouter, Link } from 'react-router-dom'
    import { Icon } from '../../../../components/light'
    
    function Header(props) {
      const { applicationTitle, tableId, onSave } = props
      return (
        <div className="m-design-header">
          <div className="m-design-header-title">
            <Icon
              name="goback"
              title="返回"
              className="m-set-application-header-icon"
              onClick={() => props.history.go(-1)}
            ></Icon>
            <span title={applicationTitle}>{applicationTitle}</span>
          </div>
          <div className="m-design-header-middle"></div>
          <div className="m-design-header-action">
            <Button type="primary" onClick={onSave}>
              保存
            </Button>
            <Link to={`/light/formview?id=${tableId}`} target="_blank" style={{display: 'inherit'}}>
              <Button>预览</Button>
            </Link>
          </div>
        </div>
      )
    }
    
    export default withRouter(Header)
    

    ItemTypes.js:

    export const ItemTypes = {
      LIST_ITEM: 'listItem',
      BTN_FIELD: 'btnField' //'btnField',
    }
    

    List.js:

    import { useDrop } from 'react-dnd'
    import { ItemTypes } from './ItemTypes'
    import ListItem from './ListItem'
    
    export default function List({
      dataSource,
      cardActiveId,
      moveCard,
      handleCardActiveId,
      handleDelete,
    }) {
      const [{ canDrop, isOver }, drop] = useDrop(() => ({
        accept: ItemTypes.BTN_FIELD,
        drop: () => ({ name: '容器' }),
        collect: (monitor) => ({
          isOver: monitor.isOver(),
          canDrop: monitor.canDrop(),
        }),
      }))
      const isActive = canDrop && isOver
      return (
        <div
          ref={drop}
          className={`m-center-list-wrap ${isActive ? 'active' : ''}`}
        >
          {dataSource.map((card, index) => (
            <ListItem
              key={card.id}
              index={index}
              cardActiveId={cardActiveId}
              card={card}
              moveCard={moveCard}
              onCardActiveId={handleCardActiveId}
              onDelete={handleDelete}
            />
          ))}
        </div>
      )
    }
    

    ListItem.js:

    import { useRef } from 'react'
    import { useDrag, useDrop } from 'react-dnd'
    import { ItemTypes } from './ItemTypes'
    import { Form, Input, Button } from 'antd'
    import { getFormComponentArr } from '../../../../utils/tools'
    
    export default function ListItem({
      index,
      cardActiveId,
      card,
      moveCard,
      onCardActiveId,
      onDelete,
    }) {
      const ref = useRef(null)
      const [{ handlerId }, drop] = useDrop({
        accept: ItemTypes.LIST_ITEM,
        collect(monitor) {
          return {
            handlerId: monitor.getHandlerId(),
          }
        },
        hover(item, monitor) {
          if (!ref.current) {
            return
          }
          const dragIndex = item.index
          const hoverIndex = index
          // Don't replace items with themselves
          if (dragIndex === hoverIndex) {
            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
          // Only perform the move when the mouse has crossed half of the items height
          // When dragging downwards, only move when the cursor is below 50%
          // When dragging upwards, only move when the cursor is above 50%
          // Dragging downwards
          if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
            return
          }
          // Dragging upwards
          if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
            return
          }
          // Time to actually perform the action
          moveCard(dragIndex, hoverIndex)
          // Note: we're mutating the monitor item here!
          // Generally it's better to avoid mutations,
          // but it's good here for the sake of performance
          // to avoid expensive index searches.
          item.index = hoverIndex
    
          console.log(hoverIndex)
        },
      })
      const [{ isDragging }, drag] = useDrag({
        type: ItemTypes.LIST_ITEM,
        item: () => {
          return { id: card.id, index }
        },
        collect: (monitor) => ({
          isDragging: monitor.isDragging(),
        }),
      })
      const opacity = isDragging ? 0 : 1
      drag(drop(ref))
    
      //console.log(card)
      const renderDom = () => {
        if (card.isModalField) {
          const result = getFormComponentArr().find(
            (componentItem) =>
              componentItem.formComponentName === card.formComponentName
          )
          return (
            <div
              ref={ref}
              style={{ opacity }}
              data-handler-id={handlerId}
              className={`m-design-card ${
                cardActiveId === card.id ? 'active' : ''
              }`}
              onClick={() => onCardActiveId({ id: card.id })}
            >
              <div className="m-design-card-info">
                <Form.Item
                  key={card.id}
                  label={card.title}
                  name={card.dataIndex}
                  rules={card.rules}
                >
                  {result ? result.component : <Input></Input>}
                </Form.Item>
              </div>
              <div className="m-design-card-action">
                <Button
                  className="m-action-btn"
                  size="small"
                  danger
                  onClick={() => onDelete(card)}
                >
                  删除
                </Button>
              </div>
            </div>
          )
        } else {
          return null
        }
      }
    
      return <>{renderDom()}</>
    }
    

    useList.js:

    import { useState, useEffect, useCallback } from 'react'
    import Api from '../../../../api'
    import { Modal, Form, message } from 'antd'
    import update from 'immutability-helper'
    import { getRouterSearchObj } from '../../../../utils/tools'
    import { v4 as uuidv4 } from 'uuid'
    
    const { confirm } = Modal
    
    let currentDataSource = []
    export default function useList(props) {
      const [form] = Form.useForm()
      const [formForAttr] = Form.useForm()
      const [dataSource, setDataSource] = useState([])
      const [applicationTitle, setApplicationTitle] = useState()
      const [cardActiveId, setCardActiveId] = useState()
      const [initValuesForAttr, setInitValuesForAttr] = useState({})
    
      //获取路由参数
      const routerSearchObj = getRouterSearchObj(props)
      const tableId = routerSearchObj.id - 0
    
      const addInitValues = {}
    
      //搜索
      const handleSearch = () => {
        Api.light.fieldsSearch({ tableId }).then((res) => {
          if (res.code === 200) {
            let tempDataSource = res.data.fields.filter((item) => !item.isSystem)
            setDataSource(tempDataSource)
            setApplicationTitle(res.data.title)
            if (Array.isArray(tempDataSource) && tempDataSource.length > 0) {
              handleCardActiveId({
                id: tempDataSource[0].id,
                myDataSource: tempDataSource,
              })
            }
          }
        })
      }
    
      //拖动改变顺序
      const moveCard = useCallback(
        (dragIndex, hoverIndex) => {
          const dragCard = dataSource[dragIndex]
          setDataSource(
            update(dataSource, {
              $splice: [
                [dragIndex, 1],
                [hoverIndex, 0, dragCard],
              ],
            })
          )
        },
        [dataSource]
      )
    
      //添加新字段
      const handleAdd = ({ fieldInfo }) => {
        const orderIndexArr = currentDataSource.map((item) => item.orderIndex)
        const orderIndex = Math.max.apply(Math, orderIndexArr) + 1
        const id = uuidv4()
        let tempValues = {
          id,
          dataIndex: `${fieldInfo.dataIndex}-${id}`,
          isColumn: true,
          isModalField: true,
          orderIndex,
        }
        console.log({ ...fieldInfo, ...tempValues })
        console.log(currentDataSource)
        setDataSource([...currentDataSource, { ...fieldInfo, ...tempValues }])
      }
    
      //保存
      const handleSave = () => {
        console.log(dataSource)
        const newDataSource = dataSource.map((item, index) => {
          return { ...item, orderIndex: index + 1 }
        })
        console.log(newDataSource)
        Api.light
          .fieldsEditAll({ tableId, dataItem: newDataSource })
          .then((res) => {
            if (res.code === 200) {
              message.success(res.message)
            }
          })
      }
    
      //删除
      const handleDelete = (record) => {
        console.log('删除, id:', record.id)
        confirm({
          title: '确认要删除吗?',
          onOk() {
            const newDataSource = dataSource.filter(item => item.id !== record.id)
            setDataSource(newDataSource)
          },
        })
      }
    
      //添加或编辑
      const handleFinish = (values) => {
        console.log('Success:', values)
      }
    
      //校验失败
      const handleFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo)
      }
    
      //设置当前card
      const handleCardActiveId = ({ id, myDataSource = dataSource }) => {
        setCardActiveId(id)
        let currentItem = myDataSource.find((item) => item.id === id)
        const rules =
          Array.isArray(currentItem.rules) && currentItem.rules.length > 0
            ? currentItem.rules[0]
            : {}
        setInitValuesForAttr({ ...currentItem, rules })
      }
    
      //修改表单字段属性
      const handleValuesChange = (changedValues, allValues) => {
        const cardActiveIndex = dataSource.findIndex(
          (item) => item.id === cardActiveId
        )
    
        let tempValues = {
          rules: [allValues.rules],
        }
    
        dataSource[cardActiveIndex] = {
          ...dataSource[cardActiveIndex],
          ...allValues,
          ...tempValues,
        }
        setDataSource([...dataSource])
      }
    
      useEffect(() => {
        formForAttr.resetFields()
        // eslint-disable-next-line
      }, [initValuesForAttr])
    
      //挂载完
      useEffect(() => {
        handleSearch()
        // eslint-disable-next-line
      }, [])
    
      //dataSource更新,同步更新currentDataSource,handleAdd函数中dataSource的值为空数组,这是一个bug
      useEffect(() => {
        currentDataSource = dataSource
      }, [dataSource])
    
      return {
        form,
        formForAttr,
        initValuesForAttr,
        dataSource,
        applicationTitle,
        addInitValues,
        tableId,
        cardActiveId,
        handleSearch,
        moveCard,
        handleDelete,
        handleFinish,
        handleFinishFailed,
        handleAdd,
        handleSave,
        handleCardActiveId,
        handleValuesChange,
      }
    }
    

  • 相关阅读:
    洛谷 P1567 统计天数【最长上升子序列/断则归一】
    洛谷 P3742 umi的函数【构造】
    洛谷 P1036 选数【背包型DFS/选or不选】
    nyoj zb的生日【背包型DFS/选or不选】
    POJ 3628 Bookshelf 2【背包型DFS/选or不选】
    【AHOI2013复仇】从一道题来看DFS及其优化的一般步骤和数组分层问题【转】
    洛谷 P1217 [USACO1.5]回文质数 Prime Palindromes【取回文数/数论/字符串】
    洛谷 P1004 方格取数 【多线程DP/四维DP/】
    Codeforces Round #449 (Div. 2) B. Chtholly's request【偶数位回文数】
    Codeforces Round #449 (Div. 2) A. Scarborough Fair【多次区间修改字符串】
  • 原文地址:https://www.cnblogs.com/xutongbao/p/15264311.html
Copyright © 2020-2023  润新知