Antd可编辑表格和总计

import {
  Button,
  Col,
  Drawer,
  Form,
  Input,
  InputNumber,
  Row,
  Space,
  Table,
  Typography,
  message
} from 'antd'
import React, { CSSProperties, ReactNode, useContext, useEffect, useState } from 'react'
import { IBadBillConfirm, IBadBillDetail } from '../../types'
import { badBillConfirm, getBadBillInfo } from './services'
import useModalStore from './store/useModalStore'
import type { FormInstance } from 'antd/es/form'
import { calculateTableListAmount } from '@/views/payment/collectionDetail/list/config/amount'
const { Paragraph } = Typography
const EditableContext = React.createContext<FormInstance<any> | null>(null)

interface EditableRowProps {
  index: number
}

interface Item {
  key: string
  name: string
  age: string
  address: string
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
  const [form] = Form.useForm()
  return (
    <Form form={form} component={false}>
      <EditableContext.Provider value={form}>
        <tr {...props} />
      </EditableContext.Provider>
    </Form>
  )
}

interface EditableCellProps {
  dataIndex: any
  record: any
  handleSave: (record: Item) => void
}

const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({
  children,
  dataIndex,
  record,
  handleSave,
  ...restProps
}) => {
  const form = useContext(EditableContext)!

  useEffect(() => {
    if (dataIndex === 'bad_amount') {
      form.setFieldValue(dataIndex, record[dataIndex])
    }
  }, [])

  let childNode = children

  const save = () => {
    form.validateFields().then((values) => {
      handleSave({ ...record, bad_amount: values?.bad_amount || 0 })
    })
  }

  if (dataIndex === 'bad_amount') {
    const unpaid_amount = record?.unpaid_amount ? Number(record?.unpaid_amount) : 0
    childNode = (
      <Form.Item style={{ margin: 0 }} name={dataIndex}>
        <InputNumber
          size='middle'
          min={0}
          max={unpaid_amount < 0 ? 99999999.99 : unpaid_amount}
          disabled={unpaid_amount < 0}
          controls={false}
          placeholder='请输入'
          style={{
            width: 140
          }}
          precision={2}
          onBlur={save}
        />
      </Form.Item>
    )
  }

  return <td {...restProps}>{childNode}</td>
}

interface IComponentModalProps {
  // 刷新
  refresh: () => void
}

type EditableTableProps = Parameters<typeof Table>[0]
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>

