背景
新需求涉及关于物流线路的问题,产品想要根据选择的线路进行自定义排序从而快速操作,以满足新建好了线路中的某一个,但想调整该站点的顺序
由于之前的项目都是用的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