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:
parent
53a6a6c621
commit
457082ffa1
@ -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 &&
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user