// 坏账详情确认
const BadBillConfirm: React.FC<IComponentModalProps> = ({ refresh }) => {
  const { modalOpen, setModalOpen, selectedRows, setSelectedRowKeys, setSelectedRows } =
    useModalStore()
  const [disabled, setDisabled] = useState(false) // 禁用
  const [loading, setLoading] = useState(false) // 加载中
  const [form] = Form.useForm()
  const components = {
    body: {
      row: EditableRow,
      cell: EditableCell
    }
  }
  const [dataSource, setDataSource] = useState<IBadBillDetail[]>([])

  const defaultColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [
    {
      title: '项目名称',
      dataIndex: 'project_name',
      width: 150,
      align: 'center',
      render(value) {
        return (
          <Paragraph style={{ marginBottom: 0 }} ellipsis={{ rows: 2, tooltip: value }}>
            {value || ''}
          </Paragraph>
        )
      }
    },
    {
      title: '客户名称',
      dataIndex: 'customer_name',
      align: 'center',
      width: 150,
      render(value) {
        return (
          <Paragraph style={{ marginBottom: 0 }} ellipsis={{ rows: 2, tooltip: value }}>
            {value || ''}
          </Paragraph>
        )
      }
    },
    {
      title: '薪资所属月',
      dataIndex: 'bill_month',
      width: 90,
      align: 'center'
    },
    {
      title: '账单金额',
      dataIndex: 'bill_amount',
      align: 'right',
      width: 90
    },
    {
      title: '回款金额',
      dataIndex: 'payment_amount',
      align: 'right',
      width: 90
    },
    {
      title: '剩余应收(不含坏账)',
      dataIndex: 'unpaid_amount',
      align: 'right',
      width: 110
    },
    {
      title: '坏账金额',
      dataIndex: 'bad_amount',
      align: 'center'
    }
  ]

  const columns = defaultColumns.map((col) => {
    return {
      ...col,
      onCell: (record: any) => ({
        record,
        dataIndex: col.dataIndex,
        handleSave
      })
    }
  })

  useEffect(() => {
    modalOpen && selectedRows?.length && getBadBillInfos()
  }, [modalOpen, selectedRows?.length])

  // 查询坏账信息
  const getBadBillInfos = async () => {
    try {
      setLoading(true)
      const bill_ids = selectedRows?.map((item: any) => item?.bill_id)
      const { data } = await getBadBillInfo({ bill_ids })
      const handleData = data?.map((item: IBadBillDetail, index: number) => {
        return {
          ...item,
          keys: `badAmount${index}`
        }
      })
      setLoading(false)
      setDataSource(handleData)
    } catch (err) {
      console.log(err)
    } finally {
      setLoading(false)
    }
  }

  const handleSave = (row: any) => {
    const newData = dataSource ? [...dataSource] : []
    const index = newData.findIndex((item) => row.keys === item.keys)
    const item = newData[index]
    newData.splice(index, 1, {
      ...item,
      ...row
    })
    setDataSource(newData)
  }

  // 重置状态
  const reset = () => {
    setDisabled(false)
    form.resetFields()
    setSelectedRows([])
    setSelectedRowKeys([])
    setModalOpen(false)
  }

  // 关闭弹窗
  const close = () => {
    reset()
  }

  const onFinish = async (values: IBadBillConfirm) => {
    try {
      setDisabled(true)
      const relation_infos = dataSource?.map((item) => {
        return {
          repayment_group_detail_id: item?.repayment_group_detail_id,
          bad_amount: item?.bad_amount || 0,
          bill_id: item?.bill_id,
          project_name: item?.project_name
        }
      })
      const params = {
        remark: values?.remark,
        relation_infos
      }
      await badBillConfirm(params)
      message.success('操作成功')
      close()
      refresh()
    } catch (err) {
      console.log(err)
    } finally {
      setDisabled(false)
    }
  }

  return (
    <>
      <Drawer
        title='坏账详情确认'
        placement='right'
        onClose={close}
        width={900}
        open={modalOpen}
        className='bad-bill'
        footer={[
          <Space align='center' style={{ display: 'flex', justifyContent: 'center' }}>
            <Button key='cancel' onClick={close}>
              取消
            </Button>
            <Button key='save' type='primary' disabled={disabled} onClick={() => form.submit()}>
              保存
            </Button>
          </Space>
        ]}
      >
        <Form
          form={form}
          labelCol={{
            span: 0
          }}
          wrapperCol={{
            span: 24
          }}
          name='badBill'
          layout='horizontal'
          onFinish={onFinish}
          autoComplete='off'
        >
          <Row>
            <Col span={24}>
              <Form.Item name='remark' rules={[{ required: true, message: '请输入坏账原因' }]}>
                <Input.TextArea
                  size='middle'
                  placeholder='请输入坏账原因'
                  maxLength={50}
                  showCount={true}
                  rows={3}
                  allowClear
                />
              </Form.Item>
            </Col>
          </Row>
        </Form>
        <Table
          components={components}
          bordered
          loading={loading}
          rowKey={(_, i) => i as any}
          dataSource={dataSource}
          columns={columns as ColumnTypes}
          pagination={false}
          scroll={{ x: 'max-content', y: 'calc(100vh - 340px)' }}
          summary={() => {
            const billAmount = calculateTableListAmount(dataSource ?? [], 'bill_amount') // 账单金额总计
            const paymentAmount = calculateTableListAmount(dataSource ?? [], 'payment_amount') // 回款金额总计
            const unpaidAmount = calculateTableListAmount(dataSource ?? [], 'unpaid_amount') // 剩余应收(不含坏账)总计
            const badAmount = calculateTableListAmount(dataSource ?? [], 'bad_amount') // 坏账金额总计
            const cells: {
              index: number
              content: string | ReactNode
              style: CSSProperties
            }[] = [
              {
                index: 0,
                content: '',
                style: {}
              },
              {
                index: 1,
                content: '',
                style: {}
              },
              {
                index: 2,
                content: '',
                style: {}
              },
              {
                index: 3,
                content: `${billAmount?.toFixed(2) || '0.00'}`,
                style: {
                  textAlign: 'right'
                }
              },
              {
                index: 4,
                content: `${paymentAmount?.toFixed(2) || '0.00'}`,
                style: {
                  textAlign: 'right'
                }
              },
              {
                index: 5,
                content: `${unpaidAmount?.toFixed(2) || '0.00'}`,
                style: {
                  textAlign: 'right'
                }
              },
              {
                index: 6,
                content: `${badAmount?.toFixed(2) || '0.00'}`,
                style: {
                  textAlign: 'center'
                }
              }
            ]

            return dataSource?.length > 0 ? (
              <Table.Summary fixed>
                <Table.Summary.Row>
                  {(cells ?? []).map((i, index) => (
                    <Table.Summary.Cell key={`${i.index}-${index}`} index={i.index}>
                      <div style={i.style}>{i.content}</div>
                    </Table.Summary.Cell>
                  ))}
                </Table.Summary.Row>
              </Table.Summary>
            ) : null
          }}
        />
      </Drawer>
    </>
  )
}

