fix: 优化自定义筛选组件和逻辑 (#486)
<!-- Note --> <!-- This is a template for submitting a new feature. Use the bug fix template if you're submitting a bug fix pull request by adding `template=bug_fix.md` to your pull request URL. --> # Description <!-- Describe the new feature or modification to an existing feature clearly and consciously. --> # Motivation <!-- Explain the reason for adding or modifying this feature. --> # Key changes <!-- Provide a technically detailed description of the key changes made. --> - Frontend - Backend # Test plan ## Suggestions <!-- Provide any suggestions or recommendations for improvements in the testing plan. --> ## Underlying risk <!-- Identify any potential risks or issues that may arise from the new feature or modification. --> # Showcase <!-- Including any screenshots of the new feature or modification. --> Co-authored-by: sealday <sealday@gmail.com> Co-authored-by: bai.jingfeng <bai.jingfeng@foxmail.com> Reviewed-on: daoyoucloud/tachycode#486 Co-authored-by: wjh <wwwjh0710@163.com> Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
parent
610fee7035
commit
aa2542b706
@ -9,89 +9,45 @@ import {
|
||||
useFilterBlock,
|
||||
} from '@nocobase/client';
|
||||
import flat from 'flat';
|
||||
import { hasDuplicateKeys } from '../utils';
|
||||
|
||||
export const removeNullCondition = (filter, fieldSchema?) => {
|
||||
const filterSchema = fieldSchema ? fieldSchema['x-filter-rules'] : '';
|
||||
const filterSchemaItem = flat(filterSchema || '');
|
||||
const filterItem = {};
|
||||
const items = flat(filter || {});
|
||||
const values = {};
|
||||
let isFilterCustom = false;
|
||||
if (filterSchema && (filterSchema.$and?.length || filterSchema.$or?.length)) {
|
||||
for (const key in items) {
|
||||
for (const filterItems in filterSchemaItem) {
|
||||
if (key.includes(filterItems)) {
|
||||
isFilterCustom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const isCustomFilter = filterSchema?.$and?.length || filterSchema?.$or?.length;
|
||||
const isFilterCustom = isCustomFilter ? hasDuplicateKeys(items, filterSchemaItem) : false;
|
||||
if (!isFilterCustom) {
|
||||
if (Object.keys(items).filter((item) => item.includes('custom')).length) {
|
||||
if (filterSchema && (filterSchema.$and?.length || filterSchema.$or?.length)) {
|
||||
for (const filterKey in filterSchemaItem) {
|
||||
const match = filterSchemaItem[filterKey]?.slice(11, -2);
|
||||
const collection = match?.split('.')[0];
|
||||
if (Object.keys(items).filter((item) => item.includes(collection)).length) {
|
||||
for (const key in items) {
|
||||
if (key.includes('custom')) {
|
||||
if (key.includes(collection)) {
|
||||
if (key.includes(match)) {
|
||||
if (Object.keys(items[key]).length) {
|
||||
filterSchemaItem[filterKey] = items[key];
|
||||
filterItem[match] = items[key];
|
||||
}
|
||||
}
|
||||
if (key.includes(collection)) delete items[key];
|
||||
else {
|
||||
const value = items[key];
|
||||
if (value != null && !isEmpty(value)) {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
values[key] = items[key];
|
||||
}
|
||||
}
|
||||
} else if (Object.keys(items).filter((item) => !item.includes('custom')).length) {
|
||||
const filterItem = Object.keys(items).filter((item) => !item.includes('custom'));
|
||||
filterItem.forEach((key) => {
|
||||
values[key] = items[key];
|
||||
});
|
||||
}
|
||||
if (isCustomFilter) {
|
||||
for (const filterKey in filterSchemaItem) {
|
||||
const match = filterSchemaItem[filterKey]?.slice(11, -2);
|
||||
const collection = match?.split('.')[0];
|
||||
const filterItems = Object.keys(items).filter((item) => item.includes(collection))[0];
|
||||
if (filterItems) {
|
||||
filterSchemaItem[filterKey] = items[filterItems];
|
||||
}
|
||||
for (const item in filterSchemaItem) {
|
||||
for (const key in filterItem) {
|
||||
if (filterSchemaItem[item].includes(key)) {
|
||||
filterSchemaItem[item] = filterItem[key];
|
||||
}
|
||||
}
|
||||
if (filterSchemaItem[item].includes('$nFilter')) {
|
||||
delete filterSchemaItem[item];
|
||||
}
|
||||
}
|
||||
const flatValue = flat.unflatten(values);
|
||||
const flatFieldSchema = flat.unflatten(filterSchemaItem);
|
||||
return {
|
||||
$and: [flatValue, flatFieldSchema],
|
||||
};
|
||||
} else {
|
||||
for (const key in items) {
|
||||
if (!key.includes('custom')) {
|
||||
const value = items[key];
|
||||
if (value != null && !isEmpty(value)) {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return flat.unflatten(values);
|
||||
}
|
||||
for (const item in items) {
|
||||
if (!item.includes('custom')) {
|
||||
values[item] = items[item];
|
||||
}
|
||||
}
|
||||
for (const item in filterSchemaItem) {
|
||||
if (filterSchemaItem[item].includes('$nFilter')) {
|
||||
delete filterSchemaItem[item];
|
||||
}
|
||||
}
|
||||
const flatValue = flat.unflatten(values);
|
||||
const flatFieldSchema = flat.unflatten(filterSchemaItem);
|
||||
flatValue['$and'] = flatValue['$and']?.filter(Boolean);
|
||||
return {
|
||||
$and: [flatValue, flatFieldSchema],
|
||||
};
|
||||
} else {
|
||||
for (const key in items) {
|
||||
const value = items[key];
|
||||
if (value != null && !isEmpty(value)) {
|
||||
if (!key.includes('custom') && value != null && !isEmpty(value)) {
|
||||
values[key] = value;
|
||||
}
|
||||
}
|
||||
@ -123,13 +79,11 @@ export const useFilterBlockActionProps = () => {
|
||||
getDataBlocks().map(async (block) => {
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
if (!target) return;
|
||||
|
||||
const param = block.service.params?.[0] || {};
|
||||
for (const key in form.values) {
|
||||
if (
|
||||
(typeof form.values[key] === 'object' &&
|
||||
!form.values[key]?.length &&
|
||||
!Object.keys(form.values[key]).length) ||
|
||||
(JSON.stringify(form.values[key]) === '{}' || JSON.stringify(form.values[key]) === '[]')) ||
|
||||
!form.values[key]
|
||||
) {
|
||||
delete form.values[key];
|
||||
|
@ -5,6 +5,7 @@ import { useCollectionManager, useRequest } from '@nocobase/client';
|
||||
import _ from 'lodash';
|
||||
|
||||
const AssociationCascader = connect((props) => {
|
||||
const { fieldNames } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = fieldSchema['collectionName'];
|
||||
const associationField = props.associationField;
|
||||
@ -36,8 +37,8 @@ const AssociationCascader = connect((props) => {
|
||||
return acc;
|
||||
}, {}) || {};
|
||||
const options = Object.entries(dict).map(([key, values]) => ({
|
||||
name: key,
|
||||
id: key,
|
||||
[fieldNames.label]: key,
|
||||
[fieldNames.value]: key,
|
||||
children: values.map((value) => ({ name: value, id: value })),
|
||||
}));
|
||||
return options;
|
||||
|
@ -1,25 +1,17 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { AutoComplete as AntdAutoComplete } from 'antd';
|
||||
import { useFieldSchema, useForm } from '@formily/react';
|
||||
import { connect, useFieldSchema, useForm } from '@formily/react';
|
||||
import { useAPIClient, useDesigner, useRequest } from '@nocobase/client';
|
||||
import { useAsyncEffect } from 'ahooks';
|
||||
import { fuzzysearch } from '../../utils';
|
||||
|
||||
export const AutoComplete = (props) => {
|
||||
export const AutoComplete = connect((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const comdef = fieldSchema['default'];
|
||||
const { fieldNames } = fieldSchema['x-component-props'];
|
||||
const fieldFilter = fieldSchema['x-component-props']['params'];
|
||||
const fieldNames = props.fieldNames || fieldSchema['x-component-props'].fieldNames;
|
||||
const fieldFilter = props.params;
|
||||
const [defultValue, setDefultValue] = useState([]);
|
||||
const api = useAPIClient();
|
||||
const [options, setOptions] = useState([]);
|
||||
const form = useForm();
|
||||
const autoValue = form.values['custom'] ? form.values['custom'][fieldSchema['collectionName']] : '';
|
||||
const [value, setValue] = useState(autoValue);
|
||||
if (!autoValue && value) {
|
||||
setValue('');
|
||||
setOptions(defultValue);
|
||||
}
|
||||
|
||||
useAsyncEffect(async () => {
|
||||
const defultOptions = await api.request({
|
||||
url: fieldSchema['collectionName'] + ':list',
|
||||
@ -33,55 +25,28 @@ export const AutoComplete = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
changLable(defultValue);
|
||||
}, [fieldNames.label]);
|
||||
}, [fieldNames?.label]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(comdef);
|
||||
}, [comdef]);
|
||||
const changLable = (defultOptions) => {
|
||||
if (defultOptions) {
|
||||
const item = defultOptions.map((item) => {
|
||||
item['label'] = item[fieldNames.label];
|
||||
item['value'] = item[fieldNames.value];
|
||||
return item;
|
||||
const items = [];
|
||||
defultOptions.forEach((item) => {
|
||||
if (!items.filter((option) => option[fieldNames.value] === item[fieldNames.value]).length) {
|
||||
items.push(item);
|
||||
}
|
||||
});
|
||||
setDefultValue(item);
|
||||
setOptions(item);
|
||||
}
|
||||
};
|
||||
if (!form.values['custom']) {
|
||||
form.values['custom'] = {};
|
||||
}
|
||||
const onSearch = (data) => {
|
||||
if (data) {
|
||||
const searchValue = defultValue.filter((item) => item.label.includes(data));
|
||||
if (searchValue.length) {
|
||||
setOptions(searchValue);
|
||||
} else {
|
||||
setOptions([]);
|
||||
}
|
||||
const valueLabel = options.filter((item) => item.value === data)[0];
|
||||
if (valueLabel) {
|
||||
form.values.custom[fieldSchema['collectionName']] = valueLabel.label;
|
||||
setValue(valueLabel.label);
|
||||
} else {
|
||||
form.values.custom[fieldSchema['collectionName']] = data;
|
||||
setValue(data);
|
||||
}
|
||||
} else {
|
||||
form.values.custom[fieldSchema['collectionName']] = {};
|
||||
setValue(data);
|
||||
setOptions(defultValue);
|
||||
setDefultValue(items);
|
||||
setOptions(items);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<AntdAutoComplete
|
||||
value={value}
|
||||
defaultValue={comdef}
|
||||
{...props}
|
||||
options={options}
|
||||
onSearch={onSearch}
|
||||
onChange={onSearch}
|
||||
filterOption={(inputValue, option) => fuzzysearch(inputValue, option[fieldNames.value].toString())}
|
||||
allowClear
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
AutoComplete.displayName = 'AutoComplete';
|
||||
export default AutoComplete;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CloseCircleFilled, CloseOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import { connect, mapProps, mapReadPretty, useFieldSchema } from '@formily/react';
|
||||
import { connect, mapProps, mapReadPretty, useFieldSchema, useForm } from '@formily/react';
|
||||
import { isValid, toArr } from '@formily/shared';
|
||||
import { isPlainObject } from '@nocobase/utils/client';
|
||||
import type { SelectProps } from 'antd';
|
||||
@ -24,11 +24,10 @@ const ObjectSelect = (props: Props) => {
|
||||
const { value, options, onChange, fieldNames, mode, loading, rawOptions, defaultValue, ...others } = props;
|
||||
const [defoptions, setDefOptions] = useState();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const comDefult = fieldSchema.default;
|
||||
const collectionName = fieldSchema['collectionName'];
|
||||
const filterField = fieldSchema['x-component-props']['params'];
|
||||
const fieldName = fieldSchema['x-component-props'].fieldNames;
|
||||
const filterField = others?.['params'];
|
||||
const api = useAPIClient();
|
||||
const form = useForm();
|
||||
useAsyncEffect(async () => {
|
||||
if (collectionName) {
|
||||
const defValue = await api.request({
|
||||
@ -39,16 +38,13 @@ const ObjectSelect = (props: Props) => {
|
||||
},
|
||||
});
|
||||
const changOptions = defValue?.data?.data.map((value) => {
|
||||
value['label'] = value[fieldName.label];
|
||||
value['value'] = value[fieldNames.label];
|
||||
value['label'] = value[fieldNames.label];
|
||||
value['value'] = value[fieldNames.value];
|
||||
return value;
|
||||
});
|
||||
setDefOptions(changOptions);
|
||||
}
|
||||
}, [filterField?.filter, collectionName]);
|
||||
useEffect(() => {
|
||||
onChange?.(comDefult);
|
||||
}, [fieldSchema.default]);
|
||||
const toValue = (v: any) => {
|
||||
if (isEmptyObject(v)) {
|
||||
return;
|
||||
@ -58,15 +54,12 @@ const ObjectSelect = (props: Props) => {
|
||||
.map((val) => {
|
||||
return isPlainObject(val) ? val[fieldNames.value] : val;
|
||||
});
|
||||
const currentOptions = getCurrentOptions(values, options, fieldNames)?.map((val) => {
|
||||
if (collectionName) {
|
||||
return val[fieldName.label];
|
||||
} else {
|
||||
return {
|
||||
label: val[fieldNames.label],
|
||||
value: val[fieldNames.value],
|
||||
};
|
||||
}
|
||||
const filterOption = defoptions ? defoptions : options;
|
||||
const currentOptions = getCurrentOptions(values, filterOption, fieldNames)?.map((val) => {
|
||||
return {
|
||||
label: val[fieldNames.label],
|
||||
value: val[fieldNames.value],
|
||||
};
|
||||
});
|
||||
if (['tags', 'multiple'].includes(mode) || props.multiple) {
|
||||
return currentOptions;
|
||||
@ -83,7 +76,7 @@ const ObjectSelect = (props: Props) => {
|
||||
role="button"
|
||||
data-testid={`select-object-${mode || 'single'}`}
|
||||
value={toValue(value)}
|
||||
defaultValue={toValue(defaultValue) || comDefult}
|
||||
defaultValue={toValue(defaultValue)}
|
||||
allowClear={{
|
||||
clearIcon: <CloseCircleFilled role="button" aria-label="icon-close-select" />,
|
||||
}}
|
||||
@ -106,17 +99,17 @@ const ObjectSelect = (props: Props) => {
|
||||
onChange={(changed) => {
|
||||
const current = getCurrentOptions(
|
||||
toArr(changed).map((v) => v.value),
|
||||
rawOptions || options,
|
||||
defoptions || rawOptions || options,
|
||||
fieldNames,
|
||||
);
|
||||
if (collectionName) {
|
||||
onChange?.(changed['label']);
|
||||
if (['tags', 'multiple'].includes(mode as string) || props.multiple) {
|
||||
onChange?.(current);
|
||||
} else {
|
||||
if (['tags', 'multiple'].includes(mode as string) || props.multiple) {
|
||||
onChange?.(current);
|
||||
} else {
|
||||
onChange?.(current.shift() || null);
|
||||
}
|
||||
onChange?.(current.shift() || null);
|
||||
}
|
||||
if (collectionName) {
|
||||
const name = fieldSchema['name'].toString().split('.')[1];
|
||||
form.values['custom'][name] = changed?.['value'];
|
||||
}
|
||||
}}
|
||||
mode={mode}
|
||||
|
@ -41,7 +41,6 @@ import {
|
||||
import _ from 'lodash';
|
||||
import { SchemaSettingsRemove } from '../../schema-settings/SchemaSettingsRemove';
|
||||
import { tval, useTranslation } from '../../locale';
|
||||
import { ContractsController } from 'packages/plugins/@hera/plugin-rental/dist/server/actions';
|
||||
|
||||
const FieldComponentProps: React.FC = observer(
|
||||
(props) => {
|
||||
@ -296,7 +295,7 @@ export const FilterCustomItemInitializer: React.FC<{
|
||||
...(defaultSchema['x-component-props'] || {}),
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
value: 'name',
|
||||
},
|
||||
associationField,
|
||||
},
|
||||
|
@ -43,13 +43,7 @@ export const SchemaSettingsRemove: FC<SchemaSettingsRemoveProps> = (props) => {
|
||||
}
|
||||
await dn.remove(null, options);
|
||||
await confirm?.onOk?.();
|
||||
if (fieldSchema['collectionName']) {
|
||||
delete form.values['custom'][fieldSchema['collectionName']];
|
||||
} else {
|
||||
if (form.values['custom']?.[fieldName]) {
|
||||
delete form.values['custom'][fieldName];
|
||||
}
|
||||
}
|
||||
delete form.values['custom'][fieldName];
|
||||
for (const key in form.fields) {
|
||||
if (key.includes(name) && form.fields[key].title === title) {
|
||||
delete form.fields[key];
|
||||
|
@ -176,19 +176,15 @@ export const EditDefaultValue = () => {
|
||||
},
|
||||
},
|
||||
}}
|
||||
onSubmit={(defaultValue) => {
|
||||
let value;
|
||||
if (defaultValue.default && !defaultValue.custom) {
|
||||
value = defaultValue.default.value?.value ? defaultValue.default.value.value : defaultValue.default.value;
|
||||
} else if (defaultValue.custom) {
|
||||
value = defaultValue.custom[collectionName];
|
||||
}
|
||||
field.setInitialValue(value);
|
||||
onSubmit={({ default: { value } }) => {
|
||||
field.setValue(value);
|
||||
fieldSchema['default'] = value;
|
||||
fieldSchema['x-component-props']['defaultValue'] = value;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
'x-uid': fieldSchema['x-uid'],
|
||||
default: value,
|
||||
['x-component-props']: fieldSchema['x-component-props'],
|
||||
default: fieldSchema.default,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
@ -343,6 +339,7 @@ export const EditTitleField = () => {
|
||||
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||
...field.componentProps.fieldNames,
|
||||
label,
|
||||
value: label,
|
||||
};
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||
@ -507,14 +504,11 @@ export const SchemaSettingComponent = () => {
|
||||
onChange={(mode) => {
|
||||
field.component = mode;
|
||||
fieldSchema['x-component'] = mode;
|
||||
fieldSchema.default = '';
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component']: mode,
|
||||
default: '',
|
||||
};
|
||||
void dn.emit('patch', {
|
||||
schema,
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
['x-component']: fieldSchema['x-component'],
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
@ -524,8 +518,8 @@ export const SchemaSettingComponent = () => {
|
||||
|
||||
export const SchemaSettingCollection = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
const collections = useCollectionManager();
|
||||
const field = useField<Field>();
|
||||
const options = collections?.dataSource['options']?.collections.map((value) => {
|
||||
return {
|
||||
label: value.title,
|
||||
@ -540,16 +534,25 @@ export const SchemaSettingCollection = () => {
|
||||
options={options}
|
||||
value={fieldSchema['collectionName']}
|
||||
onChange={(name) => {
|
||||
field.setValue('');
|
||||
fieldSchema['collectionName'] = name;
|
||||
fieldSchema['name'] = 'custom.' + name;
|
||||
fieldSchema.default = '';
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
collectionName: name,
|
||||
name: 'custom.' + name,
|
||||
default: '',
|
||||
fieldSchema['x-component-props'] = {
|
||||
...fieldSchema['x-component-props'],
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'name',
|
||||
},
|
||||
defaultValue: '',
|
||||
};
|
||||
void dn.emit('patch', { schema });
|
||||
void dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
collectionName: fieldSchema['collectionName'],
|
||||
['x-component-props']: fieldSchema['x-component-props'],
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
|
33
packages/plugins/@hera/plugin-core/src/client/utils.ts
Normal file
33
packages/plugins/@hera/plugin-core/src/client/utils.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export function fuzzysearch(needle: string, haystack: string): boolean {
|
||||
const hlen = haystack.length;
|
||||
const nlen = needle.length;
|
||||
if (nlen > hlen) {
|
||||
return false;
|
||||
}
|
||||
if (nlen === hlen) {
|
||||
return needle === haystack;
|
||||
}
|
||||
outer: for (let i = 0, j = 0; i < nlen; i++) {
|
||||
const nch = needle.charCodeAt(i);
|
||||
while (j < hlen) {
|
||||
if (haystack.charCodeAt(j++) === nch) {
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 数组去重
|
||||
const getArrayOfNoDuplicateValue = (arr: Array<string | number>): Array<string | number> => {
|
||||
return [...new Set(arr)];
|
||||
};
|
||||
|
||||
// 判断两个对象是否包含有相同的key
|
||||
export function hasDuplicateKeys(A: Object, B: Object): boolean {
|
||||
const keysA = getArrayOfNoDuplicateValue(Object.keys(A));
|
||||
const keysB = getArrayOfNoDuplicateValue(Object.keys(B));
|
||||
const keysA_B = getArrayOfNoDuplicateValue([...keysA, ...keysB]);
|
||||
return keysA_B.length < keysA.length + keysB.length;
|
||||
}
|
@ -235,7 +235,7 @@ SELECT
|
||||
records r
|
||||
WHERE
|
||||
r.contract_id = COALESCE(main.id, c.id)
|
||||
AND s.end_date + INTERVAL '1 day' >= r.date
|
||||
AND s.end_date >= r.date
|
||||
) AS records,
|
||||
(
|
||||
SELECT
|
||||
|
Loading…
Reference in New Issue
Block a user