react + antd 自定义表单控件 TableSelect

        基于 Select 控件自定义 TableSelect 控件,实现点击控件弹出 Modal 窗口,Modal 中加载 Table 组件。选择记录可以单选和多选。返回  value: string|number; label: string} | {value: string|number; label: string}[]

        onSelect 事件可以获取到操作的记录行。
        自定义查询条件设置,使用 Row Col 布局。
        Table 组件右则显示已选择的项目。

        还未实现 Table 加载数据与后台的交互,有时间完善继续完善。

import { DownOutlined  } from '@ant-design/icons'

import { Button, Col, Form, Input, Modal, Row, Tag } from "antd";
import { Select, SelectProps, Space } from "antd";
import { Table, TableColumnsType } from "antd";
import { AnyObject } from "antd/es/table/Table";

import { CSSProperties, ReactNode, useEffect, useState } from "react";

export type ValueType = {
  value: number | string, 
  label: string;
}

type TableSelectProps = {
  allowClear?: boolean | { clearIcon?: ReactNode };
  className?: string;
  children?: ReactNode;
  disabled?: boolean;
  fieldNames?: {
    value: string; 
    label: string;
  };
  id?: string;
  maxCount?: number;
  maxTagCount?: number | 'responsive';
  maxTagTextLength?: number;
  mode?: 'multiple';
  placeholder?: string;
  sideProps?: {
    visible?: boolean;
    width?: number | string;
  }
  size?: 'large' | 'middle' | 'small';
  status?: 'error' | 'warning';
  style?: CSSProperties
  title?: string;
  value?: ValueType | ValueType[] | null;
  modalProps: {
    title: string;
    width: number | string;
    height: number | string;
  }
  onBlur?: () => void;
  onChange?: (value?: ValueType | ValueType[] | null) => void;
  onClear?: () => void;
  onFocus?: () => void;
  onSelect?: (selection?: ValueType | ValueType[] | null, record?: AnyObject, selected?: boolean) => void;
}

// 自定义查询条件
const initQueryForm = (children?: ReactNode) => {
  if(children) {
    return children;
  } else {
    return (
      <Col span={24}>
        <Form.Item name="query" label="检索">
          <Input allowClear />
        </Form.Item>
      </Col>
    );
  }
}

const doSort = (prev: string, next: string) => {
  if(prev > next) {
    return 1;
  } else if(prev < next) {
    return -1;
  } else {
    return 0;
  }
}

const TableSelect = <T extends AnyObject>(prop: TableSelectProps & {
  tableProps: {
    rowKey?: string;
    columns: TableColumnsType<T>;
  }
}) => {
  const { sideProps, children, modalProps, tableProps, value, onChange, onSelect, ...selectProps} = prop;
  const { title } = modalProps;

  const id = selectProps.id || `tableSelect${new Date().getTime()}${Math.floor(Math.random() * 9000) + 1000}`;
  const fieldNames = selectProps.fieldNames || {value: 'value', label: 'label'};
  const side = sideProps || {visible: selectProps.mode === 'multiple', width: 150}
  
  let initSelections:ValueType[] = [];
  if(value) {
    if(!Object.prototype.toString.apply(value).match(/array/gi)) {
      initSelections = [value as ValueType];
    } else {
      initSelections = value as ValueType[];
    }
  }

  const [isInit, setIsInit] = useState(true);
  const [from] = Form.useForm();
  const [openModal, setOpenModal] = useState(false);
  const [selections, setSelections] = useState<ValueType[]>([]);

  const onCancel = () => {
    setIsInit(true);
    setOpenModal(false);
  }

  useEffect(() => {
    if(isInit) {
      setIsInit(false);
      setSelections(initSelections);
    }
  }, [isInit])

  return (
    <span className="ant-table-select">
      <Select {...selectProps as SelectProps} value={value}
        id={id} labelInValue open={false} suffixIcon={<DownOutlined />} 
        onClick={() => setOpenModal(true)}
        onClear={() => {
          setSelections([]);
          onChange?.(undefined);
        }}
        onDeselect={(item) => {
          const values = selections.filter(it => it.value !== item.value);
          setSelections(values);
          onChange?.(values.length===0 ? undefined : values);
        }}
      />
      {openModal && <Modal title={title} open={openModal} bodyStyle={{width: modalProps.width}}
        width={`calc(${`${modalProps.width}`.match(/^\d+$/) ? `${modalProps.width}px` : modalProps.width} + 60px)`}
        footer={selectProps.mode === 'multiple' ? undefined : []}
        onCancel={onCancel}
        onOk={() => {
          onChange?.((selections as ValueType[]).length === 0 ? undefined: selections.sort((p, n) => doSort(p.label, n.label)));
          onCancel()
        }}
      >
        <Form form={from} layout="inline" style={{marginBottom: 5}}>
          <div style={{flex: '1 0'}}>
            <Row gutter={[0, 5]} style={{width: '100%'}}>
              {initQueryForm(children)}
            </Row>
          </div>
          <div>
            <Space size={5}>
              <Button>重置</Button>
              <Button type="primary">查询</Button>
            </Space>
          </div>
        </Form>
        <div style={{display: 'flex'}}>
          <div style={{flex: '1 0'}}>
            <Table size="small" {...tableProps}
              rowKey={tableProps.rowKey||'id'}
              pagination={{pageSize: 20, current: 1, total: 3}}
              scroll={{y: modalProps.height}}
              dataSource={[{id: '123', name: '测试1'}, {id: '456', name: '测试2'}, {id: '789', name: '测试3'}] as unknown as T[]}
              ref={(target) => {
                if(target) {
                  const tableBody = target.querySelector('.ant-table-body') as HTMLDivElement;
                  tableBody.style.minHeight = tableBody.style.maxHeight;
                }
              }}
              rowSelection={{
                selectedRowKeys: selections.map(it => it.value),
                type: selectProps.mode === 'multiple' ? 'checkbox' :'radio',
                onSelect: (record, selected, _selectedRows) => {
                  const selection = {value: record[fieldNames.value], label: record[fieldNames.label]};
                  if(selected) {
                    setSelections(values => values.concat([selection]));
                  } else {
                    setSelections(values => values.filter(it => it.value !== record[fieldNames.value]));
                  }
                  onSelect?.(selection, record, selected);
                },
                onSelectAll: (selected, _selectedRows, changeRows) => {
                  const selections = changeRows.map(it => ({value: it[fieldNames.value], label: it[fieldNames.label]}));
                  if(selected) {
                    setSelections(values => values.concat(selections));
                  } else {
                    setSelections(values => values.filter(it => -1 === changeRows.findIndex(c => it.value === c[fieldNames.value])));
                  }
                  onSelect?.(selections, changeRows, selected);
                }
              }}
              onRow={(record) => {
                return {
                  onClick: () => {
                    const selection = {value: record[fieldNames.value], label: record[fieldNames.label]};
                    if(selectProps.mode !== 'multiple') {
                      onSelect?.(selection, record, true);
                      onChange?.(selection);
                      onCancel();
                    } else {
                      const index = selections.findIndex(it => it.value === record[fieldNames.value]);
                      if(-1 === index) {
                        setSelections(values => values.concat([{value: record[fieldNames.value], label: record[fieldNames.label]}]));
                        onSelect?.(selection, record, true);
                      } else {
                        setSelections(values => values.filter(it => it.value !== record[fieldNames.value]));
                        onSelect?.(selection, record, false);
                      }
                    }
                  }
                };
              }}
            />
          </div>
          {selectProps.mode === 'multiple' && side.visible && <div style={{width: side.width, height: '100%', overflowY: 'auto', overflowX: 'hidden'}}>
            {selections.map(it => <Tag key={`tag${it.value}`} closable  style={{margin: '2px 0 0 2px'}}
                  onClose={() => setSelections(values => values.filter(v => v.value !== it.value))} >{it.label}</Tag>)}
          </div>}
        </div>
      </Modal>}
    </span>
  );
}

