跳到主要内容

QueryForm

长念
长念阅读约 6 分钟1 年前发布1 年前编辑
危险

✂️ 当前组件已停止更新维护,如遇问题,需自行修复!

基于 Element React 的 Form 组件和 Choerodon UI 的 DataSet 封装的查询表单组件。

特性说明

  • 支持获取 DataSet 中的 queryFields queryDataSet 配置生成查询表单

何时使用

  • 一般情况在 QueryTable 组件中自动使用,当需要实现查询表单与表格分离时可使用该组件。

基本用法

import { QueryForm } from '@/components/element-pro';

export default () => {
return <QueryForm dataSet={dataSet} excludeFields={['batch']} className="form__planning" />;
};

API

除此以下附加属性外,支持 Table 所有属性。

参数类型
说明默认值
dataSetDataSet必输 绑定的 dataSet,用于获取 dataSet.queryDataSet-
autoQueryboolean可选 查询条件变化时,是否自动查询true
excludeFieldsstring[]可选 哪些字段定义在 queryFields 或者 queryDataSet 中但不需要渲染的字段[]
queryFormPropsOmit<FormProps, 'inline' | 'model'>可选 兼容 ELement React 表单属性设置,支持除 inlinemodel 外的属性-
queryFieldsPropsRecord<string, Record<any, any>>可选 兼容 ELement React 各组件属性设置-
cascadeMapRecord<string, string[]>可选 设置级联清除的字段,清空 A 字段时 清除 [B,C]-
afterQuery(params: any) => any可选 点击查询按钮之后的回调,参数中可以拿到所有的查询参数() => {}
afterReset() => any可选 点击重置按钮之后的回调-

源代码

QueryTable/QueryForm.tsx
import { parseDicData } from '@/utils';
import DataSet from 'choerodon-ui/dataset/data-set';
import { DataSetEvents, FieldType } from 'choerodon-ui/dataset/data-set/enum';
import Field from 'choerodon-ui/dataset/data-set/Field';
import { ButtonColor } from 'choerodon-ui/pro/lib/button/enum';
import cls from 'classnames';
import { Button, Form, Input } from 'element-react';
import { debounce, isFunction, isNil, omitBy } from 'lodash-es';
import { toJS } from 'mobx';
import { observer } from 'mobx-react';
import React, { useState } from 'react';
import CascaderPro from '../CascaderPro';
import DatePickerPro from '../DatePickerPro';
import { FormProps } from '../FormPro';
import NumberProRange, { NumberProRangeProps } from '../NumberProRange';
import SelectPro from '../SelectPro';
import styles from './index.module.less';

type FieldKeyType = string | number | symbol | any;

/**
* @see https://changnian.netlify.app/docs/c7n/custom-components/element-react/query_form
*/
export interface QueryFormProps {
/**
* @description Table DataSet
*/
dataSet: DataSet;
/**
* @description 查询表单属性,兼容 FormProps
*/
queryFormProps?: Omit<FormProps, 'inline' | 'model'>;
/**
* @description 自定义传入组件属性,实现自定义
*/
queryFieldsProps?: Record<string, Record<any, any>>;
/**
* @description 点击查询按钮之后的回调
* @default ()=>{}
*/
afterQuery?: (params: any) => any;
/**
* @description 点击查询重置之后的回调
* @default ()=>{}
*/
afterReset?: () => any;
/**
* @description 排除的查询字段 - 不会被渲染
* @default []
*/
excludeFields?: string[];
/**
* @description 级联清除字段映射 - 清除 key 时同时清除级联字段
* @default {}
*/
cascadeMap?: Record<string, string[]>;
/**
* @description 切换查询条件时是否自动查询
* @default true
*/
autoQuery?: boolean;
/**
* @description 清空查询条件时是否自动查询
* @default true
*/
autoQueryAfterReset?: boolean;

className?: string;
}

const DATE_TYPE_MAPPING = {
[FieldType.year]: {
selectMode: 'year',
meaning: '年',
},
[FieldType.month]: {
selectMode: 'month',
meaning: '月',
},
[FieldType.week]: {
selectMode: 'week',
meaning: '周',
},
} as const;

