refractor(remote-select): association field data scope merge with original filter conditions (#2118)

* fix:  deleting the last field from  sub table, the entire table will be deleted

* refactor: association select support data scope in sub-table

* refactor: locale improve

* style: sub-table style improve

* refactor: code improve

* chore: association field data scope

* refactor: code improve

* refactor: code improve
This commit is contained in:
katherinehhh 2023-08-04 18:07:58 +08:00 committed by GitHub
parent 53a6a6c621
commit 457082ffa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 110 additions and 107 deletions

View File

@ -1,13 +1,18 @@
import { GeneralField } from '@formily/core'; import { GeneralField } from '@formily/core';
import { useFieldSchema } from '@formily/react'; import { useField, useFieldSchema, useForm } from '@formily/react';
import flat from 'flat';
import { isString } from 'lodash';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { useCallback, useContext, useMemo } from 'react'; import { useCallback, useContext, useMemo } from 'react';
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
import { mergeFilter } from '../../../block-provider/SharedFilterProvider'; import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
import { useCollection, useCollectionManager } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { isInFilterFormBlock } from '../../../filter-provider'; import { isInFilterFormBlock } from '../../../filter-provider';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { AssociationFieldContext } from './context'; import { AssociationFieldContext } from './context';
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './util';
export const useInsertSchema = (component) => { export const useInsertSchema = (component) => {
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
@ -43,9 +48,75 @@ export default function useServiceOptions(props) {
const { action = 'list', service, fieldNames } = props; const { action = 'list', service, fieldNames } = props;
const params = service?.params || {}; const params = service?.params || {};
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const field = useField();
const form = useForm();
const ctx = useBlockRequestContext();
const { getField } = useCollection(); const { getField } = useCollection();
const { getCollectionFields, getCollectionJoinField } = useCollectionManager(); const { getCollectionFields, getCollectionJoinField } = useCollectionManager();
const record = useRecord(); const record = useRecord();
const parseFilter = useCallback(
(rules) => {
if (!rules) {
return undefined;
}
if (typeof rules === 'string') {
return rules;
}
const type = Object.keys(rules)[0] || '$and';
const conditions = rules[type];
const results = [];
conditions?.forEach((c) => {
const jsonlogic = getInnermostKeyAndValue(c);
const regex = /{{(.*?)}}/;
const matches = jsonlogic.value?.match?.(regex);
if (!matches || (!matches[1].includes('$form') && !matches[1].includes('$iteration'))) {
results.push(c);
return;
}
const associationfield = extractFilterfield(matches[1]);
const filterCollectionField = getCollectionJoinField(`${ctx.props.collection}.${associationfield}`);
if (['o2m', 'm2m'].includes(filterCollectionField?.interface)) {
// 对多子表单
const pattern = generatePattern(matches?.[1], associationfield);
const parseValue: any = extractValuesByPattern(flat(form.values), pattern);
const filters = parseValue.map((v) => {
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
});
results.push({ $or: filters });
} else {
const variablesCtx = { $form: form.values, $iteration: form.values };
let str = matches?.[1];
if (str.includes('$iteration')) {
const path = field.path.segments.concat([]);
path.pop();
str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
}
const parseValue = parseVariables(str, variablesCtx);
if (Array.isArray(parseValue)) {
const filters = parseValue.map((v) => {
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
});
results.push({ $or: filters });
} else {
const filterObj = JSON.parse(
JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
);
results.push(filterObj);
}
}
});
return { [type]: results };
},
[ctx.props?.collection, field.path.segments, form.values, getCollectionJoinField],
);
const fieldServiceFilter = useMemo(() => {
const filterFromSchema = isString(fieldSchema?.['x-component-props']?.service?.params?.filter)
? field.componentProps?.service?.params?.filter
: fieldSchema?.['x-component-props']?.service?.params?.filter;
return mergeFilter([parseFilter(filterFromSchema) || service?.params?.filter]);
}, [field.componentProps?.service?.params?.filter, fieldSchema, parseFilter, service?.params?.filter]);
const normalizeValues = useCallback( const normalizeValues = useCallback(
(obj) => { (obj) => {
@ -85,7 +156,7 @@ export default function useServiceOptions(props) {
}, },
} }
: null, : null,
params?.filter, fieldServiceFilter,
]), ]),
isOToAny && isOToAny &&
sourceValue !== undefined && sourceValue !== undefined &&

View File

@ -2,6 +2,7 @@ import { ISchema } from '@formily/react';
import { isArr } from '@formily/shared'; import { isArr } from '@formily/shared';
import { getDefaultFormat, str2moment } from '@nocobase/utils/client'; import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
import { Tag } from 'antd'; import { Tag } from 'antd';
import { get, isFunction } from 'lodash';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import React from 'react'; import React from 'react';
import { CollectionFieldOptions, useCollectionManager } from '../../../collection-manager'; import { CollectionFieldOptions, useCollectionManager } from '../../../collection-manager';
@ -93,3 +94,37 @@ export const toValue = (value, placeholder) => {
} }
return value; return value;
}; };
export const parseVariables = (str: string, ctx) => {
if (str) {
const result = get(ctx, str);
return isFunction(result) ? result() : result;
} else {
return str;
}
};
export function extractFilterfield(str) {
const match = str.match(/^\$form\.([^.[\]]+)/);
if (match) {
return match[1];
}
return null;
}
export function extractValuesByPattern(obj, pattern) {
const regexPattern = new RegExp(pattern.replace(/\*/g, '\\d+'));
const result = [];
for (const key in obj) {
if (regexPattern.test(key)) {
const value = obj[key];
result.push(value);
}
}
return result;
}
export function generatePattern(str, fieldName) {
const result = str.replace(`$form.${fieldName}.`, `${fieldName}.*.`);
return result;
}

