fix: 图表添加级联选择 (#522)

<!-- 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. -->

Reviewed-on: daoyoucloud/tachycode#522
Co-authored-by: wjh <wwwjh0710@163.com>
Co-committed-by: wjh <wwwjh0710@163.com>
This commit is contained in:
wjh 2024-03-28 14:47:41 +08:00 committed by baijingfeng
parent eb0ba1c96b
commit 08adf2fe40
4 changed files with 127 additions and 24 deletions

View File

@ -24,38 +24,88 @@ const AssociationCascader = connect((props) => {
});
const options = useMemo(() => {
const dict: { [key: string]: string[] } =
data?.data.reduce((acc, item) => {
if (item[associationField]) {
const key = item[associationField][joinTitleField];
acc[key] ??= [];
acc[key].push(item[titleField]);
if (props.chartCascader) {
const dataOptions = {};
const options = [];
data?.data.forEach((item) => {
if (item?.[associationField]) {
const field = [];
if (Array.isArray(item[associationField]) && item[associationField].length) {
field.push(...item[associationField]);
} else {
field.push({ ...item[associationField] });
}
field.forEach((fieldItem) => {
if (!Object.keys(fieldItem).length) return;
if (dataOptions[fieldItem[joinTitleField]]) {
dataOptions[fieldItem[joinTitleField]].push({
label: item[titleField],
value: item[titleField],
});
} else {
dataOptions[fieldItem[joinTitleField]] = [
{
label: item[titleField],
value: item[titleField],
},
];
}
});
}
return acc;
}, {}) || {};
const options = Object.entries(dict).map(([key, values]) => ({
[fieldNames.label]: key,
[fieldNames.value]: key,
children: values.map((value) => ({ [fieldNames.label]: value, [fieldNames.value]: value })),
}));
return options;
});
for (const key in dataOptions) {
options.push({
label: key,
value: key,
children: [...dataOptions[key]],
});
}
return options;
} else {
const dict: { [key: string]: string[] } =
data?.data.reduce((acc, item) => {
if (item[associationField]) {
const key = item[associationField][joinTitleField];
acc[key] ??= [];
acc[key].push(item[titleField]);
}
return acc;
}, {}) || {};
const options = Object.entries(dict).map(([key, values]) => ({
[fieldNames.label]: key,
[fieldNames.value]: key,
children: values.map((value) => ({ [fieldNames.label]: value, [fieldNames.value]: value })),
}));
return options;
}
}, [associationField, joinTitleField, titleField, data?.data]);
return <SingleValueCascader options={options} {...props} showSearch />;
return (
<SingleValueCascader
options={options}
{...props}
showSearch
titleField={titleField}
joinTitleField={joinTitleField}
/>
);
});
const SingleValueCascader = (props) => {
const { value, options, onChange, fieldNames } = props;
const { value, options, onChange, fieldNames, chartCascader, titleField, joinTitleField } = props;
const fieldLabel = fieldNames ? fieldNames.value : titleField;
const joinFieldLabel = fieldNames ? fieldNames.value : 'value';
const arrayValue = value && options ? [] : undefined;
if (arrayValue) {
for (const option of options) {
if (option[fieldNames.value] === value) {
arrayValue.push(option[fieldNames.value]);
if (option[fieldLabel] === value) {
arrayValue.push(option[fieldLabel]);
break;
}
for (const subOption of option.children) {
if (subOption[fieldNames.value] === value) {
arrayValue.push(option[fieldNames.value]);
arrayValue.push(subOption[fieldNames.value]);
if (subOption[joinFieldLabel] === value) {
arrayValue.push(option[joinFieldLabel]);
arrayValue.push(subOption[joinFieldLabel]);
break;
}
}

View File

@ -22,6 +22,7 @@ import {
useDesignable,
useGlobalTheme,
useSchemaInitializerItem,
useCompile,
} from '@nocobase/client';
import { useMemoizedFn } from 'ahooks';
import { Alert, ConfigProvider, Typography } from 'antd';
@ -35,7 +36,7 @@ const { Paragraph, Text } = Typography;
const FieldComponentProps: React.FC = observer(
(props) => {
const form = useForm();
const schema = getPropsSchemaByComponent(form.values.component);
const schema = getPropsSchemaByComponent(form.values.component, props['allCollection']);
return schema ? <SchemaComponent schema={schema} {...props} /> : null;
},
{ displayName: 'FieldComponentProps' },
@ -127,12 +128,31 @@ export const ChartFilterCustomItemInitializer: React.FC<{
const dm = useDataSourceManager();
const sourceFields = useChartFilterSourceFields();
const { options: fieldComponents, values: fieldComponentValues } = useFieldComponents();
const { collections, getCollectionFields } = useCollectionManager_deprecated();
const compile = useCompile();
const allCollection = collections.map((collection) => {
return {
label: collection.getOption('title'),
value: collection.getOption('name'),
};
});
const handleClick = useCallback(async () => {
const values = await FormDialog(
t('Add custom field'),
() => (
<SchemaComponentOptions
scope={{ ...scope, useChartFilterSourceFields }}
scope={{
...scope,
useChartFilterSourceFields,
useCollectionField(collection) {
return getCollectionFields(collection)?.map((field) => {
return {
label: compile(field.uiSchema.title),
value: field.name,
};
});
},
}}
components={{ ...components, FieldComponentProps }}
>
<FormLayout layout={'vertical'}>
@ -176,6 +196,9 @@ export const ChartFilterCustomItemInitializer: React.FC<{
type: 'object',
title: t('Component properties'),
'x-component': 'FieldComponentProps',
'x-component-props': {
allCollection,
},
},
},
}}
@ -242,6 +265,7 @@ export const ChartFilterCustomItemInitializer: React.FC<{
'x-component-props': {
...(defaultSchema['x-component-props'] || {}),
...props,
chartCascader: true,
},
}),
);

View File

@ -55,7 +55,7 @@ export const getOptionsSchema = () => {
return options;
};
export const getPropsSchemaByComponent = (component: string) => {
export const getPropsSchemaByComponent = (component: string, allCollection) => {
const showTime = {
type: 'boolean',
'x-content': '{{ t("Show time") }}',
@ -135,6 +135,34 @@ export const getPropsSchemaByComponent = (component: string) => {
options,
},
},
AssociationCascader: {
type: 'object',
properties: {
collection: {
type: 'string',
title: '{{t("Field collection")}}',
'x-decorator': 'FormItem',
'x-component': 'Select',
enum: allCollection,
},
associationField: {
type: 'string',
title: '{{t("Field Association")}}',
'x-visible': false,
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-reactions': {
dependencies: ['component', '.collection'],
fulfill: {
schema: {
'x-visible': "{{$deps[0]=== 'AssociationCascader' && $deps[1]}}",
enum: '{{useCollectionField($deps[1])}}',
},
},
},
},
},
},
};
return propsSchema[component];
};

View File

@ -507,6 +507,7 @@ export const useFieldComponents = () => {
{ label: t('Select'), value: 'Select' },
{ label: t('Radio group'), value: 'Radio.Group' },
{ label: t('Checkbox group'), value: 'Checkbox.Group' },
{ label: t('AssociationCascader'), value: 'AssociationCascader' },
// { label: t('China region'), value: 'chinaRegion' },
];
return {