const QueryForm: React.FC<QueryFormProps> = ({
dataSet,
className = '',
excludeFields = [],
afterQuery,
afterReset,
queryFormProps = {},
queryFieldsProps = {},
cascadeMap = {},
autoQuery = true,
autoQueryAfterReset = true,
}) => {
const { queryDataSet } = dataSet;

const defaultValues = React.useMemo(() => {
const _defaultValues = {};
// 使用 data 能够获取到 DataSet 解析之后的值,可以支持 transformResponse 等的数据转换
Object.entries(toJS(queryDataSet?.current?.data) || {})
.filter(([, v]) => !isNil(v))
.forEach(([k, v]) => {
_defaultValues[k] = v;
// NOTE: 已对 DatePicker 重新封装,支持 moment dayjs 等,不需要再做类型转换
// 日期特殊处理
// if (v instanceof moment) {
// console.log('moment', v, new Date(moment(v).valueOf()));
// _defaultValues[k] = new Date(moment(v).valueOf());
// } else {
// _defaultValues[k] = v;
// }
});
return _defaultValues;
}, [queryDataSet?.current?.data]); // for once

const cascadeMapping = React.useMemo(() => Object.entries(cascadeMap), [cascadeMap]);

const [isExpand, setIsExpand] = useState<boolean>(false);
const [model, setModel] = React.useState<any>({ ...defaultValues });

const debouncedQuery = React.useCallback(
debounce(() => dataSet.query(), 500, { leading: false }),
[dataSet]
);

const queryFields: Array<[FieldKeyType, Field]> = React.useMemo(() => {
// dataSet.queryFields 一定会生成 dataSet.queryDataSet,此处仅判断 queryDataSet
const fieldsMap: Array<[FieldKeyType, Field]> = Array.from(queryDataSet?.fields ?? []).filter(
([k]) => !excludeFields.includes(k)
);
return fieldsMap;
}, [queryDataSet?.fields, excludeFields?.length]);

// 配置中的默认值
const configDefaultValues = React.useMemo(() => {
const cfgDefaultValues = {};
queryFields.forEach(([k, field]) => {
const { defaultValue } = field?.pristineProps || {};
if (defaultValue) {
cfgDefaultValues[k] = defaultValue;
}
});
return cfgDefaultValues;
}, [queryFields?.length]);

const handleInputChange = (key: string, value: any) => {
setModel((prevModel: any) => ({
...prevModel,
[key]: value,
}));
queryDataSet?.current?.set(`${key}`, value);
if (autoQuery) {
debouncedQuery();
}
};

const handleNumberRangeChange = (key: string, value: NumberProRangeProps['value']) => {
setModel((v) => ({ ...v, [key]: value }));

if (isNil(value?.[0]) || isNil(value?.[1])) {
return;
}
if (autoQuery) {
debouncedQuery();
}
};

const handleQuery = debounce(() => {
Object.keys(model).forEach((key) => {
queryDataSet?.current?.set(`${key}`, model[key]);
});
dataSet.query();
if (afterQuery && isFunction(afterQuery)) {
const queryData = omitBy(queryDataSet?.current?.toJSONData() || {}, (_, k) =>
k?.startsWith('_')
);

afterQuery({ ...queryData, ...dataSet.queryParameter });
}
}, 500);

/**
* @description 重置 - 逻辑为重置到默认值
*/
const handleReset = debounce(() => {
const resetValue = {};
const forceResetValue = {};
Object.entries(model).forEach(([k]) => {
resetValue[k] = null;
const { type, range } = dataSet.queryDataSet?.getField(k)?.pristineProps || {};
if (range && type === FieldType.number) {
forceResetValue[k] = null;
}
});
// 保留注释 for debugger
console.log('[reset value]', { ...resetValue, ...configDefaultValues, ...forceResetValue });
setModel({ ...resetValue, ...configDefaultValues, ...forceResetValue });

queryDataSet?.current?.set({
...resetValue,
...configDefaultValues,
...forceResetValue,
});

if (autoQueryAfterReset) {
dataSet.query();
}
if (afterReset && isFunction(afterReset)) {
afterReset();
}
}, 500);

React.useEffect(() => {
const handleCascadeChange = ({ name, record, dataSet }) => {
for (const [key, keys] of cascadeMapping) {
if (name === key) {
const obj = {};
keys.forEach((k) => Object.assign(obj, { [k]: undefined }));
record.set({ ...obj });
setModel((m) => ({ ...m, ...obj }));
return;
}
}
};

queryDataSet?.addEventListener(DataSetEvents.update, handleCascadeChange);

return () => {
queryDataSet?.removeEventListener(DataSetEvents.update, handleCascadeChange);
};
}, [queryDataSet, cascadeMapping]);

const renderFormItem = (key: string, field: Field, index: number) => {
// field 仅有 name type 2个可用属性,且 type 为解析过的属性
// const { name, type } = field;

if (index >= 4 && !isExpand) {
return null;
}

const {
name,
type = FieldType.string,
placeholder: _placeholder, // element 不支持 string[]
lookupCode,
lookupUrl,
textField,
valueField,
options,
multiple, // element 不支持分隔符形式
label,
labelWidth,
range = false,
} = field.pristineProps || {};

const fieldProps = name ? queryFieldsProps[name] : undefined;

const placeholder = typeof _placeholder === 'string' ? _placeholder : _placeholder?.join(', ');
if (type === FieldType.number && range) {
return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
>
<NumberProRange
name={name}
value={model?.[key]}
dataSet={queryDataSet}
range={range}
placeholder={placeholder || '请输入'}
{...fieldProps}
onChange={(val) => handleNumberRangeChange(key, val)}
/>
</Form.Item>
);
}
if (type === FieldType.date) {
return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
style={{}}
>
<DatePickerPro
value={model?.[key]}
placeholder={`${placeholder || '请选择日期'}`}
onChange={(value) => handleInputChange(key, value)}
isRange={!!range}
{...fieldProps}
/>
</Form.Item>
);
}
// 年月周
if ([FieldType.year, FieldType.month, FieldType.week].includes(type)) {
const { selectMode, meaning } = DATE_TYPE_MAPPING[type];
return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
style={{}}
>
<DatePickerPro
placeholder={`${placeholder || `请选择${meaning}`}`}
selectionMode={selectMode}
align="right"
value={model?.[key]}
onChange={(value) => handleInputChange(key, value)}
isRange={!!range}
{...fieldProps}
/>
</Form.Item>
);
}