View File

@ -6,14 +6,12 @@ import flat from 'flat';
import _, { uniqBy } from 'lodash'; import _, { uniqBy } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { ResourceActionOptions, useRequest } from '../../../api-client'; import { ResourceActionOptions, useRequest } from '../../../api-client';
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
import { mergeFilter } from '../../../block-provider/SharedFilterProvider'; import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
import { useCollection, useCollectionManager } from '../../../collection-manager'; import { useCollection, useCollectionManager } from '../../../collection-manager';
import { getInnermostKeyAndValue } from '../../common/utils/uitls'; import { getInnermostKeyAndValue } from '../../common/utils/uitls';
import { useCompile } from '../../hooks'; import { useCompile } from '../../hooks';
import { Select, defaultFieldNames } from '../select'; import { Select, defaultFieldNames } from '../select';
import { ReadPretty } from './ReadPretty'; import { ReadPretty } from './ReadPretty';
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './utils';
const EMPTY = 'N/A'; const EMPTY = 'N/A';
export type RemoteSelectProps<P = any> = SelectProps<P, any> & { export type RemoteSelectProps<P = any> = SelectProps<P, any> & {
@ -49,7 +47,6 @@ const InternalRemoteSelect = connect(
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const isQuickAdd = fieldSchema['x-component-props']?.addMode === 'quickAdd'; const isQuickAdd = fieldSchema['x-component-props']?.addMode === 'quickAdd';
const field = useField(); const field = useField();
const ctx = useBlockRequestContext();
const { getField } = useCollection(); const { getField } = useCollection();
const searchData = useRef(null); const searchData = useRef(null);
const { getCollectionJoinField, getInterface } = useCollectionManager(); const { getCollectionJoinField, getInterface } = useCollectionManager();
@ -123,70 +120,6 @@ const InternalRemoteSelect = connect(
}, },
[targetField?.uiSchema, fieldNames], [targetField?.uiSchema, fieldNames],
); );
const parseFilter = useCallback(
(rules) => {
if (!rules) {
return undefined;
}
if (typeof rules === 'string') {
return rules;
}
const type = Object.keys(rules)[0] || '$and';
const conditions = rules[type];
const results = [];
conditions?.forEach((c) => {
const jsonlogic = getInnermostKeyAndValue(c);
const regex = /{{(.*?)}}/;
const matches = jsonlogic.value?.match?.(regex);
if (!matches || (!matches[1].includes('$form') && !matches[1].includes('$iteration'))) {
results.push(c);
return;
}
const associationfield = extractFilterfield(matches[1]);
const filterCollectionField = getCollectionJoinField(`${ctx.props.collection}.${associationfield}`);
if (['o2m', 'm2m'].includes(filterCollectionField?.interface)) {
// 对多子表单
const pattern = generatePattern(matches?.[1], associationfield);
const parseValue: any = extractValuesByPattern(flat(form.values), pattern);
const filters = parseValue.map((v) => {
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
});
results.push({ $or: filters });
} else {
const variablesCtx = { $form: form.values, $iteration: form.values };
let str = matches?.[1];
if (str.includes('$iteration')) {
const path = field.path.segments.concat([]);
path.pop();
str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
}
const parseValue = parseVariables(str, variablesCtx);
if (Array.isArray(parseValue)) {
const filters = parseValue.map((v) => {
return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
});
results.push({ $or: filters });
} else {
const filterObj = JSON.parse(
JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
);
results.push(filterObj);
}
}
});
return { [type]: results };
},
[ctx.props?.collection, field.path.segments, form.values, getCollectionJoinField],
);
const filter = useMemo(() => {
const filterFromSchema = _.isString(fieldSchema?.['x-component-props']?.service?.params?.filter)
? field.componentProps?.service?.params?.filter
: fieldSchema?.['x-component-props']?.service?.params?.filter;
return mergeFilter([parseFilter(filterFromSchema) || service?.params?.filter]);
}, [field.componentProps?.service?.params?.filter, fieldSchema, parseFilter, service?.params?.filter]);
const { data, run, loading } = useRequest( const { data, run, loading } = useRequest(
{ {
action: 'list', action: 'list',
@ -194,8 +127,7 @@ const InternalRemoteSelect = connect(
params: { params: {
pageSize: 200, pageSize: 200,
...service?.params, ...service?.params,
// search needs filter: service?.params?.filter,
filter,
}, },
}, },
{ {
@ -243,7 +175,7 @@ const InternalRemoteSelect = connect(
}, },
} }
: {}, : {},
filter, service?.params?.filter,
]), ]),
}); });
searchData.current = search; searchData.current = search;

View File

@ -1,35 +0,0 @@
import { get, isFunction } from 'lodash';
export const parseVariables = (str: string, ctx) => {
if (str) {
const result = get(ctx, str);
return isFunction(result) ? result() : result;
} else {
return str;
}
};
export function extractFilterfield(str) {
const match = str.match(/^\$form\.([^.[\]]+)/);
if (match) {
return match[1];
}
return null;
}
export function extractValuesByPattern(obj, pattern) {
const regexPattern = new RegExp(pattern.replace(/\*/g, '\\d+'));
const result = [];
for (const key in obj) {
if (regexPattern.test(key)) {
const value = obj[key];
result.push(value);
}
}
return result;
}
export function generatePattern(str, fieldName) {
const result = str.replace(`$form.${fieldName}.`, `${fieldName}.*.`);
return result;
}