feat(data-vi): add extended API for field interface configuration (#6742)

* feat(data-vi): add extend api

* feat(data-vi): add extend API for field interface configuration
This commit is contained in:
YANG QIA 2025-04-25 10:51:46 +08:00 committed by GitHub
parent b923d89fa0
commit 757765228f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 119 deletions

View File

@ -1,81 +0,0 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { processData } from '../utils';
describe('utils', () => {
describe('process data', () => {
it('should process select field', () => {
expect(
processData(
[
{
name: 'tag',
type: 'bigInt',
interface: 'select',
uiSchema: {
type: 'string',
enum: [
{
value: '1',
label: 'Yes',
},
{
value: '2',
label: 'No',
},
],
},
label: 'Tag',
value: 'tag',
key: 'tag',
},
],
[{ tag: 1 }],
{},
),
).toEqual([{ tag: 'Yes' }]);
});
it('should not process when aggregating', () => {
expect(
processData(
[
{
name: 'tag',
type: 'bigInt',
interface: 'select',
uiSchema: {
type: 'string',
enum: [
{
value: '1',
label: 'Yes',
},
{
value: '2',
label: 'No',
},
],
},
label: 'Tag',
value: 'tag',
key: 'tag',
query: {
field: 'tag',
aggregation: 'count',
},
},
],
[{ tag: 1 }],
{},
),
).toEqual([{ tag: 1 }]);
});
});
});

View File

@ -14,6 +14,7 @@ import {
DEFAULT_DATA_SOURCE_KEY,
useACLRoleContext,
useDataSourceManager,
usePlugin,
} from '@nocobase/client';
import { useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -21,7 +22,8 @@ import { ChartConfigContext } from '../configure';
import formatters from '../configure/formatters';
import { useChartsTranslation } from '../locale';
import { ChartRendererContext } from '../renderer';
import { getField, getSelectedFields, parseField, processData } from '../utils';
import { getField, getSelectedFields, parseField } from '../utils';
import PluginDataVisualiztionClient from '..';
export type FieldOption = {
value: string;
@ -226,12 +228,36 @@ export const useOrderReaction = (defaultOptions: any[], fields: FieldOption[]) =
};
export const useData = (data?: any[], dataSource?: string, collection?: string) => {
const { t } = useChartsTranslation();
const { service, query } = useContext(ChartRendererContext);
const plugin = usePlugin(PluginDataVisualiztionClient);
const fields = useFieldsWithAssociation(dataSource, collection);
const form = useForm();
const selectedFields = getSelectedFields(fields, form?.values?.query || query);
return processData(selectedFields, service?.data || data || [], { t });
const selectedFields = getSelectedFields(fields, form?.values?.query || query) as (FieldOption & { query?: any })[];
const fieldInterfaceConfigs = plugin.fieldInterfaceConfigs;
const formatters = {};
for (const field of selectedFields) {
if (field?.query?.aggregation) {
continue;
}
const config = fieldInterfaceConfigs[field.interface];
if (!config) {
continue;
}
const { valueFormatter } = config;
formatters[field.value] = (value: any) => valueFormatter(field, value);
}
return (service?.data || data || []).map((record: any) => {
const processed = {};
Object.entries(record).forEach(([key, value]) => {
const formatter = formatters[key];
if (!formatter) {
processed[key] = value;
return;
}
processed[key] = formatter(value);
});
return processed;
});
};
export const useCollectionFieldsOptions = (dataSource: string, collectionName: string, maxDepth = 2, excludes = []) => {

View File

@ -41,10 +41,42 @@ import { useChartRefreshActionProps } from './initializers/RefreshAction';
import { useChartBlockRefreshActionProps } from './initializers/BlockRefreshAction';
import { ChartRendererToolbar, ChartFilterBlockToolbar, ChartFilterItemToolbar } from './toolbar';
import { ChartCardItem } from './block/CardItem';
import { Schema } from '@formily/react';
type fieldInterfaceConfig = {
valueFormatter: (field: any, value: any) => any;
};
const valueFormatter = (field: any, value: any) => {
const options = field.uiSchema?.enum;
const parseEnumValues = (options: { label: string; value: string }[], value: any) => {
if (Array.isArray(value)) {
return value.map((v) => parseEnumValues(options, v));
}
const option = options.find((option) => option.value === (value?.toString?.() || value));
return Schema.compile(option?.label || value, { t: lang });
};
if (!options || !Array.isArray(options)) {
return value;
}
return parseEnumValues(options, value);
};
class PluginDataVisualiztionClient extends Plugin {
public charts: ChartGroup = new ChartGroup();
fieldInterfaceConfigs: {
[fieldInterface: string]: fieldInterfaceConfig;
} = {
select: { valueFormatter },
multipleSelect: { valueFormatter },
radioGroup: { valueFormatter },
};
registerFieldInterfaceConfig(key: string, config: fieldInterfaceConfig) {
this.fieldInterfaceConfigs[key] = config;
}
async load() {
this.charts.addGroup('antd', { title: 'Ant Design', charts: antd });
this.charts.addGroup('ant-design-charts', { title: 'Ant Design Charts', charts: g2plot });

View File

@ -106,40 +106,6 @@ export const getSelectedFields = (fields: FieldOption[], query: QueryProps) => {
return selectedFields;
};
export const processData = (selectedFields: (FieldOption & { query?: any })[], data: any[], scope: any) => {
const parseEnum = (field: FieldOption, value: any) => {
const options = field.uiSchema?.enum as { value: string; label: string }[];
if (!options || !Array.isArray(options)) {
return value;
}
if (Array.isArray(value)) {
return value.map((v) => parseEnum(field, v));
}
const option = options.find((option) => option.value === (value?.toString?.() || value));
return Schema.compile(option?.label || value, scope);
};
return data.map((record) => {
const processed = {};
Object.entries(record).forEach(([key, value]) => {
const field = selectedFields.find((field) => field.value === key && !field?.query?.aggregation);
if (!field) {
processed[key] = value;
return;
}
switch (field.interface) {
case 'select':
case 'radioGroup':
case 'multipleSelect':
processed[key] = parseEnum(field, value);
break;
default:
processed[key] = value;
}
});
return processed;
});
};
export const removeUnparsableFilter = (filter: any) => {
if (typeof filter === 'object' && filter !== null) {
if (Array.isArray(filter)) {