if (type === FieldType.string) {
const lookupOptions = queryDataSet?.getField(key)?.options?.toData();
if (lookupCode || lookupUrl || options || lookupOptions) {
if (typeof options === 'string') {
return null;
}
if (fieldProps?.isTree && lookupOptions) {
const parsedOptions = parseDicData(lookupOptions, {
labelField: textField!,
valueField: valueField!,
childrenField: 'childList',
});
return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
style={{}}
>
<CascaderPro
value={model?.[key] ?? []}
options={parsedOptions}
placeholder={`${placeholder || '请选择'}`}
onChange={(value) => handleInputChange(key, value)}
clearable
{...fieldProps}
// element select 不支持分隔符
/>
</Form.Item>
);
}
return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
style={{}}
>
<SelectPro
name={name}
value={model?.[key] || ''}
dataSet={queryDataSet}
options={options}
valueField={valueField}
textField={textField}
placeholder={`${placeholder || '请选择'}`}
onChange={(value) => handleInputChange(key, value)}
multiple={!!multiple}
clearable
{...fieldProps}
// element select 不支持分隔符
/>
</Form.Item>
);
}
}

return (
<Form.Item
key={name}
label={`${label}:`}
className={styles.form__item}
labelWidth={labelWidth}
>
<Input
value={model?.[key]}
placeholder={`${placeholder || '请输入'}`}
onChange={(value) => handleInputChange(key, value)}
icon={
model?.[key] ? (
<i
className="el-icon-circle-close el-input__icon"
style={{ cursor: 'pointer' }}
onClick={() => handleInputChange(key, null)}
/>
) : (
''
)
}
{...fieldProps}
/>
</Form.Item>
);
};

if (!queryFields.length) {
return null;
}

return (
<Form className={cls(styles.form__query, className)} model={model} {...queryFormProps} inline>
{queryFields.map(([key, field], index: number) => renderFormItem(key, field, index))}
<div className={cls(styles.form__item, styles.buttons__query)} style={{ float: 'right' }}>
<Button type={ButtonColor.primary} onClick={() => handleQuery()}>
查询
</Button>
<Button className={styles.button__reset} onClick={() => handleReset()}>
重置
</Button>
{/* 目前仅支持 5 列展示 */}
{queryFields.length > 4 && (
<Button type="text" onClick={() => setIsExpand(!isExpand)} style={{ marginLeft: 10 }}>
{isExpand ? '收起 >' : '更多'}
</Button>
)}
</div>
</Form>
);
};
export default observer(QueryForm);

更新日志

alpha

2024-12-12

  • 🐞 修复查询表单默认值缓存后无法清除问题;

2024-11-14

  • 🍋 优化查询表单支持重置后自动查询;
  • 🍋 查询条件支持清除;

2024-11-04

  • 🎉 新增 autoQuery 属性支持自动查询;
  • 🎉 新增 CascaderPro 树形查询组件支持;
  • 🎉 新增 NumberRangePro 数字范围查询组件支持;
  • 🍋 新增查询防抖优化查询;

2024-10-09

  • 🎉 新增 cascadeMap 属性支持级联清除;
  • 🎉 新增 queryFieldsProps 属性支持传入组件属性;

2024-09-14

  • 🎉 日期组件替换为 DatePickerPro
  • 🐞 修复默认值取值逻辑;

2024-08-05

  • 🎉 新增 QueryForm 组件;