EditableProtable实现拖拽排序

这里写自定义目录标题

背景

新需求涉及关于物流线路的问题,产品想要根据选择的线路进行自定义排序从而快速操作,以满足新建好了线路中的某一个,但想调整该站点的顺序

在这里插入图片描述

由于之前的项目都是用的vue3,有同事觉得ant design pro的组件设计,比我们之前基于element ui封装的组件库更高级,所以转战react,但是ant design pro并没有封装可编辑表格的拖拽组件,想要实现form表单的校验+可拖拽+可编辑的需求,遇到了很多坑所以记录一下

附上源码


import { SITE_TYPE_MAP, State } from '@/constants/site'
import { MenuOutlined } from '@ant-design/icons'
import {
  EditableFormInstance,
  EditableProTable,
  ModalForm,
  ProColumns,
  ProForm,
  ProFormInstance,
} from '@ant-design/pro-components'
import type { DragEndEvent } from '@dnd-kit/core'
import { DndContext } from '@dnd-kit/core'
import { restrictToVerticalAxis } from '@dnd-kit/modifiers'
import {
  arrayMove,
  SortableContext,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { App, Form, Select } from 'antd'
import React, { useEffect, useRef, useState } from 'react'

interface RowProps extends React.HTMLAttributes<HTMLTableRowElement> {
  'data-row-key': string
}

interface IProps {
  open?: boolean
  record: DeliveryLogisticsRouteDetailOutModel
  onOpenChange?: (visible: boolean) => void
}

const Row = ({ children, ...props }: RowProps) => {
  const {
    attributes,
    listeners,
    setNodeRef,
    setActivatorNodeRef,
    transform,
    transition,
    isDragging,
  } = useSortable({
    id: props['data-row-key'],
  })

  const style: React.CSSProperties = {
    ...props.style,
    transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }),
    transition,
    ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}),
  }

  return (
    <tr id={props.id} {...props} ref={setNodeRef} style={style} {...attributes}>
      {React.Children.map(children, (child) => {
        if ((child as React.ReactElement).key === 'sort') {
          return React.cloneElement(child as React.ReactElement, {
            children: (
              <MenuOutlined
                ref={setActivatorNodeRef}
                style={{ touchAction: 'none', cursor: 'move' }}
                {...listeners}
              />
            ),
          })
        }
        return child
      })}
    </tr>
  )
}

interface Item extends Omit<DeliverySiteOutDTO, 'name'> {
  key: number
  name?: number | string
  children?: Item[]
}

