使用react+antd实现类似于NC查询方案的查询组件。具备增删改存储查询方案及条件参数的功能
代码如下:
组件代码:
/components/SearchTree/index.ts
import React from 'react';
import { Card, Button, Col, Form, Input, Row, Tree, Modal, message, Popconfirm } from 'antd';
import { connect, Dispatch } from 'umi';
import { CloseCircleOutlined } from '@ant-design/icons';
import { ConnectState } from '@/models/connect';
import moment from 'moment';
interface SearchTreeType {
dispatch: Dispatch;
loading?: boolean;
searchForm: any; // 表单组件
fatherFormRef?: any; // ref元素(父元素)
onFinish?: (values: any) => void; // 提交表单函数
onDelete?: () => void; // 删除方案函数
onRef?: any; // 设置ref元素函数(本体元素)
functionId?: number; // 多页面方案时不同页面的ID
activeSelect?: number; // 当前选中方案id
storage?: string; // 会话存储默认选中方案
}
class SearchTree extends React.PureComponent<SearchTreeType> {
state = {
list: [{ id: -1, presetName: '方案' }], // 方案列表
selectedKeys: [], // 当前选中方案
nowScheme: [], // 方案中可选表单组件
nowSearch: [], // 当前选中方案的表单组件
modalOpen: false,
}
componentDidMount() {
// 组件挂载后设置ref并初始化调用方案接口
this.props.onRef && this.props.onRef(this);
this.setState({ nowScheme: this.props.searchForm });
this.fetchList(true);
}
// 获取数据
fetchList = (first: boolean = false, edit: boolean = false) => {
const { dispatch } = this.props;
const { list } = this.state;
const newList = JSON.parse(JSON.stringify(list));
// 调用后台接口获取方案列表
dispatch({
type: 'searchTree/getSearchTree',
payload: {
functionId: this.props.functionId,
},
callback: (res) => {
if (res.status === '1') {
newList[0].children = res.data;
this.setState({ list: newList }, () => {
// 存在查询方案时
if (res.data.length > 0) {
// 修改方案操作后
if (edit) {
const nowSearch: any = [];
// 获取当前方案的表单组件
this.props.searchForm.forEach((item: any) => {
JSON.parse(res.data.filter((item: any) => item.id == this.state.selectedKeys[0])[0].searchPreset).forEach((v: string) => {
if (item.props.name === v) {
nowSearch.push(item);
}
})
})
this.setState({ nowSearch }, () => {
// 获取当前方案的查询参数
const tempParams = JSON.parse(res.data.filter((item: any) => item.id == this.state.selectedKeys[0])[0].paramValue || '{}');
// 存在日期格式参数时需要进行处理,否则会报错
if (tempParams.deliveryDate) {
tempParams.deliveryDate = tempParams.deliveryDate.map((item: any) => (moment(item)));
}
this.props.fatherFormRef.current.resetFields();
this.props.fatherFormRef.current.setFieldsValue(tempParams);
})
} else {
// 非修改操作且存在当前选中方案时
if (this.props.activeSelect) {
this.setState({ selectedKeys: [this.props.activeSelect] }, () => {
const nowSearch: any = [];
this.props.searchForm.forEach((item: any) => {
JSON.parse(res.data.filter((item: any) => item.id === this.props.activeSelect)[0].searchPreset).forEach((v: string) => {
if (item.props.name === v) {
nowSearch.push(item);
}
})
})
this.setState({ nowSearch }, () => {
const tempParams = JSON.parse(res.data.filter((item: any) => item.id === this.props.activeSelect)[0].paramValue || '{}');
// 存在日期格式参数时需要进行处理,否则会报错
if (tempParams.deliveryDate) {
tempParams.deliveryDate = tempParams.deliveryDate.map((item: any) => (moment(item)));
}
this.props.fatherFormRef.current.resetFields();
this.props.fatherFormRef.current.setFieldsValue(tempParams);
})
})
} else {
// 非修改操作且不存在当前选中方案时的默认选中(全部/第一条/最后一条)
this.setState({ selectedKeys: [first ? (res.data.filter((item: any) => item.presetName === '全部').length > 0 ? res.data.filter((item: any) => item.presetName === '全部')[0].id : res.data[0].id) : res.data[res.data.length - 1].id] }, () => {
const nowSearch: any = [];
this.props.searchForm.forEach((item: any) => {
JSON.parse(first ? (res.data.filter((item: any) => item.presetName === '全部').length > 0 ? res.data.filter((item: any) => item.presetName === '全部')[0].searchPreset : res.data[0].searchPreset) : res.data[res.data.length - 1].searchPreset).forEach((v: string) => {
if (item.props.name === v) {
nowSearch.push(item);
}
})
})
this.setState({ nowSearch }, () => {
const tempParams = JSON.parse((first ? (res.data.filter((item: any) => item.presetName === '全部').length > 0 ? res.data.filter((item: any) => item.presetName === '全部')[0].paramValue : res.data[0].paramValue) : res.data[res.data.length - 1].paramValue) || '{}');
// 存在日期格式参数时需要进行处理,否则会报错
if (tempParams.deliveryDate) {
tempParams.deliveryDate = tempParams.deliveryDate.map((item: any) => (moment(item)));
}
this.props.fatherFormRef.current.resetFields();
this.props.fatherFormRef.current.setFieldsValue(tempParams);
})
})
}
}
} else {
// 不存在查询方案时默认选中方案
this.setState({ selectedKeys: [-1] }, () => {
this.props.fatherFormRef.current.resetFields();
})
}
});
}
}
})
}
// 设置方案(删除选中的表单组件)
handleSetScheme = (index: number) => {
const { nowScheme } = this.state;
this.setState({ nowScheme: nowScheme.filter((v: any, i: number) => (i !== index)) });
}
// 点击树节点
handleSelect = (selectedKeys: any, { node }: any) => {
const nowSearch: any = [];
if (selectedKeys.length === 0) {
selectedKeys = [-1]
this.setState({ nowScheme: this.props.searchForm })
} else {
if (selectedKeys[0] === -1) {
this.setState({ nowScheme: this.props.searchForm })
} else {
// 获取当前方案的表单组件
this.props.searchForm.forEach((item: any) => {
JSON.parse(node.searchPreset).forEach((v: string) => {
if (item.props.name === v) {
nowSearch.push(item);
}
})
})
}
}
this.setState({ selectedKeys, nowSearch }, () => {
if (selectedKeys[0] === -1) {
this.props.fatherFormRef.current.resetFields();
} else {
// 获取当前方案的查询参数
const tempParams = JSON.parse(node.paramValue || '{}');
// 存在日期格式参数时需要进行处理,否则会报错
if (tempParams.deliveryDate) {
tempParams.deliveryDate = tempParams.deliveryDate.map((item: any) => (moment(item)));
}
this.props.fatherFormRef.current.resetFields();
this.props.fatherFormRef.current.setFieldsValue(tempParams);
}
})
}
// 关闭model框
handleCancel = () => {
this.setState({ modalOpen: false }, () => {
this.saveRef.current.resetFields()
});
}
handleOk = () => {
this.saveRef.current.validateFields().then((values: any) => {
this.handleAdd(values);
});
}
// 新增方案
handleAdd = (values: any) => {
const { nowScheme } = this.state;
const searchList = nowScheme.map((item: any) => (item.props.name))
const { dispatch } = this.props
const searchParams = this.props.fatherFormRef.current.getFieldsValue()
// 调用新增接口
dispatch({
type: 'searchTree/addSearchTree',
payload: {
...values,
parentId: -1,
funtionId: this.props.functionId,
searchPreset: JSON.stringify(searchList),
paramValue: JSON.stringify(searchParams),
},
callback: (res) => {
if (res.status === '1') {
message.success('方案新增成功!', 1)
this.handleCancel()
this.fetchList()
} else {
message.error(res.detail ? `${res.detail}` : '未知错误,请联系管理员', 1)
}
}
})
}
// 删除方案
handleDelete = () => {
const { selectedKeys } = this.state
const { dispatch } = this.props
if (selectedKeys && selectedKeys.length > 0) {
// 调用删除接口
dispatch({
type: 'searchTree/delSearchTree',
payload: {
id: selectedKeys[0]
},
callback: (res) => {
if (res.status === '1') {
message.success('删除成功!', 1)
this.props.onDelete && this.props.onDelete()
// 删除会话存储中的方案
sessionStorage.removeItem(this.props.storage as string)
this.fetchList()
} else {
message.error(res.detail ? `${res.detail}` : '未知错误,请联系管理员', 1)
}
}
})
} else {
message.warning('请先选中要删除的方案', 1)
}
}
renderForm = () => {
return (
<Form ref={this.saveRef} onFinish={this.handleAdd}>
<Row gutter={16}>
<Col span={20}>
<Form.Item
name="presetName"
label="名称"
labelCol={{ span: 5 }}
rules={[{ required: true, message: '请输入方案名称' }]}
>
<Input placeholder="请输入方案名称" />
</Form.Item>
</Col>
</Row>
</Form>
)
}
// 修改方案参数
handleEdit = () => {
const { selectedKeys } = this.state
const { dispatch } = this.props
const searchParams = this.props.fatherFormRef.current.getFieldsValue()
if (selectedKeys && selectedKeys.length > 0) {
// 调用修改接口
dispatch({
type: 'searchTree/editSearchTree',
payload: {
id: selectedKeys[0],
paramValue: JSON.stringify(searchParams),
},
callback: (res) => {
if (res.status === '1') {
message.success('修改成功!', 1)
this.fetchList(false, true)
} else {
message.error(res.detail ? `${res.detail}` : '未知错误,请联系管理员', 1)
}
}
})
} else {
message.warning('请先选中要编辑的方案', 1)
}
}
render() {
const { loading, fatherFormRef, onFinish } = this.props;
const { list, nowScheme, nowSearch } = this.state;
return (
<>
<Row>
<Col span={6}>
<Card bordered style={{ height: '100%', overflow: 'hidden' }}>
{
this.state.selectedKeys[0] === -1 && (
<Button type="primary" style={{ marginBottom: '20px', marginRight: '10px' }} onClick={() => this.setState({ modalOpen: true })}>保存方案</Button>
)
}
{
this.state.selectedKeys.length > 0 && this.state.selectedKeys[0] !== -1 && (
<>
<Popconfirm title="确定要删除选中的查询方案吗?" onConfirm={this.handleDelete}>
<Button type="default" danger style={{ marginBottom: '20px', marginRight: '10px' }}>删除方案</Button>
</Popconfirm>
<Button type="primary" style={{ marginBottom: '20px' }} onClick={this.handleEdit}>保存修改</Button>
</>
)
}
{
list && list.length > 0 && (
<Tree
blockNode={true}
defaultExpandAll={true}
onSelect={this.handleSelect}
selectedKeys={this.state.selectedKeys}
fieldNames={{ key: 'id', title: 'presetName' }}
treeData={list}
/>
)
}
</Card>
</Col>
<Col span={18}>
<Card bordered>
{
this.state.selectedKeys[0] === -1 ? (
<Form ref={fatherFormRef} onFinish={onFinish}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{
nowScheme.length > 0 && nowScheme.map((item: any, index: number) => (
<Col span={12} key={index} style={{ display: 'flex' }}>
{
!item.props.rules &&
<CloseCircleOutlined style={{ margin: '0 5px 24px 0', cursor: 'pointer' }} onClick={() => this.handleSetScheme(index)} />
}
<div style={{ width: '100%' }}>
{item}
</div>
</Col>
))
}
</Row>
</Form>
) : (
<Form ref={fatherFormRef} onFinish={onFinish}>
<Row gutter={{ md: 8, lg: 24, xl: 48 }}>
{
nowSearch.length > 0 && nowSearch.map((item: any, index: number) => (
<Col span={12} key={index}>
{item}
</Col>
))
}
</Row>
</Form>
)
}
</Card>
</Col>
</Row>
<Modal
title="保存方案"
open={this.state.modalOpen}
style={{ top: 140 }}
onOk={this.handleOk}
onCancel={this.handleCancel}
width={400}
footer={[
<Button key="submit" type="primary" loading={loading} onClick={this.handleOk}>
确定
</Button>,
]}
>
{this.renderForm()}
</Modal>
</>
)
}
}
// 连接dva仓库
export default connect(({ searchTree, loading }: ConnectState) => ({
loading: loading.effects['searchTree/addSearchTree']
}))(SearchTree)
在页面中应用查询方案组件:
demo.tsx
import React from 'react';
import { connect, Dispatch } from 'umi';
import { Button, Card, DatePicker, Form, Input, message, Table, Select, Modal, Divider, TreeSelect } from 'antd';
import { ConnectState } from '@/models/connect';
import GoodsSelect from '@/components/Select/GoodsSelect'; // 自封装的表单组件
import UserSelect from '@/components/Select/UserSelect'; // 自封装的表单组件
import moment from 'moment';
import SearchTree from '@/components/SearchTree/index';
const { RangePicker } = DatePicker;
const { Option } = Select;
interface PropsType {
dispatch: Dispatch;
data: any;
loading?: boolean;
}
class Demo extends React.PureComponent<PropsType> {
state: any = {
formValues: {},
isModalOpen: false,
activeSelect: null,
};
componentDidMount() {
// 在会话存储中缓存选中方案
if (sessionStorage.getItem('searchTree1')) {
this.setState({ activeSelect: Number(sessionStorage.getItem('searchTree1')) })
}
}
// 打开查询Modal框
showModal = () => {
this.setState(
{
isModalOpen: true,
},
() => {
let temp = this.state.formValues;
if (temp.deliveryTimeLeft && temp.deliveryTimeRight) {
temp.deliverytime = [moment(temp.deliveryTimeLeft), moment(temp.deliveryTimeRight)];
}
this.formRef.current.setFieldsValue(temp);
// 设置查询方案组件中的可选表单组件
this.searchChild.setState({ nowScheme: this.searchForm() });
},
);
}
handleOk = () => {
const { selectedKeys } = this.searchChild.state;
if (selectedKeys.length === 0 || selectedKeys[0] === -1) {
message.error('请先选择方案!', 1);
return;
}
this.formRef.current.validateFields().then((values: any) => {
this.handleSearch(values);
this.setState({
isModalOpen: false,
});
});
};
handleSearch = (values: any) => {
if (values.deliverytime) {
values.deliveryTimeLeft = values.deliverytime[0].format('YYYY-MM-DD');
values.deliveryTimeRight = values.deliverytime[1].format('YYYY-MM-DD');
delete values.deliverytime;
}
this.setState(
{
formValues: {
...values,
}
},
() => {
if (this.searchChild.state) {
// 缓存当前搜索所使用的查询方案
sessionStorage.setItem('searchTree1', JSON.stringify(this.searchChild.state.selectedKeys[0]));
}
// 调用查询列表接口
this.fetchList(values);
},
);
};
// 重置
handleReset = () => {
this.formRef.current.resetFields();
this.setState({ formValues: {} })
this.fetchList();
};
// 可选表单组件。 注意:若不写成函数格式,直接写为数组形式可能会导致部分自封装的表单组件或异步函数组件无法使用
searchForm = () => [
<Form.Item name="deliverytime" label="送货时间">
<RangePicker style={{ width: '100%' }} />
</Form.Item>,
<Form.Item name="vbillcode" label="订单编号">
<Input placeholder="请输入订单编号" style={{ width: '100%' }} allowClear />
</Form.Item>,
<Form.Item name="employee" label="业务员">
<UserSelect width={'100%'} mode={'multiple'} />
</Form.Item>,
<Form.Item name="goodsData" label="商品">
<GoodsSelect width={'100%'} mode={'multiple'} codeValue labelGroup />
</Form.Item>,
<Form.Item name="ncBrand" label="商品品牌">
<Input width={'100%'} placeholder='请输入商品品牌' />
</Form.Item>,
<Form.Item name="orderStatus" label="单据状态">
<Select placeholder='请选择单据状态' mode="multiple" style={{ width: '100%' }} allowClear>
<Option value={1} key={1}>未审核</Option>
<Option value={2} key={2}>已审核</Option>
<Option value={4} key={4}>已发货</Option>
</Select>
</Form.Item>,
<Form.Item name="orderTypes" label="订单类型">
<Select placeholder='请选择订单类型' mode="multiple" style={{ width: '100%' }} allowClear>
<Option value={'普通订单'} key={1}>普通订单</Option>
<Option value={'全品类订单'} key={2}>全品类订单</Option>
<Option value={'直运订单'} key={3}>直运订单</Option>
</Select>
</Form.Item>,
]
renderForm = () => {
return (
<SearchTree
searchForm={this.searchForm()}
fatherFormRef={this.formRef}
onFinish={this.handleSearch}
onDelete={() => { this.setState({ activeSelect: null }) }}
formValues={this.state.formValues}
activeSelect={this.state.activeSelect}
functionId={1}
storage={'searchTree1'}
onRef={(ref: any) => {
this.searchChild = ref;
}}
/>
);
};
render() {
const { data, loading } = this.props;
return (
<>
<Card bordered={false}>
<div
style={{
padding: '15px',
display: 'flex',
alignItems: 'center',
backgroundColor: '#e1e1e1',
}}
>
<Button type="primary" onClick={this.showModal} htmlType="submit">
查询
</Button>
<Divider type="vertical" />
</div>
<div style={{ marginBottom: '20px', display: 'flex', alignItems: 'center' }}></div>
<Table
components={this.components}
columns={columns}
loading={loading}
rowKey={(record: any) => record.id}
dataSource={data}
bordered
pagination={false}
/>
</Card>
<Modal
title="查询"
open={this.state.isModalOpen}
onOk={this.handleOk}
onCancel={() => { this.setState({ isModalOpen: false }) }}
width={1200}
okText="查询"
footer={[
<Button key="reset" onClick={this.handleReset}>
重置
</Button>,
<Button key="cancel" onClick={() => { this.setState({ isModalOpen: false }) }}>
取消
</Button>,
<Button key="submit" type="primary" loading={loading} onClick={this.handleOk}>
查询
</Button>,
]}
>
{this.renderForm()}
</Modal>
</>
);
}
}
export default connect(({ demo, loading }: ConnectState) => ({
data: demo.data,
loading: loading.effects['demo/getSaleExecution'],
}))(Demo);
后台接口文档参考:
查询方案列表接口 'searchTree/getSearchTree':
传参:页面ID
回显数据类型:[{id:方案ID,paramValue:条件参数JSON串格式,parentId:-1,presetName:方案名称,serachPreset:方案表单组件JSON串格式,}]
新增方案接口 'searchTree/addSearchTree':
传参:方案名称、parentId:-1、页面ID、方案表单组件、条件参数
删除方案接口 'searchTree/delSearchTree':
传参:方案ID
修改方案的条件参数接口 'searchTree/editSearchTree':
传参:方案ID、条件参数