基于 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>
</>
);
}