export default BadBillConfirm

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Antd提供了Table组件,可以实现表格的展示和编辑功能。而要实现可编辑表格和拖拽功能,需要对Table组件进行一些扩展。 1. 可编辑表格 Antd的Table组件提供了editable属性,开启该属性后,表格单元格就可以进行编辑了。同时,还需要在columns中配置每一列是否可编辑,以及编辑时的类型和属性。 例如,下面的代码可以实现一个简单的可编辑表格: ```jsx import { Table, Input, InputNumber, Popconfirm, Form } from 'antd'; import React, { useState } from 'react'; const EditableCell = ({ editing, dataIndex, title, inputType, record, index, children, ...restProps }) => { const inputNode = inputType === 'number' ? <InputNumber /> : <Input />; return ( <td {...restProps}> {editing ? ( <Form.Item name={dataIndex} style={{ margin: 0, }} rules={[ { required: true, message: `Please Input ${title}!`, }, ]} > {inputNode} </Form.Item> ) : ( children )} </td> ); }; const EditableTable = () => { const [form] = Form.useForm(); const [data, setData] = useState([ { key: '1', name: 'Edward King 0', age: 32, address: 'London, Park Lane no. 0', }, { key: '2', name: 'Edward King 1', age: 32, address: 'London, Park Lane no. 1', }, ]); const [editingKey, setEditingKey] = useState(''); const isEditing = (record) => record.key === editingKey; const edit = (record) => { form.setFieldsValue({ name: '', age: '', address: '', ...record, }); setEditingKey(record.key); }; const cancel = () => { setEditingKey(''); }; const save = async (key) => { try { const row = await form.validateFields(); const newData = [...data]; const index = newData.findIndex((item) => key === item.key); if (index > -1) { const item = newData[index]; newData.splice(index, 1, { ...item, ...row }); setData(newData); setEditingKey(''); } else { newData.push(row); setData(newData); setEditingKey(''); } } catch (errInfo) { console.log('Validate Failed:', errInfo); } }; const columns = [ { title: 'Name', dataIndex: 'name', width: '25%', editable: true, }, { title: 'Age', dataIndex: 'age', width: '15%', editable: true, }, { title: 'Address', dataIndex: 'address', width: '40%', editable: true, }, { title: 'Operation', dataIndex: 'operation', render: (_, record) => { const editable = isEditing(record); return editable ? ( <span> <a href="javascript:;" onClick={() => save(record.key)} style={{ marginRight: 8, }} > Save </a> <Popconfirm title="Sure to cancel?" onConfirm={cancel}> <a>Cancel</a> </Popconfirm> </span> ) : ( <a disabled={editingKey !== ''} onClick={() => edit(record)}> Edit </a> ); }, }, ]; const mergedColumns = columns.map((col) => { if (!col.editable) { return col; } return { ...col, onCell: (record) => ({ record, inputType: col.dataIndex === 'age' ? 'number' : 'text', dataIndex: col.dataIndex, title: col.title, editing: isEditing(record), }), }; }); return ( <Form form={form} component={false}> <Table components={{ body: { cell: EditableCell, }, }} bordered dataSource={data} columns={mergedColumns} rowClassName="editable-row" pagination={{ onChange: cancel, }} /> </Form> ); }; export default EditableTable; ``` 2. 拖拽 Antd的Table组件默认不支持拖拽功能,但是可以通过一些插件来实现。 其中,react-beautiful-dnd是一个比较常用的拖拽插件,可以很方便地和Antd的Table组件结合使用。具体实现步骤如下: 1. 安装react-beautiful-dnd插件 ```bash npm install react-beautiful-dnd ``` 2. 将Table组件封装成DragDropContext组件 ```jsx import { Table } from 'antd'; import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; const DragTable = ({ columns, dataSource, onDragEnd }) => { return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="droppable"> {(provided) => ( <Table {...provided.droppableProps} ref={provided.innerRef} columns={columns} dataSource={dataSource} pagination={false} rowClassName="drag-row" onRow={(record, index) => ({ index, moveRow: true, })} /> )} </Droppable> </DragDropContext> ); }; export default DragTable; ``` 3. 实现onDragEnd回调函数 ```jsx const onDragEnd = (result) => { if (!result.destination) { return; } const { source, destination } = result; // 根据拖拽后的顺序重新排序dataSource const newData = [...dataSource]; const [removed] = newData.splice(source.index, 1); newData.splice(destination.index, 0, removed); // 更新dataSource setData(newData); }; ``` 最后,在页面中引用DragTable组件即可实现拖拽功能。例如: ```jsx <DragTable columns={columns} dataSource={data} onDragEnd={onDragEnd} /> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值