mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
feat(data-vi): improved user experiences (refer to pr) (#4013)
* feat(data-vi): improved user experiences (refer to pr) * feat: enhance transformers * fix: transformer * fix: test * fix: tooltips * feat: add format * chore: add locales and tip
This commit is contained in:
parent
91254bdf55
commit
91fdd84ea1
@ -1,13 +1,12 @@
|
||||
import * as client from '@nocobase/client';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { vi } from 'vitest';
|
||||
import formatters from '../block/formatters';
|
||||
import transformers from '../block/transformers';
|
||||
import formatters from '../configure/formatters';
|
||||
import transformers from '../transformers';
|
||||
import {
|
||||
useChartFields,
|
||||
useFieldsWithAssociation,
|
||||
useFieldTransformer,
|
||||
useFieldTypes,
|
||||
useFormatters,
|
||||
useOrderFieldsOptions,
|
||||
useTransformers,
|
||||
@ -174,41 +173,6 @@ describe('hooks', () => {
|
||||
expect(field.dataSource).toEqual(formatters.datetime);
|
||||
});
|
||||
|
||||
test('useFieldTypes', () => {
|
||||
const fields = renderHook(() => useFieldsWithAssociation('main', 'orders')).result.current;
|
||||
const { result } = renderHook(() => useFieldTypes(fields));
|
||||
const func = result.current;
|
||||
let state1 = {};
|
||||
let state2 = {};
|
||||
const field = {
|
||||
dataSource: [],
|
||||
state: {},
|
||||
};
|
||||
const query = (path: string, val: string) => ({
|
||||
get: () => {
|
||||
if (path === 'query') {
|
||||
return { measures: [{ field: ['price'] }, { field: ['name'] }] };
|
||||
}
|
||||
return val;
|
||||
},
|
||||
});
|
||||
const field1 = {
|
||||
query: (path: string) => query(path, 'price'),
|
||||
setState: (state) => (state1 = state),
|
||||
...field,
|
||||
};
|
||||
const field2 = {
|
||||
query: (path: string) => query(path, 'name'),
|
||||
setState: (state) => (state2 = state),
|
||||
...field,
|
||||
};
|
||||
func(field1);
|
||||
func(field2);
|
||||
expect(field1.dataSource.map((item) => item.value)).toEqual(Object.keys(transformers));
|
||||
expect(state1).toEqual({ value: 'number', disabled: true });
|
||||
expect(state2).toEqual({ value: null, disabled: false });
|
||||
});
|
||||
|
||||
test('useTransformers', () => {
|
||||
const field = {
|
||||
query: () => ({
|
||||
@ -217,7 +181,9 @@ describe('hooks', () => {
|
||||
dataSource: [],
|
||||
};
|
||||
renderHook(() => useTransformers(field));
|
||||
expect(field.dataSource.map((item) => item.value)).toEqual(Object.keys(transformers['datetime']));
|
||||
expect(field.dataSource.map((item) => item.value)).toEqual(
|
||||
Object.keys({ ...transformers['general'], ...transformers['datetime'] }),
|
||||
);
|
||||
});
|
||||
|
||||
test('useFieldTransformers', () => {
|
||||
|
@ -1,54 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export type Transformer = (val: any, locale?: string) => string | number;
|
||||
|
||||
const transformers: {
|
||||
[key: string]: {
|
||||
[key: string]: Transformer;
|
||||
};
|
||||
} = {
|
||||
datetime: {
|
||||
YYYY: (val: string) => dayjs(val).format('YYYY'),
|
||||
MM: (val: string) => dayjs(val).format('MM'),
|
||||
DD: (val: string) => dayjs(val).format('DD'),
|
||||
'YYYY-MM': (val: string) => dayjs(val).format('YYYY-MM'),
|
||||
'YYYY-MM-DD': (val: string) => dayjs(val).format('YYYY-MM-DD'),
|
||||
'YYYY-MM-DD hh:mm': (val: string) => dayjs(val).format('YYYY-MM-DD hh:mm'),
|
||||
'YYYY-MM-DD hh:mm:ss': (val: string) => dayjs(val).format('YYYY-MM-DD hh:mm:ss'),
|
||||
},
|
||||
date: {
|
||||
YYYY: (val: string) => dayjs(val).format('YYYY'),
|
||||
MM: (val: string) => dayjs(val).format('MM'),
|
||||
DD: (val: string) => dayjs(val).format('DD'),
|
||||
'YYYY-MM': (val: string) => dayjs(val).format('YYYY-MM'),
|
||||
'YYYY-MM-DD': (val: string) => dayjs(val).format('YYYY-MM-DD'),
|
||||
},
|
||||
time: {
|
||||
'hh:mm:ss': (val: string) => dayjs(val).format('hh:mm:ss'),
|
||||
'hh:mm': (val: string) => dayjs(val).format('hh:mm'),
|
||||
hh: (val: string) => dayjs(val).format('hh'),
|
||||
},
|
||||
number: {
|
||||
Percent: (val: number) =>
|
||||
new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(
|
||||
val,
|
||||
),
|
||||
Currency: (val: number, locale = 'en-US') => {
|
||||
const currency = {
|
||||
'zh-CN': 'CNY',
|
||||
'en-US': 'USD',
|
||||
'ja-JP': 'JPY',
|
||||
'ko-KR': 'KRW',
|
||||
'pt-BR': 'BRL',
|
||||
'ru-RU': 'RUB',
|
||||
'tr-TR': 'TRY',
|
||||
'es-ES': 'EUR',
|
||||
}[locale];
|
||||
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(val);
|
||||
},
|
||||
Exponential: (val: number | string) => (+val)?.toExponential(),
|
||||
Abbreviation: (val: number, locale = 'en-US') => new Intl.NumberFormat(locale, { notation: 'compact' }).format(val),
|
||||
},
|
||||
};
|
||||
|
||||
export default transformers;
|
@ -4,7 +4,7 @@ import { QueryProps } from '../renderer';
|
||||
import { parseField } from '../utils';
|
||||
import { ISchema } from '@formily/react';
|
||||
import configs, { AnySchemaProperties, Config } from './configs';
|
||||
import { Transformer } from '../block/transformers';
|
||||
import { Transformer } from '../transformers';
|
||||
|
||||
export type RenderProps = {
|
||||
data: Record<string, any>[];
|
||||
|
@ -25,36 +25,49 @@ export class G2PlotChart extends Chart {
|
||||
};
|
||||
|
||||
getProps({ data, general, advanced, fieldProps }: RenderProps) {
|
||||
const xFieldProps = fieldProps[general.xField];
|
||||
const yFieldProps = fieldProps[general.yField];
|
||||
const seriesFieldProps = fieldProps[general.seriesField];
|
||||
const config = {
|
||||
legend: {
|
||||
color: {
|
||||
itemLabelText: (datnum: { label: string }) => {
|
||||
const props = fieldProps[general.seriesField];
|
||||
const transformer = props?.transformer;
|
||||
const transformer = seriesFieldProps?.transformer;
|
||||
return transformer ? transformer(datnum.label) : datnum.label;
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: (d, index: number, data, column: any) => {
|
||||
const field = column.y?.field;
|
||||
const props = fieldProps[field];
|
||||
const name = props?.label || field;
|
||||
const transformer = props?.transformer;
|
||||
const value = column.y?.value[index];
|
||||
return { name, value: transformer ? transformer(value) : value };
|
||||
tooltip: {
|
||||
title: (data: any) => {
|
||||
const { [general.xField]: x } = data;
|
||||
return xFieldProps?.transformer ? xFieldProps.transformer(x) : x;
|
||||
},
|
||||
items: [
|
||||
(data: any) => {
|
||||
const { [general.xField]: x, [general.yField]: y, [general.seriesField]: series } = data;
|
||||
let name = '';
|
||||
if (series) {
|
||||
name = seriesFieldProps.transformer ? seriesFieldProps.transformer(series) : series;
|
||||
} else {
|
||||
name = yFieldProps?.label || general.yField;
|
||||
}
|
||||
return {
|
||||
name,
|
||||
value: yFieldProps?.transformer ? yFieldProps.transformer(y) : y,
|
||||
};
|
||||
},
|
||||
],
|
||||
},
|
||||
axis: {
|
||||
x: {
|
||||
labelFormatter: (datnum: any) => {
|
||||
const props = fieldProps[general.xField];
|
||||
const transformer = props?.transformer;
|
||||
const transformer = xFieldProps?.transformer;
|
||||
return transformer ? transformer(datnum) : datnum;
|
||||
},
|
||||
},
|
||||
y: {
|
||||
labelFormatter: (datnum: any) => {
|
||||
const props = fieldProps[general.yField];
|
||||
const transformer = props?.transformer;
|
||||
const transformer = yFieldProps?.transformer;
|
||||
return transformer ? transformer(datnum) : datnum;
|
||||
},
|
||||
},
|
||||
|
@ -32,16 +32,19 @@ export class Pie extends G2PlotChart {
|
||||
};
|
||||
|
||||
getProps({ data, general, advanced, fieldProps }: RenderProps) {
|
||||
const angleFieldProps = fieldProps[general.angleField];
|
||||
const props = super.getProps({ data, general, advanced, fieldProps });
|
||||
return {
|
||||
...props,
|
||||
tooltip: (d, index: number, data, column: any) => {
|
||||
const field = column.y0.field;
|
||||
const props = fieldProps[field];
|
||||
const name = props?.label || field;
|
||||
const transformer = props?.transformer;
|
||||
const value = column.y0.value[index];
|
||||
return { name, value: transformer ? transformer(value) : value };
|
||||
tooltip: {
|
||||
items: [
|
||||
(data: any) => {
|
||||
const { [general.colorField]: color, [general.angleField]: angle } = data;
|
||||
const name = color || angleFieldProps?.label || general.angleField;
|
||||
const transformer = angleFieldProps?.transformer;
|
||||
return { name, value: transformer ? transformer(angle) : angle };
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -30,18 +30,9 @@ export const ChartConfigProvider: React.FC = (props) => {
|
||||
const { insertAdjacent } = useDesignable();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [current, setCurrent] = useState<ChartConfigCurrent>({} as any);
|
||||
const { token } = theme.useToken();
|
||||
return (
|
||||
<ChartConfigContext.Provider value={{ visible, setVisible, current, setCurrent }}>
|
||||
<div
|
||||
className={css`
|
||||
.ant-card {
|
||||
border: ${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary};
|
||||
}
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
{props.children}
|
||||
<ChartRendererProvider {...current.field?.decoratorProps}>
|
||||
<ChartConfigure insert={(schema, options) => insertAdjacent('beforeEnd', schema, options)} />
|
||||
</ChartRendererProvider>
|
||||
|
@ -2,22 +2,33 @@ import { RightSquareOutlined } from '@ant-design/icons';
|
||||
import { ArrayItems, Editable, FormCollapse, FormItem, FormLayout, Switch } from '@formily/antd-v5';
|
||||
import { Form as FormType, ObjectField, createForm, onFieldChange, onFormInit } from '@formily/core';
|
||||
import { FormConsumer, ISchema, Schema } from '@formily/react';
|
||||
import { AutoComplete, FormProvider, SchemaComponent, gridRowColWrap, useDesignable } from '@nocobase/client';
|
||||
import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
|
||||
import {
|
||||
AutoComplete,
|
||||
FormProvider,
|
||||
SchemaComponent,
|
||||
Select,
|
||||
gridRowColWrap,
|
||||
useDesignable,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography, theme } from 'antd';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import React, { useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
useChartFields,
|
||||
useCollectionOptions,
|
||||
useCollectionFieldsOptions,
|
||||
useCollectionFilterOptions,
|
||||
useData,
|
||||
useFieldTypes,
|
||||
useFieldsWithAssociation,
|
||||
useFormatters,
|
||||
useOrderFieldsOptions,
|
||||
useOrderReaction,
|
||||
useFieldTypeSelectProps,
|
||||
useArgument,
|
||||
useTransformers,
|
||||
useTransformerSelectProps,
|
||||
useFieldSelectProps,
|
||||
} from '../hooks';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { ChartRenderer, ChartRendererContext } from '../renderer';
|
||||
@ -27,6 +38,8 @@ import { useChartTypes, useCharts, useDefaultChartType } from '../chart/group';
|
||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
|
||||
import { ChartConfigContext } from './ChartConfigProvider';
|
||||
const { Paragraph, Text } = Typography;
|
||||
import { css } from '@emotion/css';
|
||||
import { TransformerDynamicComponent } from './TransformerDynamicComponent';
|
||||
|
||||
export type SelectedField = {
|
||||
field: string | string[];
|
||||
@ -100,18 +113,21 @@ export const ChartConfigure: React.FC<{
|
||||
};
|
||||
const chartType = useDefaultChartType();
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
() => {
|
||||
const decoratorProps = initialValues || field?.decoratorProps;
|
||||
const config = decoratorProps?.config || {};
|
||||
return createForm({
|
||||
values: {
|
||||
config: { chartType },
|
||||
...(initialValues || field?.decoratorProps),
|
||||
...decoratorProps,
|
||||
config: { chartType, ...config, title: config.title || field?.componentProps?.title },
|
||||
collection: [dataSource, collection],
|
||||
},
|
||||
effects: (form) => {
|
||||
onFieldChange('config.chartType', () => initChart(true));
|
||||
onFormInit(() => queryReact(form));
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
// visible, dataSource, collection added here to re-initialize form when visible, dataSource, collection change
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[field, visible, dataSource, collection],
|
||||
@ -159,6 +175,7 @@ export const ChartConfigure: React.FC<{
|
||||
open={visible}
|
||||
onOk={() => {
|
||||
const { query, config, transform, mode } = form.values;
|
||||
const { title, bordered } = config || {};
|
||||
const afterSave = () => {
|
||||
setVisible(false);
|
||||
current.service?.run(dataSource, collection, query);
|
||||
@ -175,8 +192,18 @@ export const ChartConfigure: React.FC<{
|
||||
mode: mode || 'builder',
|
||||
};
|
||||
if (schema && schema['x-uid']) {
|
||||
schema['x-component-props'] = {
|
||||
...schema['x-component-props'],
|
||||
title,
|
||||
bordered,
|
||||
};
|
||||
schema['x-decorator-props'] = rendererProps;
|
||||
field.decoratorProps = rendererProps;
|
||||
field.componentProps = {
|
||||
...field.componentProps,
|
||||
title,
|
||||
bordered,
|
||||
};
|
||||
field['x-acl-action'] = `${collection}:list`;
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
@ -204,10 +231,21 @@ export const ChartConfigure: React.FC<{
|
||||
});
|
||||
}}
|
||||
width={'95%'}
|
||||
className={css`
|
||||
.ant-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
`}
|
||||
styles={{
|
||||
header: {
|
||||
padding: '12px 24px 0px 24px',
|
||||
},
|
||||
body: {
|
||||
background: 'rgba(128, 128, 128, 0.08)',
|
||||
},
|
||||
footer: {
|
||||
padding: '0px 24px 12px 24px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormProvider form={form}>
|
||||
@ -216,10 +254,11 @@ export const ChartConfigure: React.FC<{
|
||||
<Col span={7}>
|
||||
<Card
|
||||
style={{
|
||||
height: 'calc(100vh - 300px)',
|
||||
height: 'calc(100vh - 288px)',
|
||||
overflow: 'auto',
|
||||
margin: '12px 0 12px 12px',
|
||||
margin: '6px 0 6px 12px',
|
||||
}}
|
||||
bodyStyle={{ padding: '0 16px' }}
|
||||
ref={queryRef}
|
||||
>
|
||||
<Tabs
|
||||
@ -242,10 +281,11 @@ export const ChartConfigure: React.FC<{
|
||||
<Col span={6}>
|
||||
<Card
|
||||
style={{
|
||||
height: 'calc(100vh - 300px)',
|
||||
height: 'calc(100vh - 288px)',
|
||||
overflow: 'auto',
|
||||
margin: '12px 3px 12px 3px',
|
||||
margin: '6px 0',
|
||||
}}
|
||||
bodyStyle={{ padding: '0 16px 10px 16px' }}
|
||||
ref={configRef}
|
||||
>
|
||||
<Tabs
|
||||
@ -256,8 +296,8 @@ export const ChartConfigure: React.FC<{
|
||||
children: <ChartConfigure.Config />,
|
||||
},
|
||||
{
|
||||
label: t('Transform'),
|
||||
key: 'transform',
|
||||
label: t('Transformation'),
|
||||
key: 'transformation',
|
||||
children: <ChartConfigure.Transform />,
|
||||
},
|
||||
]}
|
||||
@ -265,13 +305,7 @@ export const ChartConfigure: React.FC<{
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<Card
|
||||
style={{
|
||||
margin: '12px 12px 12px 0',
|
||||
}}
|
||||
>
|
||||
<ChartConfigure.Renderer />
|
||||
</Card>
|
||||
<ChartConfigure.Renderer />
|
||||
</Col>
|
||||
</Row>
|
||||
</FormLayout>
|
||||
@ -292,9 +326,18 @@ ChartConfigure.Renderer = function Renderer(props) {
|
||||
const config = cloneDeep(form.values.config);
|
||||
const transform = cloneDeep(form.values.transform);
|
||||
return (
|
||||
<ChartRendererContext.Provider value={{ collection, config, transform, service, data }}>
|
||||
<ChartRenderer {...props} />
|
||||
</ChartRendererContext.Provider>
|
||||
<Card
|
||||
size="small"
|
||||
title={form.values.config?.title}
|
||||
style={{
|
||||
margin: '6px 12px 6px 0',
|
||||
}}
|
||||
bordered={form.values.config?.bordered}
|
||||
>
|
||||
<ChartRendererContext.Provider value={{ collection, config, transform, service, data }}>
|
||||
<ChartRenderer {...props} />
|
||||
</ChartRendererContext.Provider>
|
||||
</Card>
|
||||
);
|
||||
}}
|
||||
</FormConsumer>
|
||||
@ -311,6 +354,7 @@ ChartConfigure.Query = function Query() {
|
||||
const fieldOptions = useCollectionFieldsOptions(dataSource, collection, 1);
|
||||
const compiledFieldOptions = Schema.compile(fieldOptions, { t });
|
||||
const filterOptions = useCollectionFilterOptions(dataSource, collection);
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const { service } = useContext(ChartRendererContext);
|
||||
const onCollectionChange = (value: string[]) => {
|
||||
@ -348,6 +392,7 @@ ChartConfigure.Query = function Query() {
|
||||
onCollectionChange,
|
||||
collection: current?.collection,
|
||||
useOrderReaction: useOrderReaction(compiledFieldOptions, fields),
|
||||
collapsePanelBg: token.colorBgContainer,
|
||||
}}
|
||||
components={{ ArrayItems, Editable, FormCollapse, FormItem, Space, Switch, FromSql, FilterDynamicComponent }}
|
||||
/>
|
||||
@ -373,6 +418,7 @@ ChartConfigure.Config = function Config() {
|
||||
</span>
|
||||
);
|
||||
};
|
||||
const formCollapse = FormCollapse.createFormCollapse(['card', 'basic']);
|
||||
|
||||
return (
|
||||
<FormConsumer>
|
||||
@ -383,8 +429,8 @@ ChartConfigure.Config = function Config() {
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={getConfigSchema(schema)}
|
||||
scope={{ t, chartTypes, useChartFields: getChartFields, getReference }}
|
||||
components={{ FormItem, ArrayItems, Space, AutoComplete }}
|
||||
scope={{ t, chartTypes, useChartFields: getChartFields, getReference, formCollapse }}
|
||||
components={{ FormItem, ArrayItems, Space, AutoComplete, FormCollapse }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
@ -395,14 +441,41 @@ ChartConfigure.Config = function Config() {
|
||||
ChartConfigure.Transform = function Transform() {
|
||||
const { t } = useChartsTranslation();
|
||||
const fields = useFieldsWithAssociation();
|
||||
const useFieldTypeOptions = useFieldTypes(fields);
|
||||
const getChartFields = useChartFields(fields);
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={transformSchema}
|
||||
components={{ FormItem, ArrayItems, Space }}
|
||||
scope={{ useChartFields: getChartFields, useFieldTypeOptions, useTransformers, t }}
|
||||
/>
|
||||
<>
|
||||
<Alert type="info" style={{ marginBottom: '20px' }} message={t('Transformation tip')} closable />
|
||||
<div
|
||||
className={css`
|
||||
.ant-formily-item-feedback-layout-loose {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-space {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<SchemaComponent
|
||||
schema={transformSchema}
|
||||
components={{
|
||||
FormItem,
|
||||
ArrayItems,
|
||||
Space,
|
||||
TransformerDynamicComponent,
|
||||
Select: withDynamicSchemaProps(Select),
|
||||
}}
|
||||
scope={{
|
||||
useChartFields: getChartFields,
|
||||
useTransformers,
|
||||
useTransformerSelectProps,
|
||||
useFieldSelectProps: useFieldSelectProps(fields),
|
||||
useFieldTypeSelectProps,
|
||||
useArgument,
|
||||
t,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { useField } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { SchemaComponent } from '@nocobase/client';
|
||||
import { Field } from '@formily/core';
|
||||
|
||||
export const TransformerDynamicComponent: React.FC<{
|
||||
schema: any;
|
||||
}> = (props) => {
|
||||
const { schema } = props;
|
||||
const field = useField<Field>();
|
||||
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
properties: {
|
||||
[schema.name]: {
|
||||
type: 'void',
|
||||
...schema,
|
||||
value: field.value,
|
||||
'x-component-props': {
|
||||
...schema['x-component-props'],
|
||||
defaultValue: field.value,
|
||||
onChange: (e: any) => {
|
||||
if (typeof e === 'object' && e?.target) {
|
||||
field.value = e.target.value;
|
||||
return;
|
||||
}
|
||||
field.value = e;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -44,48 +44,97 @@ export const getConfigSchema = (general: any): ISchema => ({
|
||||
config: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
chartType: {
|
||||
type: 'string',
|
||||
title: '{{t("Chart type")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
collapse: {
|
||||
type: 'void',
|
||||
'x-component': 'FormCollapse',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Please select a chart type.")}}',
|
||||
formCollapse: '{{formCollapse}}',
|
||||
size: 'small',
|
||||
style: {
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
enum: '{{ chartTypes }}',
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
properties: {
|
||||
general,
|
||||
},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
properties: {
|
||||
advanced: {
|
||||
type: 'json',
|
||||
title: '{{t("JSON config")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
extra: lang('Same properties set in the form above will be overwritten by this JSON config.'),
|
||||
},
|
||||
'x-component': 'Input.JSON',
|
||||
pane1: {
|
||||
type: 'void',
|
||||
'x-component': 'FormCollapse.CollapsePanel',
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 3,
|
||||
header: lang('Container'),
|
||||
key: 'card',
|
||||
},
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: '{{t("Title")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
bordered: {
|
||||
type: 'boolean',
|
||||
'x-content': lang('Show border'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reference: {
|
||||
type: 'string',
|
||||
'x-reactions': {
|
||||
dependencies: ['.chartType'],
|
||||
fulfill: {
|
||||
schema: {
|
||||
'x-content': '{{ getReference($deps[0]) }}',
|
||||
pane2: {
|
||||
type: 'void',
|
||||
'x-component': 'FormCollapse.CollapsePanel',
|
||||
'x-component-props': {
|
||||
header: lang('Chart'),
|
||||
key: 'basic',
|
||||
style: {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
chartType: {
|
||||
type: 'string',
|
||||
title: '{{t("Chart type")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Please select a chart type.")}}',
|
||||
},
|
||||
enum: '{{ chartTypes }}',
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
properties: {
|
||||
general,
|
||||
},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
properties: {
|
||||
advanced: {
|
||||
type: 'json',
|
||||
title: '{{t("JSON config")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
extra: lang('Same properties set in the form above will be overwritten by this JSON config.'),
|
||||
},
|
||||
'x-component': 'Input.JSON',
|
||||
'x-component-props': {
|
||||
autoSize: {
|
||||
minRows: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
reference: {
|
||||
type: 'string',
|
||||
'x-reactions': {
|
||||
dependencies: ['.chartType'],
|
||||
fulfill: {
|
||||
schema: {
|
||||
'x-content': '{{ getReference($deps[0]) }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -171,6 +220,11 @@ export const querySchema: ISchema = {
|
||||
'x-component': 'FormCollapse',
|
||||
'x-component-props': {
|
||||
formCollapse: '{{formCollapse}}',
|
||||
size: 'small',
|
||||
style: {
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
pane1: {
|
||||
@ -326,6 +380,9 @@ export const querySchema: ISchema = {
|
||||
'x-component-props': {
|
||||
header: lang('Sort'),
|
||||
key: 'sort',
|
||||
style: {
|
||||
border: 'none',
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
orders: getArraySchema(
|
||||
@ -457,47 +514,80 @@ export const querySchema: ISchema = {
|
||||
export const transformSchema: ISchema = {
|
||||
type: 'void',
|
||||
properties: {
|
||||
transform: getArraySchema(
|
||||
{
|
||||
field: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Field")}}',
|
||||
style: {
|
||||
maxWidth: '100px',
|
||||
transform: {
|
||||
type: 'array',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
space: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
wrap: true,
|
||||
},
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
field: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Field")}}',
|
||||
},
|
||||
'x-use-component-props': 'useFieldSelectProps',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Type")}}',
|
||||
},
|
||||
'x-use-component-props': 'useFieldTypeSelectProps',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Transformer")}}',
|
||||
style: {
|
||||
maxWidth: '200px',
|
||||
},
|
||||
},
|
||||
'x-use-component-props': 'useTransformerSelectProps',
|
||||
'x-reactions': '{{ useTransformers }}',
|
||||
'x-visible': '{{ $self.dataSource && $self.dataSource.length }}',
|
||||
},
|
||||
argument: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'TransformerDynamicComponent',
|
||||
'x-reactions': '{{ useArgument }}',
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Type")}}',
|
||||
},
|
||||
'x-reactions': '{{ useFieldTypeOptions }}',
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
placeholder: '{{t("Format")}}',
|
||||
},
|
||||
'x-reactions': '{{ useTransformers }}',
|
||||
'x-visible': '{{ $self.dataSource && $self.dataSource.length }}',
|
||||
},
|
||||
},
|
||||
{
|
||||
'x-decorator-props': {
|
||||
style: {
|
||||
width: '50%',
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{t("Add transformation")}}',
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -103,6 +103,9 @@ const EditOperator = () => {
|
||||
if (!operatorList.length) {
|
||||
const names = fieldName.split('.');
|
||||
const name = names.pop();
|
||||
if (names.length < 2) {
|
||||
return null;
|
||||
}
|
||||
props = cm.getCollectionField(names.join('.'));
|
||||
if (!props) {
|
||||
return null;
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChartConfigContext } from '../configure';
|
||||
import formatters from '../block/formatters';
|
||||
import formatters from '../configure/formatters';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { ChartRendererContext } from '../renderer';
|
||||
import { getField, getSelectedFields, parseField, processData } from '../utils';
|
||||
|
@ -1,46 +1,85 @@
|
||||
import transformers from '../block/transformers';
|
||||
import transformers, { Transformer, TransformerConfig } from '../transformers';
|
||||
import { lang } from '../locale';
|
||||
import { ChartRendererProps } from '../renderer';
|
||||
import { getSelectedFields } from '../utils';
|
||||
import { FieldOption } from './query';
|
||||
import { useField } from '@formily/react';
|
||||
import { Field } from '@formily/core';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
/**
|
||||
* useFieldTypes
|
||||
* Get field types for using transformers
|
||||
* Only supported types will be displayed
|
||||
* Some interfaces and types will be mapped to supported types
|
||||
*/
|
||||
export const useFieldTypes = (fields: FieldOption[]) => (field: any) => {
|
||||
const selectedField = field.query('.field').get('value');
|
||||
const query = field.query('query').get('value') || {};
|
||||
const selectedFields = getSelectedFields(fields, query);
|
||||
const fieldProps = selectedFields.find((field) => field.value === selectedField);
|
||||
const supports = Object.keys(transformers);
|
||||
field.dataSource = supports.map((key) => ({
|
||||
export const useFieldSelectProps = (fields: FieldOption[]) =>
|
||||
function useFieldSelectProps() {
|
||||
const field = useField<Field>();
|
||||
const query = field.query('query').get('value') || {};
|
||||
const selectedFields = getSelectedFields(fields, query);
|
||||
const supports = Object.keys(transformers).filter((key) => key !== 'general');
|
||||
return {
|
||||
onChange: (value: string) => {
|
||||
field.value = value;
|
||||
const typeField = field.query('.type').take() as Field;
|
||||
if (!value) {
|
||||
typeField.setState({
|
||||
value: null,
|
||||
disabled: true,
|
||||
});
|
||||
}
|
||||
const fieldProps = selectedFields.find((field) => field.value === value);
|
||||
typeField.dataSource = supports.map((key) => ({
|
||||
label: lang(key),
|
||||
value: key,
|
||||
}));
|
||||
const map = {
|
||||
createdAt: 'datetime',
|
||||
updatedAt: 'datetime',
|
||||
double: 'number',
|
||||
integer: 'number',
|
||||
percent: 'number',
|
||||
};
|
||||
const fieldInterface = fieldProps?.interface;
|
||||
const fieldType = fieldProps?.type;
|
||||
const key = map[fieldInterface] || map[fieldType] || fieldType;
|
||||
if (supports.includes(key)) {
|
||||
typeField.setState({
|
||||
value: key,
|
||||
disabled: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
typeField.setState({
|
||||
value: null,
|
||||
disabled: false,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useFieldTypeSelectProps = () => {
|
||||
const field = useField<Field>();
|
||||
const supports = Object.keys(transformers).filter((key) => key !== 'general');
|
||||
const options = supports.map((key) => ({
|
||||
label: lang(key),
|
||||
value: key,
|
||||
}));
|
||||
const map = {
|
||||
createdAt: 'datetime',
|
||||
updatedAt: 'datetime',
|
||||
double: 'number',
|
||||
integer: 'number',
|
||||
percent: 'number',
|
||||
|
||||
return {
|
||||
options,
|
||||
onChange: (value: string) => {
|
||||
field.value = value;
|
||||
const transformerField = field.query('.format').take() as Field;
|
||||
transformerField.setValue(null);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const useTransformerSelectProps = () => {
|
||||
const field = useField<Field>();
|
||||
return {
|
||||
onChange: (value: string) => {
|
||||
field.value = value;
|
||||
const argumentField = field.query('.argument').take() as Field;
|
||||
argumentField.setValue(null);
|
||||
},
|
||||
};
|
||||
const fieldInterface = fieldProps?.interface;
|
||||
const fieldType = fieldProps?.type;
|
||||
const key = map[fieldInterface] || map[fieldType] || fieldType;
|
||||
if (supports.includes(key)) {
|
||||
field.setState({
|
||||
value: key,
|
||||
disabled: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
field.setState({
|
||||
value: null,
|
||||
disabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTransformers = (field: any) => {
|
||||
@ -49,22 +88,82 @@ export const useTransformers = (field: any) => {
|
||||
field.dataSource = [];
|
||||
return;
|
||||
}
|
||||
const options = Object.keys(transformers[selectedType] || {}).map((key) => ({
|
||||
label: lang(key),
|
||||
value: key,
|
||||
}));
|
||||
const options = Object.entries({ ...transformers.general, ...(transformers[selectedType] || {}) }).map(
|
||||
([key, config]) => {
|
||||
const label = typeof config === 'function' ? key : config.label || key;
|
||||
return {
|
||||
label: lang(label),
|
||||
value: key,
|
||||
};
|
||||
},
|
||||
);
|
||||
field.dataSource = options;
|
||||
};
|
||||
|
||||
export const useArgument = (field: any) => {
|
||||
const selectedType = field.query('.type').get('value');
|
||||
const format = field.query('.format').get('value');
|
||||
if (!format || !selectedType) {
|
||||
field.setComponentProps({
|
||||
schema: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const config = transformers[selectedType][format] || transformers['general'][format];
|
||||
if (!config || typeof config === 'function') {
|
||||
field.setComponentProps({
|
||||
schema: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = uid();
|
||||
field.setComponentProps({
|
||||
schema: {
|
||||
name: id,
|
||||
...config.schema,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useFieldTransformer = (transform: ChartRendererProps['transform'], locale = 'en-US') => {
|
||||
return (transform || [])
|
||||
const transformersMap: {
|
||||
[field: string]: {
|
||||
transformer: TransformerConfig;
|
||||
argument?: string | number;
|
||||
}[];
|
||||
} = (transform || [])
|
||||
.filter((item) => item.field && item.type && item.format)
|
||||
.reduce((mp, item) => {
|
||||
const transformer = transformers[item.type][item.format];
|
||||
const transformer = transformers[item.type][item.format] || transformers.general[item.format];
|
||||
if (!transformer) {
|
||||
return mp;
|
||||
}
|
||||
mp[item.field] = (val: any) => transformer(val, locale);
|
||||
mp[item.field] = [...(mp[item.field] || []), { transformer, argument: item.argument }];
|
||||
return mp;
|
||||
}, {});
|
||||
const result = {};
|
||||
Object.entries(transformersMap).forEach(([field, transformers]) => {
|
||||
result[field] = transformers.reduce(
|
||||
(fn: Transformer, config) => {
|
||||
const { transformer } = config;
|
||||
let { argument } = config;
|
||||
return (val) => {
|
||||
try {
|
||||
if (typeof transformer === 'function') {
|
||||
return transformer(fn(val), argument);
|
||||
}
|
||||
if (!argument && !transformer.schema) {
|
||||
argument = locale;
|
||||
}
|
||||
return transformer.fn(fn(val), argument);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return val;
|
||||
}
|
||||
};
|
||||
},
|
||||
(val) => val,
|
||||
);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
@ -21,6 +21,7 @@ import { createRendererSchema, getField } from '../utils';
|
||||
import { ChartRendererContext } from './ChartRendererProvider';
|
||||
import { useChart } from '../chart/group';
|
||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||
import { Schema } from '@formily/react';
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
export const ChartRenderer: React.FC & {
|
||||
@ -50,6 +51,7 @@ export const ChartRenderer: React.FC & {
|
||||
return props;
|
||||
}, {}),
|
||||
});
|
||||
const compiledProps = Schema.compile(chartProps);
|
||||
const C = chart?.Component;
|
||||
|
||||
if (!chart) {
|
||||
@ -67,7 +69,7 @@ export const ChartRenderer: React.FC & {
|
||||
}}
|
||||
FallbackComponent={ErrorFallback}
|
||||
>
|
||||
<C {...chartProps} />
|
||||
<C {...compiledProps} />
|
||||
</ErrorBoundary>
|
||||
</Spin>
|
||||
);
|
||||
@ -102,7 +104,7 @@ ChartRenderer.Designer = function Designer() {
|
||||
>
|
||||
{t('Duplicate')}
|
||||
</SchemaSettingsItem>
|
||||
<SchemaSettingsBlockTitleItem />
|
||||
{/* <SchemaSettingsBlockTitleItem /> */}
|
||||
<SchemaSettingsDivider />
|
||||
<SchemaSettingsRemove
|
||||
// removeParentsIfNoChildren
|
||||
|
@ -30,6 +30,7 @@ export type TransformProps = {
|
||||
field: string;
|
||||
type: string;
|
||||
format: string;
|
||||
argument?: string | number;
|
||||
};
|
||||
|
||||
export type QueryProps = Partial<{
|
||||
|
@ -0,0 +1,232 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export type Transformer = (val: any, ...args: any[]) => string | number;
|
||||
export type TransformerConfig =
|
||||
| Transformer
|
||||
| {
|
||||
label?: string;
|
||||
schema?: any;
|
||||
fn: Transformer;
|
||||
};
|
||||
|
||||
const transformers: {
|
||||
[key: string]: {
|
||||
[key: string]: TransformerConfig;
|
||||
};
|
||||
} = {
|
||||
general: {
|
||||
Prefix: {
|
||||
schema: {
|
||||
'x-component': 'Input',
|
||||
},
|
||||
fn: (val: string, prefix: string) => (prefix ? `${prefix}${val}` : val),
|
||||
},
|
||||
Suffix: {
|
||||
schema: {
|
||||
'x-component': 'Input',
|
||||
},
|
||||
fn: (val: string, suffix: string) => (suffix ? `${val}${suffix}` : val),
|
||||
},
|
||||
},
|
||||
datetime: {
|
||||
Format: {
|
||||
schema: {
|
||||
'x-component': 'AutoComplete',
|
||||
'x-component-props': {
|
||||
allowClear: true,
|
||||
style: {
|
||||
minWidth: 200,
|
||||
},
|
||||
},
|
||||
enum: [
|
||||
{ label: 'YYYY', value: 'YYYY' },
|
||||
{ label: 'YYYY-MM', value: 'YYYY-MM' },
|
||||
{ label: 'YYYY-MM-DD', value: 'YYYY-MM-DD' },
|
||||
{ label: 'YYYY-MM-DD hh:mm', value: 'YYYY-MM-DD hh:mm' },
|
||||
{ label: 'YYYY-MM-DD hh:mm:ss', value: 'YYYY-MM-DD hh:mm:ss' },
|
||||
],
|
||||
},
|
||||
fn: (val: string, format: string) => dayjs(val).format(format),
|
||||
},
|
||||
YYYY: {
|
||||
label: 'YYYY (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY'),
|
||||
},
|
||||
MM: {
|
||||
label: 'MM (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('MM'),
|
||||
},
|
||||
DD: {
|
||||
label: 'DD (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('DD'),
|
||||
},
|
||||
'YYYY-MM': {
|
||||
label: 'YYYY-MM (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM'),
|
||||
},
|
||||
'YYYY-MM-DD': {
|
||||
label: 'YYYY-MM-DD (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM-DD'),
|
||||
},
|
||||
'YYYY-MM-DD hh:mm': {
|
||||
label: 'YYYY-MM-DD hh:mm (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM-DD hh:mm'),
|
||||
},
|
||||
'YYYY-MM-DD hh:mm:ss': {
|
||||
label: 'YYYY-MM-DD hh:mm:ss (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM-DD hh:mm:ss'),
|
||||
},
|
||||
},
|
||||
date: {
|
||||
Format: {
|
||||
schema: {
|
||||
'x-component': 'AutoComplete',
|
||||
'x-component-props': {
|
||||
allowClear: true,
|
||||
style: {
|
||||
minWidth: 200,
|
||||
},
|
||||
},
|
||||
enum: [
|
||||
{ label: 'YYYY', value: 'YYYY' },
|
||||
{ label: 'MM', value: 'MM' },
|
||||
{ label: 'DD', value: 'DD' },
|
||||
{ label: 'YYYY-MM', value: 'YYYY-MM' },
|
||||
{ label: 'YYYY-MM-DD', value: 'YYYY-MM-DD' },
|
||||
],
|
||||
},
|
||||
fn: (val: string, format: string) => dayjs(val).format(format),
|
||||
},
|
||||
YYYY: {
|
||||
label: 'YYYY (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY'),
|
||||
},
|
||||
MM: {
|
||||
label: 'MM (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('MM'),
|
||||
},
|
||||
DD: {
|
||||
label: 'DD (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('DD'),
|
||||
},
|
||||
'YYYY-MM': {
|
||||
label: 'YYYY-MM (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM'),
|
||||
},
|
||||
'YYYY-MM-DD': {
|
||||
label: 'YYYY-MM-DD (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('YYYY-MM-DD'),
|
||||
},
|
||||
},
|
||||
time: {
|
||||
Format: {
|
||||
schema: {
|
||||
'x-component': 'AutoComplete',
|
||||
'x-component-props': {
|
||||
allowClear: true,
|
||||
style: {
|
||||
minWidth: 200,
|
||||
},
|
||||
},
|
||||
enum: [
|
||||
{ label: 'hh:mm:ss', value: 'hh:mm:ss' },
|
||||
{ label: 'hh:mm', value: 'hh:mm' },
|
||||
{ label: 'hh', value: 'hh' },
|
||||
],
|
||||
},
|
||||
fn: (val: string, format: string) => dayjs(val).format(format),
|
||||
},
|
||||
'hh:mm:ss': {
|
||||
label: 'hh:mm:ss (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('hh:mm:ss'),
|
||||
},
|
||||
'hh:mm': {
|
||||
label: 'hh:mm (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('hh:mm'),
|
||||
},
|
||||
hh: {
|
||||
label: 'hh (Deprecated, use Format instead)',
|
||||
fn: (val: string) => dayjs(val).format('hh'),
|
||||
},
|
||||
},
|
||||
number: {
|
||||
Precision: {
|
||||
schema: {
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: '1', value: 0 },
|
||||
{ label: '1.0', value: 1 },
|
||||
{ label: '1.00', value: 2 },
|
||||
{ label: '1.000', value: 3 },
|
||||
],
|
||||
},
|
||||
fn: (val: number, precision: number) => val.toFixed(precision),
|
||||
},
|
||||
Separator: {
|
||||
schema: {
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: '100,000.00', value: 'en-US' },
|
||||
{ label: '100.000,00', value: 'de-DE' },
|
||||
{ label: '100 000.00', value: 'ru-RU' },
|
||||
],
|
||||
},
|
||||
fn: (val: number, separator: string) => {
|
||||
switch (separator) {
|
||||
case 'en-US':
|
||||
return val.toLocaleString('en-US', { maximumFractionDigits: 2 });
|
||||
case 'de-DE':
|
||||
return val.toLocaleString('de-DE', { maximumFractionDigits: 2 });
|
||||
case 'ru-RU':
|
||||
return val.toLocaleString('ru-RU', { maximumFractionDigits: 2 });
|
||||
default:
|
||||
return val;
|
||||
}
|
||||
},
|
||||
},
|
||||
Percent: (val: number) =>
|
||||
new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(
|
||||
val,
|
||||
),
|
||||
Currency: {
|
||||
label: 'Currency (Deprecated, use Separator & Prefix instead)',
|
||||
fn: (val: number, locale = 'en-US') => {
|
||||
const currency =
|
||||
{
|
||||
'zh-CN': 'CNY',
|
||||
'en-US': 'USD',
|
||||
'ja-JP': 'JPY',
|
||||
'ko-KR': 'KRW',
|
||||
'pt-BR': 'BRL',
|
||||
'ru-RU': 'RUB',
|
||||
'tr-TR': 'TRY',
|
||||
'es-ES': 'EUR',
|
||||
}[locale] || 'USD';
|
||||
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(val);
|
||||
},
|
||||
},
|
||||
Exponential: (val: number | string) => (+val)?.toExponential(),
|
||||
Abbreviation: (val: number, locale = 'en-US') => new Intl.NumberFormat(locale, { notation: 'compact' }).format(val),
|
||||
},
|
||||
string: {
|
||||
'Type conversion': {
|
||||
schema: {
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: 'Number', value: 'Number' },
|
||||
{ label: 'Date', value: 'Date' },
|
||||
],
|
||||
},
|
||||
fn: (val: number, targetType: string) => {
|
||||
try {
|
||||
return new Function(`return ${targetType}(${val})`)();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return val;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default transformers;
|
@ -6,7 +6,8 @@ import { FieldOption } from './hooks';
|
||||
import { QueryProps } from './renderer';
|
||||
|
||||
export const createRendererSchema = (decoratorProps: any, componentProps = {}) => {
|
||||
const { collection } = decoratorProps;
|
||||
const { collection, config } = decoratorProps;
|
||||
const { title, bordered } = config || {};
|
||||
return {
|
||||
type: 'void',
|
||||
'x-decorator': 'ChartRendererProvider',
|
||||
@ -16,6 +17,8 @@ export const createRendererSchema = (decoratorProps: any, componentProps = {}) =
|
||||
'x-component': 'CardItem',
|
||||
'x-component-props': {
|
||||
size: 'small',
|
||||
title,
|
||||
bordered,
|
||||
},
|
||||
'x-initializer': 'charts:addBlock',
|
||||
properties: {
|
||||
|
@ -80,5 +80,12 @@
|
||||
"Time range": "Time range",
|
||||
"Edit field properties": "Edit field properties",
|
||||
"Select a source field to use metadata of the field": "Select a source field to use metadata of the field",
|
||||
"Original field": "Original field"
|
||||
"Original field": "Original field",
|
||||
"Transformation": "Transformation",
|
||||
"Add transformation": "Add transformation",
|
||||
"Container": "Container",
|
||||
"Show border": "Show border",
|
||||
"Transformation tip": "Fields allow multiple transformations, applied sequentially. Pay attention to data type changes after each transformation. Drag-and-drop functionality enables adjustment of transformation order.",
|
||||
"Type conversion": "Type conversion",
|
||||
"Transformer": "Transformer"
|
||||
}
|
||||
|
@ -81,5 +81,12 @@
|
||||
"Time range": "时间范围",
|
||||
"Edit field properties": "编辑字段属性",
|
||||
"Select a source field to use metadata of the field": "选择来源字段可以复用字段的元数据配置",
|
||||
"Original field": "原始字段"
|
||||
"Original field": "原始字段",
|
||||
"Transformation": "数据转换",
|
||||
"Add transformation": "添加数据转换",
|
||||
"Container": "容器",
|
||||
"Show border": "显示边框",
|
||||
"Transformation tip": "一个字段可以应用多次转换,会按照顺序执行,请注意每次转换后的数据类型,拖动可以调整转换顺序。",
|
||||
"Type conversion": "类型转换",
|
||||
"Transformer": "转换方法"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user