export { TableSelect }
import { Button, Form, TableColumnsType } from "antd";
import { TableSelect } from "common/components/TableSelect";

interface DataType {
  key: React.Key;
  name: string;
  age: number;
  address: string;
}

export default () => {
  const columns: TableColumnsType<DataType> = [
    {title: 'Name', dataIndex: 'name', render: (text: string) => <a>{text}</a>},
    {title: 'Age', dataIndex: 'age'},
    {title: 'Address', dataIndex: 'address'}
  ];

  const [form] = Form.useForm();
  
  return (
    <>
    <Form form={form} initialValues={{ test2: {value: '123', label: '123'} }}>
      <Form.Item name="test2">
        <TableSelect mode="multiple" allowClear style={{width: 160}} maxTagCount={1}
          modalProps={{title:'2222', width: 800, height: 400}} tableProps={{columns}}
          fieldNames={{label: 'name', value: 'id'}}
        >
          {/* <Form.Item name="query1" label="查询1">
            <Input />
          </Form.Item>
          <Form.Item name="query2" label="查询2">
            <Input />
          </Form.Item> */}
        </TableSelect>
      </Form.Item>
    </Form>
    <Button onClick={() => console.log(form.getFieldsValue())}>Test</Button>
    </>
  );
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React中,清空单可以通过Antd的Form组件提供的`resetFields`方法来实现。具体步骤如下: 1. 在单的父组件中引入`Form`组件,并将单的所有控件都用`Form.Item`包裹起来,每个`Form.Item`需要设置`name`属性,这个属性值需要与`getFieldDecorator`方法中的`id`参数保持一致。 ```jsx import { Form, Input, Button } from 'antd'; class MyForm extends React.Component { render() { const { getFieldDecorator } = this.props.form; return ( <Form> <Form.Item label="用户名" name="username"> {getFieldDecorator('username')(<Input />)} </Form.Item> <Form.Item label="密码" name="password"> {getFieldDecorator('password')(<Input.Password />)} </Form.Item> <Form.Item> <Button type="primary" onClick={this.handleSubmit}>提交</Button> <Button onClick={this.handleReset}>重置</Button> </Form.Item> </Form> ); } } ``` 2. 在单的父组件中定义`handleSubmit`和`handleReset`方法。`handleSubmit`方法用于提交单,`handleReset`方法用于清空单。 ```jsx class MyForm extends React.Component { handleSubmit = e => { e.preventDefault(); this.props.form.validateFields((err, values) => { if (!err) { console.log('Received values of form: ', values); } }); }; handleReset = () => { this.props.form.resetFields(); }; render() { //... } } ``` 3. 在单的父组件中将`MyForm`组件包裹在`Form.create`函数中,生成一个新的高阶组件,并将其导出。 ```jsx const WrappedMyForm = Form.create({ name: 'my_form' })(MyForm); export default WrappedMyForm; ``` 这样,当用户点击单中的“重置”按钮时,单中的所有控件都会被清空。如果想要清空单中的某一个控件,可以通过`setFieldsValue`方法来清空,具体可见前面的回答。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值