1.ProForm表单使用
1.1父组件 index.js
import React, { useRef } from 'react';
import type { ProFormInstance } from '@ant-design/pro-components';
const baseFormRef = useRef<ProFormInstance>();
// 函数
const handleFormValuesChange = useCallback((changedValues: any, formValues: Record<string, any>) => {
const changedKey = Object.keys(changedValues || {})[0];
if (changedKey === 'createDeptName') {
// 做页面联动操作
}
const values = cloneDeep(formValues);
setBaseFormData(values);
}, [baseFormRef])
// 组件
<BaseForm
formRef={baseFormRef}
changecreateDeptName={(name) => {
setgecreateDeptName(name)
}}
onFormValuesChange={handleFormValuesChange}
/>
formRef 是为了控制整个表单,在父组件能拿到子组件的表单项,ProForm负责接收
onFormValuesChange 是当表单任意一项值发生变化时都能监听到,达到组件间的联动效果,ProForm负责接收
changecreateDeptName 传递给子组件的自定义事件
1.2子组件 baseForm.js
import { Col, Row } from 'antd';
import {
ProForm,
ProFormDatePicker,
ProFormSelect,
ProFormText,
ProFormDigit,
ProFormTextArea
} from '@ant-design/pro-components';
const formItemLayout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
<ProForm
layout="horizontal"
{...formItemLayout}
formRef={formRef}
submitter={false}
onValuesChange={(changedValues, formValues) => {
onFormValuesChange(changedValues, formValues);
}}
omitNil={false}
>
<Row gutter={[24, 0]} justify="start">
<Col span={8}>
<CProInputToSearch
label="哈哈哈"
name="managerName"
width="xl"
rules={[
{
required: true,
},
]}
fieldProps={{
onClick: (e) => {
toSearch('managerCode');
},
}}
/>
</Col>
<Col span={8}>
<CProInputToSearch
name="createDeptName"
label="啦啦啦"
width="xl"
disabled={!isSpecialRole}
width="xl"
// disabled={true}
rules={[
{
required: true,
message: '填报部门不能为空!',
},
]}
fieldProps={{
onClick: (e) => {
changecreateDeptName(formRef?.current?.getFieldValue('createDeptCode'))
toSearchDept('createDeptCode');
},
}}
/>
</Col>
</Row>
</ProForm>
1.3封装的CProInputToSearch组件
index.js
import { Component, PureComponent } from "react";
import {
ProFormText,
} from '@ant-design/pro-components';
import { SearchOutlined } from '@ant-design/icons';
import "./index.less";
interface CInputToSearchInterface {
[x: string]: any;
}
export default class CProInputToSearch extends Component<CInputToSearchInterface> {
render() {
const {
fieldProps = {},
disabled,
...other
} = this.props;
const { onClick, hasSearchIcon = true,readOnly=true, ...otherFieldProps } = fieldProps || {};
return (
<ProFormText
{ ...other }
disabled={disabled}
fieldProps={{
...otherFieldProps,
className: "c-pro-input-search",
onClick: onClick,
readOnly,
// maxLength: 0,
addonAfter: (
!disabled && hasSearchIcon ? <SearchOutlined
onClick={onClick}
/> : null
),
}}
/>
)
}
}
index.less
.c-pro-input-search {
.ant-input-affix-wrapper-readonly > .ant-input-suffix > .ant-input-clear-icon-hidden {
visibility: visible;
}
.ant-input-affix-wrapper-disabled > .ant-input-suffix > .ant-input-clear-icon-hidden {
visibility: hidden;
}
}
2.ProTable使用
需求: 点击表单项,弹窗,可选择列表,选择完成后,更新表单
/**
* 点击查询图标
* @param type
*/
const toSearch = (type: string) => {
setVisible(true);
setQueryType(type);
};
2.1封装单选列表
2.1.1配置文件
export const queryModalMap = {
createDeptCode: {
title: "封装的标题",
queryApi: "",
isPagination: true,
rowKey: "organizationCode",
columns: [
{
title: '',
dataIndex: '',
width: '50%',
align: 'center',
},
{
title: '',
dataIndex: '',
width: '50%',
align: 'center',
},
]
},
}
title定义的标题 queryApi请求的接口会返回数据源 isPagination是否分页 rowKey列表的唯一值 columns列表的columns
2.1.2父组件
const closeModal = () => {
setVisible(false);
setQueryType('');
};
const getQueryModalParams = (queryType: string) => {
let params = {};
switch (queryType) {
case 'createDeptCode':
const companyCode = formRef.current.getFieldValue('companyCode');
params = {
parentOrgCode: companyCode,
};
break;
}
return params;
};
/**
* 查询结果
* @param queryData
*/
const queryRet = (queryData: { queryType: string; selectedRow: Record<string, string> }) => {
const { queryType, selectedRow } = queryData;
let nowFormData = formRef?.current?.getFieldsValue();
nowFormData = cloneDeep(nowFormData);
switch (queryType) {
case 'createDeptCode':
const changedValues = {
createDeptName: selectedRow.orgNameKk,
createDeptCode: selectedRow.organizationCode,
managerCode: '',
givenName: '',
};
Object.assign(nowFormData, changedValues);
formRef?.current?.setFieldsValue({
...changedValues,
})
formRef?.current?.setFieldsValue({
managerList: [],
managerName: '',
managerCode: []
})
onFormValuesChange(changedValues, nowFormData);
break;
}
};
<QueryModal
queryType={queryType}
config={queryModalMap[queryType]}
visible={deptVisible}
onCancel={closeModal}
onSubmit={queryRet}
baseParams={getQueryModalParams(queryType)}
/>
)}
queryType当前弹窗类型标识 config弹窗的配置文件 visible是否显示 onCancel取消 onSubmit确定 baseParams接口的额外参数
2.1.3子组件
import React, { useRef, useState } from 'react';
import { request } from '@umijs/max'
import { Button, Form, Modal } from 'antd';
import { ProTable } from '@ant-design/pro-components';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
export type IAny = {
[property:string]: any;
}
export type UpdateFormProps = {
onCancel: (flag?: boolean, formVals?: IAny) => void;
onSubmit: (val: {queryType: string; selectedRow: IAny; selectionType: 'radio'|'checkbox'}, helpData?: Record<string, any>) => void;
visible: boolean;
config: {
title: string;
queryApi: string;
columns: ProColumns<IAny>;
isPagination?: boolean;
rowKey?: string;
selectionType?: 'checkbox' | 'radio';
},
queryType?: string;
baseParams?: Record<string, any>;
};
const QueryModal: React.FC<UpdateFormProps> = (props) => {
const {
queryType,
visible,
config,
onCancel,
onSubmit,
baseParams = {},
} = props;
const { title, queryApi, columns, isPagination, rowKey, selectionType } = config || {};
const [form] = Form.useForm();
const actionRef = useRef<ActionType>();
const [selectedRows, setSelectedRows] = useState<API.StandardItem[]>([]);
const handleOk = async () => {
onCancel();
onSubmit({
queryType: queryType || '',
selectedRow: selectionType === 'checkbox' ? selectedRows: selectedRows[0],
selectionType: selectionType || 'radio',
}, {
_baseParams: baseParams,
});
}
return (
<Modal
title={title || ''}
visible={visible}
onOk={handleOk}
onCancel={() => {
onCancel()
}}
width={800}
footer={[
<Button onClick={onCancel} key="close">取消</Button>,
<Button type="primary" key="confirm" onClick={handleOk} disabled={!selectedRows.length}>确定</Button>,
]}
>
<ProTable<API.StrategyItem, API.PageParams>
actionRef={actionRef}
rowKey={rowKey || "id"}
// 如果值不设置为0, 样式会有问题
search={{
labelWidth: 0,
// collapsed: false,
}}
// params={baseParams}
request={async (params = {}, sort, filter) => {
console.log('params====', params)
const res = await request<API.RuleList>(queryApi, {
method: 'POST',
data: {
...(baseParams || {}),
...params,
},
});
if (isPagination) {
return Promise.resolve({
data: res.data?.list ?? [],
total: res.data?.total ?? 0,
success: true,
});
}
return Promise.resolve({
data: res.data ?? [],
success: true,
});
}}
columns={columns}
toolBarRender={false}
rowSelection={{
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows);
},
type: selectionType || 'radio'
}}
scroll={{ y: 200 }}
tableAlertRender={false}
/>
</Modal>
);
};
export default QueryModal
2.2多选列表,穿梭框
2.2.1 配置文件
export const queryModalMap = {
managerCode: {
title: "项目",
queryApi: "/cmcc-budget/user/selectUserByDepartment",
isPagination: true,
selectionType: 'checkbox',
rowKey: "userAccount",
columns: [
{
title: '姓名',
dataIndex: 'userName',,
width: '50%',
align: 'center',
},
{
title: '账号',
dataIndex: 'userAccount',
width: '50%',
// hideInSearch: true,
align: 'center',
},
],
LFTableFieldTies: {
userAccount: "managerCode",
userName: "managerName",
},
},
}
2.2.2 父组件
const getQuerySelectedContent = (queryType: string) => {
if (queryType === 'managerCode') {
const xx = formRef.current.getFieldValue('managerList');
return xx;
}
return [];
};
<PartCheckModal
queryType={queryType}
config={queryModalMap[queryType]}
visible={visible}
onCancel={closeModal}
onSubmit={queryRet}
baseParams={getQueryModalParams(queryType)}
selectedDataSource={getQuerySelectedContent(queryType)}
/>
2.2.3 子组件
index.tsx
import React, { useRef, useState } from 'react';
import { request } from '@umijs/max'
import { Button, Form, Modal, Table, message } from 'antd';
import { ProTable } from '@ant-design/pro-components';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { SwapRightOutlined, SwapLeftOutlined } from '@ant-design/icons';
import styles from "./index.less"
export type IAny = {
[property:string]: any;
}
export type QueryModalProps = {
onCancel: (flag?: boolean, formVals?: IAny) => void;
onSubmit: (val: {queryType: string; selectedRow: IAny; selectionType: 'radio'|'checkbox'}) => void;
visible: boolean;
config: {
title: string;
queryApi: string;
columns: ProColumns<IAny>;
isPagination?: boolean;
rowKey?: string;
selectionType?: 'checkbox' | 'radio';
LFTableFieldTies?: Record<string, string>;
},
queryType?: string;
baseParams?: Record<string, any>;
selectedDataSource?: Record<string, any>[];
};
const QueryModalToCheckBox: React.FC<QueryModalProps> = (props) => {
const {
queryType,
visible,
config,
onCancel,
onSubmit,
baseParams = {},
selectedDataSource,
} = props;
const {
title,
queryApi,
columns,
isPagination,
rowKey = "id",
selectionType,
LFTableFieldTies,
} = config || {};
const formatSelectedDataSource = () => {
let nextData: Record<string, string>[] = [];
let nextRowKey: React.Key[] = [];
if(!LFTableFieldTies) {
return selectedDataSource || [];
}
(selectedDataSource || []).forEach(item => {
const modifidFileds = {};
Object.keys(LFTableFieldTies).forEach(key => {
modifidFileds[key] = item[LFTableFieldTies[key]]
})
const dataItem = {
...item,
...modifidFileds,
}
nextData.push(dataItem)
nextRowKey.push(dataItem[rowKey])
})
return [nextData, nextRowKey];
}
const [_SDataSource = [], _SKeys = []] = formatSelectedDataSource();
const actionRef = useRef<ActionType>();
const [leftSelectedRows, setLeftSelectedRows] = useState<API.StandardItem[]>([]);
const [leftSelectedRowsKeys, setLeftSelectedRowsKeys] = useState<React.Key[]>([])
const [rightDataSource, setRightDataSource] = useState(_SDataSource);
const [rightRowKeys, setRightRowKeys] = useState<React.Key[]>(_SKeys);
const [rightSelectedRowsKeys, setRightSelectedRowsKeys] = useState<React.Key[]>([])
const handleOk = async () => {
onSubmit({
queryType: queryType || '',
selectedRow: rightDataSource,
selectionType: selectionType || 'radio',
});
onCancel();
}
const handleAdd = () => {
if (!leftSelectedRowsKeys.length) {
return;
}
setRightDataSource([
...rightDataSource,
...leftSelectedRows,
])
setRightRowKeys([
...rightRowKeys,
...leftSelectedRowsKeys,
])
setLeftSelectedRows([]);
setLeftSelectedRowsKeys([]);
}
const handleDel = () => {
const nextRightDataSource = rightDataSource.filter(row => (!rightSelectedRowsKeys.includes(row[rowKey])));
const nextRightRowKeys = rightRowKeys.filter(rowkey => (!rightSelectedRowsKeys.includes(rowkey)));
setRightDataSource(nextRightDataSource);
setRightRowKeys(nextRightRowKeys)
setRightSelectedRowsKeys([]);
}
return (
<Modal
title={title || ''}
visible={visible}
onOk={handleOk}
onCancel={() => {
onCancel()
}}
width={800}
footer={[
<Button
onClick={onCancel}
key="close"
>取消</Button>,
<Button
type="primary"
key="confirm"
onClick={handleOk}
disabled={!rightDataSource.length}
>确定</Button>,
]}
>
<ProTable<API.StrategyItem, API.PageParams>
className={styles.QueryModalToCheckBox}
actionRef={actionRef}
rowKey={rowKey}
search={true}
request={async (params = {}, sort, filter) => {
const res = await request<API.RuleList>(queryApi, {
method: 'POST',
data: {
...(baseParams || {}),
...params,
},
});
if (isPagination) {
return Promise.resolve({
data: res.data.list,
total: res.data.total,
success: true,
});
}
return Promise.resolve({
data: res.data,
success: true,
});
}}
columns={columns}
toolBarRender={false}
rowSelection={{
selectedRowKeys: leftSelectedRowsKeys,
onChange: (_, selectedRows) => {
if ((selectedRows?.length + rightRowKeys?.length) > 5) {
message.warning('最多选择5位项目责任人!')
} else {
setLeftSelectedRowsKeys(_)
setLeftSelectedRows(selectedRows);
}
},
getCheckboxProps: (record: IAny) => {
return {
disabled: rightRowKeys.includes(record[rowKey]),
}
},
type: selectionType || 'radio'
}}
options={false}
scroll={{ y: 200, x: 200 }}
tableAlertRender={false}
tableRender={(_, dom) => {
return (
<div className="table-render-box">
<div className="table-container-left">
{dom}
</div>
<div className="table-container-action">
<div className='icon-box'>
<SwapRightOutlined
style={{
fontSize: '32px',
color: leftSelectedRowsKeys.length ? "#4a90e5" : "gray",
}}
onClick={handleAdd}
/>
<SwapLeftOutlined
style={{
fontSize: '32px',
transform: "rotateX(180deg)",
color: rightSelectedRowsKeys.length ? "#4a90e5" : "gray",
}}
onClick={handleDel}
/>
</div>
</div>
<div className="table-container-right">
<Table
rowKey={rowKey}
columns={columns}
dataSource={rightDataSource || []}
rowSelection={{
selectedRowKeys: rightSelectedRowsKeys,
onChange: (_, selectedRows) => {
setRightSelectedRowsKeys(_)
},
type: selectionType || 'radio'
}}
scroll={{ y: 200, x: 200 }}
/>
</div>
</div>
)
}}
/>
</Modal>
);
};
export default QueryModalToCheckBox;
index.less
:local(.QueryModalToCheckBox) {
:global {
overflow-x: hidden;
.table-render-box {
display: flex;
width: 100%;
}
.table-container-left, .table-container-right {
flex: 0 0 45%;
overflow-x: scroll;
}
.table-container-action {
flex: 0 0 10%;
position: relative;
.icon-box {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
}
}
.table-container-right {
.ant-table-thead .ant-table-cell {
padding-top: 12px;
padding-bottom: 12px;
}
}
}
}
总结:antdPro中表单和表格的简单使用,更多的是封装组件的思想,达到可配置化,可拓展化,由数据驱动页面拆分为配置化工程驱动页面。