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:
parent
eb0ba1c96b
commit
08adf2fe40
@ -24,38 +24,88 @@ const AssociationCascader = connect((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
const dict: { [key: string]: string[] } =
|
if (props.chartCascader) {
|
||||||
data?.data.reduce((acc, item) => {
|
const dataOptions = {};
|
||||||
if (item[associationField]) {
|
const options = [];
|
||||||
const key = item[associationField][joinTitleField];
|
data?.data.forEach((item) => {
|
||||||
acc[key] ??= [];
|
if (item?.[associationField]) {
|
||||||
acc[key].push(item[titleField]);
|
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;
|
});
|
||||||
}, {}) || {};
|
for (const key in dataOptions) {
|
||||||
const options = Object.entries(dict).map(([key, values]) => ({
|
options.push({
|
||||||
[fieldNames.label]: key,
|
label: key,
|
||||||
[fieldNames.value]: key,
|
value: key,
|
||||||
children: values.map((value) => ({ [fieldNames.label]: value, [fieldNames.value]: value })),
|
children: [...dataOptions[key]],
|
||||||
}));
|
});
|
||||||
return options;
|
}
|
||||||
|
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]);
|
}, [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 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;
|
const arrayValue = value && options ? [] : undefined;
|
||||||
if (arrayValue) {
|
if (arrayValue) {
|
||||||
for (const option of options) {
|
for (const option of options) {
|
||||||
if (option[fieldNames.value] === value) {
|
if (option[fieldLabel] === value) {
|
||||||
arrayValue.push(option[fieldNames.value]);
|
arrayValue.push(option[fieldLabel]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (const subOption of option.children) {
|
for (const subOption of option.children) {
|
||||||
if (subOption[fieldNames.value] === value) {
|
if (subOption[joinFieldLabel] === value) {
|
||||||
arrayValue.push(option[fieldNames.value]);
|
arrayValue.push(option[joinFieldLabel]);
|
||||||
arrayValue.push(subOption[fieldNames.value]);
|
arrayValue.push(subOption[joinFieldLabel]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
useDesignable,
|
useDesignable,
|
||||||
useGlobalTheme,
|
useGlobalTheme,
|
||||||
useSchemaInitializerItem,
|
useSchemaInitializerItem,
|
||||||
|
useCompile,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
import { Alert, ConfigProvider, Typography } from 'antd';
|
import { Alert, ConfigProvider, Typography } from 'antd';
|
||||||
@ -35,7 +36,7 @@ const { Paragraph, Text } = Typography;
|
|||||||
const FieldComponentProps: React.FC = observer(
|
const FieldComponentProps: React.FC = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const schema = getPropsSchemaByComponent(form.values.component);
|
const schema = getPropsSchemaByComponent(form.values.component, props['allCollection']);
|
||||||
return schema ? <SchemaComponent schema={schema} {...props} /> : null;
|
return schema ? <SchemaComponent schema={schema} {...props} /> : null;
|
||||||
},
|
},
|
||||||
{ displayName: 'FieldComponentProps' },
|
{ displayName: 'FieldComponentProps' },
|
||||||
@ -127,12 +128,31 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
const dm = useDataSourceManager();
|
const dm = useDataSourceManager();
|
||||||
const sourceFields = useChartFilterSourceFields();
|
const sourceFields = useChartFilterSourceFields();
|
||||||
const { options: fieldComponents, values: fieldComponentValues } = useFieldComponents();
|
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 handleClick = useCallback(async () => {
|
||||||
const values = await FormDialog(
|
const values = await FormDialog(
|
||||||
t('Add custom field'),
|
t('Add custom field'),
|
||||||
() => (
|
() => (
|
||||||
<SchemaComponentOptions
|
<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 }}
|
components={{ ...components, FieldComponentProps }}
|
||||||
>
|
>
|
||||||
<FormLayout layout={'vertical'}>
|
<FormLayout layout={'vertical'}>
|
||||||
@ -176,6 +196,9 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
title: t('Component properties'),
|
title: t('Component properties'),
|
||||||
'x-component': 'FieldComponentProps',
|
'x-component': 'FieldComponentProps',
|
||||||
|
'x-component-props': {
|
||||||
|
allCollection,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -242,6 +265,7 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
...(defaultSchema['x-component-props'] || {}),
|
...(defaultSchema['x-component-props'] || {}),
|
||||||
...props,
|
...props,
|
||||||
|
chartCascader: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -55,7 +55,7 @@ export const getOptionsSchema = () => {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPropsSchemaByComponent = (component: string) => {
|
export const getPropsSchemaByComponent = (component: string, allCollection) => {
|
||||||
const showTime = {
|
const showTime = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
'x-content': '{{ t("Show time") }}',
|
'x-content': '{{ t("Show time") }}',
|
||||||
@ -135,6 +135,34 @@ export const getPropsSchemaByComponent = (component: string) => {
|
|||||||
options,
|
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];
|
return propsSchema[component];
|
||||||
};
|
};
|
||||||
|
@ -507,6 +507,7 @@ export const useFieldComponents = () => {
|
|||||||
{ label: t('Select'), value: 'Select' },
|
{ label: t('Select'), value: 'Select' },
|
||||||
{ label: t('Radio group'), value: 'Radio.Group' },
|
{ label: t('Radio group'), value: 'Radio.Group' },
|
||||||
{ label: t('Checkbox group'), value: 'Checkbox.Group' },
|
{ label: t('Checkbox group'), value: 'Checkbox.Group' },
|
||||||
|
{ label: t('AssociationCascader'), value: 'AssociationCascader' },
|
||||||
// { label: t('China region'), value: 'chinaRegion' },
|
// { label: t('China region'), value: 'chinaRegion' },
|
||||||
];
|
];
|
||||||
return {
|
return {
|
||||||
|
Loading…
Reference in New Issue
Block a user