const SConfig: React.FC<IProps> = ({ open, record, onOpenChange }) => {
  const [dataSource, setDataSource] = useState<Item[]>([])
  const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([])
  const [optionsValues, setOptionsValues] = useState<DeliverySiteOutModel[]>([])
  const { message } = App.useApp()
  const [form] = Form.useForm()

  const initialSearch = async (keyWords = '') => {
  // 以下类似都是接口请求
    const { data } = await deliverySiteControllerListUsingPost
    const result = data ?? []
    setOptionsValues(result)
  }

  useEffect(() => {
    const list =
      record?.deliverySiteOutDTOList?.map?.((item) => ({
        key: Math.random() * 1000000,
        ...item,
        name: item.id,
      })) ?? []
    setDataSource(list)
    const dataKeys = list.map((item) => item.key)
    setEditableRowKeys(dataKeys)
    initialSearch()
  }, [record])
  const formRef = useRef<ProFormInstance<any>>()

  const onDragEnd = ({ active, over }: DragEndEvent) => {
    if (active.id !== over?.id) {
      const tableDataSource = formRef.current?.getFieldValue('table') as Item[]
      const activeIndex = tableDataSource.findIndex((i) => i.key === active.id)
      const overIndex = tableDataSource.findIndex((i) => i.key === over?.id)
      const dagEndList = arrayMove(tableDataSource, activeIndex, overIndex)

      formRef.current?.setFieldsValue({
        table: dagEndList,
      })
    }
  }

  const save = async (id: number, _: any, config: any) => {
    try {
      const options = optionsValues.find((item) => item.id === id)

      const tableDataSource = formRef.current?.getFieldValue('table') as Item[]
      tableDataSource.forEach((item) => {
        if (item.key === config.record.key) {
          item.type = options?.type
        }
      })
      formRef.current?.setFieldsValue({
        table: tableDataSource,
      })
    } catch (errInfo) {
      console.log('Save failed:', errInfo)
    }
  }

  const columns: ProColumns<Item>[] = [
    {
      key: 'sort',
      dataIndex: 'key',
      editable: false,
      width: 60,
    },
    {
      title: '站点名称',
      dataIndex: 'name',
      key: 'name',
      valueType: 'select',
      formItemProps: {
        rules: [{ required: true, message: '请选择站点名称' }],
      },
      renderFormItem(schema, config) {
        return (
          <Select
            onChange={(id: number) => save(id, schema, config)}
            fieldNames={{
              label: 'name',
              value: 'id',
            }}
            defaultActiveFirstOption={false}
            placeholder="请选择仓库简称"
            options={optionsValues}
          />
        )
      },

      className: 'drag-visible',
    },
    {
      title: '站点类型',
      dataIndex: 'type',
      valueType: 'select',
      key: 'type',
      valueEnum: SITE_TYPE_MAP,
      className: 'drag-visible',
      editable: false,
    },
    {
      title: '操作',
      valueType: 'option',

      render: (text, record) => [
        <a
          key="delete"
          onClick={() => {
            const tableDataSource = formRef.current?.getFieldValue('table') as Item[]
            formRef.current?.setFieldsValue({
              table: tableDataSource.filter((item) => item.key !== record.key),
            })
          }}
        >
          删除
        </a>,
      ],
    },
  ]
  const editorFormRef = useRef<EditableFormInstance<Item>>()

  return (
    <>
      <ModalForm<{ table: Item[] }>
        title="站点配置"
        modalProps={{
          destroyOnClose: true,
        }}
        validateTrigger="onBlur"
        form={form}
        open={open}
        formRef={formRef}
        onOpenChange={onOpenChange}
        submitTimeout={2000}
        onFinish={async (values) => {
          const ids = values.table?.map?.((item: any) => item.name) ?? []
          deliveryLogisticsRouteControllerInsertDeliveryLogisticsRouteSiteUsingPost({
            deliveryLogisticsRouterId: record.id,
            deliverySiteIdList: ids,
          })
          message.success('提交成功')
          return true
        }}
      >
        <ProForm.Item label="" name="table" initialValue={dataSource} trigger="onValuesChange">
          <DndContext modifiers={[restrictToVerticalAxis]} onDragEnd={onDragEnd}>
            <SortableContext
              // rowKey array
              items={editableKeys}
              strategy={verticalListSortingStrategy}
            >
              <EditableProTable<Item>
                rowKey={(obj) => obj.key}
                name="table"
                editable={{
                  type: 'multiple',
                  editableKeys,
                  onChange(editableKeys) {
                    setEditableRowKeys(editableKeys)
                  },
                  actionRender: (row, config, defaultDoms) => {
                    return [defaultDoms.delete]
                  },
                }}
                editableFormRef={editorFormRef}
                recordCreatorProps={{
                  newRecordType: 'dataSource',
                  position: 'bottom',
                  record: () => {
                    const addItem = {
                      key: Math.random() * 1000000,
                      name: undefined,
                      type: undefined,
                    }

                    return addItem
                  },
                }}
                columns={columns}
                pagination={false}
                components={{
                  body: {
                    row: Row,
                  },
                }}
              />
            </SortableContext>
          </DndContext>
        </ProForm.Item>
      </ModalForm>
    </>
  )
}
export default SConfig

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值