feat: add chart plugin (#1477)
* feat: add pie schema template * chore: refactor * chore: update * chore: init server * feat: add pie schema template * chore: add chart utils * chore: update * chore: update * chore: update * chore: update * feat: mvp * chore: update * chore: test * feat: example * fix: templates map error * chore: update * feat: add ChartBlockEngineDesigner.tsx * chore: clean code * chore: update templates * chore: init bat template * chore: update chart block engine * feat: add chart block engine designer * chore: update chart block initializer * chore: update pie template * chore: update * feat: split sql * chore: update * chore: clean * feat: support bar * chore: add bar template * chore: clean code * chore: clean code * fix: chart block initializer title * chore: update * fix: use title * chore: update * fix: bugs * chore: clean code * chore: update pie template * chore: update ChartBlockInitializer.tsx * chore: update * feat: support chart config * chore: add local plugin * feat: add column template * feat: add column chart action * feat: add line chart template * feat: add line action * feat: add area template * feat: add area action * chore: update pie template * chore: update * chore: update dep * fix: export missing utils * chore: update dep * chore: update dep * chore: update pie template * fix(charts): sql table capitalization * fix(charts): improve sql compatibility * fix: support choices type * chore: update * chore: clean code * chore: get collection fields in chart engine * feat(demo): chart block initializer * fix: hello block * chore: init data analyse plugin * chore: add data-analyse plugin * chore: init plugin * chore: update * chore: export schema initializers * feat: add data analyse block * chore: update data analyse block initializer * chore: rename plugin name * chore: update * chore: update enum * chore: update add block * feat: add pie schema template * chore: refactor * chore: update * chore: init server * feat: add pie schema template * chore: add chart utils * chore: update * chore: update * chore: update * chore: update * feat: mvp * chore: update * chore: test * feat: example * fix: templates map error * chore: update * feat: add ChartBlockEngineDesigner.tsx * chore: clean code * chore: update templates * chore: init bat template * chore: update chart block engine * feat: add chart block engine designer * chore: update chart block initializer * chore: update pie template * chore: update * feat: split sql * chore: update * chore: clean * feat: support bar * chore: add bar template * chore: clean code * chore: clean code * fix: chart block initializer title * chore: update * fix: use title * chore: update * fix: bugs * chore: clean code * chore: update pie template * chore: update ChartBlockInitializer.tsx * chore: update * feat: support chart config * chore: add local plugin * feat: add column template * feat: add column chart action * feat: add line chart template * feat: add line action * feat: add area template * feat: add area action * chore: update pie template * chore: update * chore: update dep * fix: export missing utils * chore: update dep * chore: update dep * chore: update pie template * fix(charts): sql table capitalization * fix(charts): improve sql compatibility * fix: support choices type * chore: update * chore: clean code * chore: get collection fields in chart engine * chore: init dataset plugin * chore: init dataset designer * chore: init dataset block initializer * chore: update * chore: update * chore: update deps * chore: add tests * chore: update utils * chore: init plugin * feat: add datasets client * chore: update tests * chore: update datastructures * chore: update * feat: add DataSetBlockInitializer * chore: use dataset initializer * chore: update deps * chore: update * chore: update pie template * chore: update ChartBlockInitializer * chore: update ChartBlockEngine * chore: remove mdg file * chore: update ChartBlockEngine * chore: update * chore: update g2plot * chore: update template * chore: update pie handler * chore: update DataSetPreviewTable * chore: update ChartBlockEngine * chore: update ChartBlockEngineDesigner * chore: remove useless files * chore: update ChartBlockInitializer * chore: update template * chore: feat area template * chore: update tests * chore: update local plugins * chore: update deps * chore: update templates * chore: update template * feat: add CustomSelect * chore: update select component * fix: seriesField color error * chore: update template * chore: update * fix: bar * chore: update templates & clean code * chore: update templates * feat: add FunnelTemplate * feat: charts queries * fix: data shift * feat: add ChartQueryMetadataProvider * chore: update * chore: remove dataset * chore: update * chore: update * chore: clean code * fix: delete query query list wrong behavior * fix: delete all queries query list behavior * chore: add empty occupancy * chore: update description * chore: update chart icon * chore: update funnel template * chore: update template title * chore: add references * chore: update dialog height * fix: scatter template * chore: update popup description * refactor: schema level * refactor: charts schema * chore: remove console * chore: remove console * fix: error message * chore: remove console * chore: update DataSetPreviewTable * feat: chart icons * feat: update i18n * feat: update i18n * chore: update i18n * chore: update i18n * chore: update i18n * feat: add tittle * chore: update icons * chore: update i18n * chore: update i18n * chore: update i18n * chore: update i18n * chore: update i18n * fix: improve code * chore: update i18n * chore: update i18n * fix: chart icon * feat: quick add query (#1527) * feat: quick add query * feat: query * chore: remove useless files * fix: json5 validator error * chore: update * chore: update style * chore: lint * feat: improve code * feat: sql validation * refactor: clean code * Update utils.ts * chore: format code * chore: remove useless file * chore: update --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
555378c342
commit
b276ce1258
1
packages/app/client/src/plugins/charts.ts
Normal file
1
packages/app/client/src/plugins/charts.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from '@nocobase/plugin-charts/client';
|
@ -33,7 +33,7 @@ import {
|
|||||||
Venn,
|
Venn,
|
||||||
Violin,
|
Violin,
|
||||||
Waterfall,
|
Waterfall,
|
||||||
WordCloud
|
WordCloud,
|
||||||
} from '@antv/g2plot';
|
} from '@antv/g2plot';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { observer, useField } from '@formily/react';
|
import { observer, useField } from '@formily/react';
|
||||||
@ -90,7 +90,6 @@ const plots = {
|
|||||||
|
|
||||||
export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotProps<O>, ref: any) {
|
export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotProps<O>, ref: any) {
|
||||||
const { className, plot, config } = props;
|
const { className, plot, config } = props;
|
||||||
|
|
||||||
const containerRef = useRef(undefined);
|
const containerRef = useRef(undefined);
|
||||||
const plotRef = useRef(undefined);
|
const plotRef = useRef(undefined);
|
||||||
|
|
||||||
@ -107,7 +106,7 @@ export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotPr
|
|||||||
plotRef.current.update(config);
|
plotRef.current.update(config);
|
||||||
} else {
|
} else {
|
||||||
plotRef.current = new plot(containerRef.current, config);
|
plotRef.current = new plot(containerRef.current, config);
|
||||||
plotRef.current.render();
|
plotRef?.current?.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
syncRef(plotRef, ref);
|
syncRef(plotRef, ref);
|
||||||
@ -122,7 +121,6 @@ export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotPr
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderPlot();
|
renderPlot();
|
||||||
|
|
||||||
return () => destoryPlot();
|
return () => destoryPlot();
|
||||||
}, [config, plot]);
|
}, [config, plot]);
|
||||||
|
|
||||||
@ -154,6 +152,7 @@ export const G2Plot: any = observer((props: any) => {
|
|||||||
field.data.loading = false;
|
field.data.loading = false;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!plot || !config) {
|
if (!plot || !config) {
|
||||||
return <div style={{ opacity: 0.3 }}>{t('In configuration')}...</div>;
|
return <div style={{ opacity: 0.3 }}>{t('In configuration')}...</div>;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export const Tabs: any = observer((props: TabsProps) => {
|
|||||||
return (
|
return (
|
||||||
<DndContext>
|
<DndContext>
|
||||||
<AntdTabs
|
<AntdTabs
|
||||||
|
style={props.style}
|
||||||
tabBarExtraContent={{
|
tabBarExtraContent={{
|
||||||
right: render(),
|
right: render(),
|
||||||
}}
|
}}
|
||||||
|
@ -191,7 +191,11 @@ SchemaInitializer.Item = (props: SchemaInitializerItemProps) => {
|
|||||||
key={item.key}
|
key={item.key}
|
||||||
onClick={(info) => {
|
onClick={(info) => {
|
||||||
item?.clearKeywords?.();
|
item?.clearKeywords?.();
|
||||||
onClick({ ...info, item });
|
if (item.onClick) {
|
||||||
|
item.onClick({ ...info, item });
|
||||||
|
} else {
|
||||||
|
onClick({ ...info, item });
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{compile(item.title)}
|
{compile(item.title)}
|
||||||
|
@ -44,115 +44,6 @@ export const BlockInitializers = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'g2plot',
|
|
||||||
type: 'itemGroup',
|
|
||||||
title: '{{t("Chart blocks")}}',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
key: 'column',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Column chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'BarChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
plot: 'Column',
|
|
||||||
config: chartConfig.column,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'bar',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Bar chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'BarChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
plot: 'Bar',
|
|
||||||
config: chartConfig.bar,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'line',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Line chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'LineChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
plot: 'Line',
|
|
||||||
config: chartConfig.line,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'pie',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Pie chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'PieChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
plot: 'Pie',
|
|
||||||
config: chartConfig.pie,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'area',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Area chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'AreaChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
plot: 'Area',
|
|
||||||
config: chartConfig.area,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'other',
|
|
||||||
type: 'item',
|
|
||||||
title: '{{t("Other chart")}}',
|
|
||||||
component: 'G2PlotInitializer',
|
|
||||||
icon: 'AreaChartOutlined',
|
|
||||||
schema: {
|
|
||||||
type: 'void',
|
|
||||||
'x-designer': 'G2Plot.Designer',
|
|
||||||
'x-decorator': 'CardItem',
|
|
||||||
'x-component': 'G2Plot',
|
|
||||||
'x-component-props': {
|
|
||||||
// plot: 'Area',
|
|
||||||
// config: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'media',
|
key: 'media',
|
||||||
type: 'itemGroup',
|
type: 'itemGroup',
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
export * from '../../schema-component/antd/association-filter/ActionBarAssociationFilterAction';
|
||||||
|
// association filter
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilter';
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDelete';
|
||||||
|
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDisplayField';
|
||||||
export * from './ActionInitializer';
|
export * from './ActionInitializer';
|
||||||
export * from './BlockInitializer';
|
export * from './BlockInitializer';
|
||||||
export * from './BulkDestroyActionInitializer';
|
export * from './BulkDestroyActionInitializer';
|
||||||
@ -37,8 +42,3 @@ export * from './TableSelectorInitializer';
|
|||||||
export * from './UpdateActionInitializer';
|
export * from './UpdateActionInitializer';
|
||||||
export * from './UpdateSubmitActionInitializer';
|
export * from './UpdateSubmitActionInitializer';
|
||||||
export * from './ViewActionInitializer';
|
export * from './ViewActionInitializer';
|
||||||
// association filter
|
|
||||||
export * from '../../schema-component/antd/association-filter/AssociationFilter';
|
|
||||||
export * from '../../schema-component/antd/association-filter/ActionBarAssociationFilterAction';
|
|
||||||
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDisplayField';
|
|
||||||
export * from '../../schema-component/antd/association-filter/AssociationFilterDesignerDelete';
|
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
Sequelize,
|
Sequelize,
|
||||||
SyncOptions,
|
SyncOptions,
|
||||||
Transactionable,
|
Transactionable,
|
||||||
Utils,
|
Utils
|
||||||
} from 'sequelize';
|
} from 'sequelize';
|
||||||
import { SequelizeStorage, Umzug } from 'umzug';
|
import { SequelizeStorage, Umzug } from 'umzug';
|
||||||
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
||||||
@ -58,7 +58,7 @@ import {
|
|||||||
SyncListener,
|
SyncListener,
|
||||||
UpdateListener,
|
UpdateListener,
|
||||||
UpdateWithAssociationsListener,
|
UpdateWithAssociationsListener,
|
||||||
ValidateListener,
|
ValidateListener
|
||||||
} from './types';
|
} from './types';
|
||||||
import { patchSequelizeQueryInterface, snakeCase } from './utils';
|
import { patchSequelizeQueryInterface, snakeCase } from './utils';
|
||||||
|
|
||||||
@ -374,6 +374,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
return dialect.includes(this.sequelize.getDialect());
|
return dialect.includes(this.sequelize.getDialect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
escapeId(identifier: string) {
|
||||||
|
return this.inDialect('mysql') ? `\`${identifier}\`` : `"${identifier}"`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add collection to database
|
* Add collection to database
|
||||||
* @param options
|
* @param options
|
||||||
|
4
packages/plugins/charts/client.d.ts
vendored
Executable file
4
packages/plugins/charts/client.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/client';
|
||||||
|
export { default } from './lib/client';
|
||||||
|
|
30
packages/plugins/charts/client.js
Executable file
30
packages/plugins/charts/client.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
var _index = _interopRequireWildcard(require("./lib/client"));
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
var _exportNames = {};
|
||||||
|
Object.defineProperty(exports, "default", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function get() {
|
||||||
|
return _index.default;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(_index).forEach(function (key) {
|
||||||
|
if (key === "default" || key === "__esModule") return;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||||
|
if (key in exports && exports[key] === _index[key]) return;
|
||||||
|
Object.defineProperty(exports, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get: function get() {
|
||||||
|
return _index[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
13
packages/plugins/charts/package.json
Normal file
13
packages/plugins/charts/package.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@nocobase/plugin-charts",
|
||||||
|
"version": "0.9.1-alpha.1",
|
||||||
|
"main": "lib/server/index.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"@nocobase/server": "0.9.1-alpha.1",
|
||||||
|
"@nocobase/test": "0.9.1-alpha.1",
|
||||||
|
"@nocobase/client": "^0.9.1-alpha.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"json5": "^2.2.3"
|
||||||
|
}
|
||||||
|
}
|
4
packages/plugins/charts/server.d.ts
vendored
Executable file
4
packages/plugins/charts/server.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
export * from './lib/server';
|
||||||
|
export { default } from './lib/server';
|
||||||
|
|
30
packages/plugins/charts/server.js
Executable file
30
packages/plugins/charts/server.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||||
|
|
||||||
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||||
|
|
||||||
|
var _index = _interopRequireWildcard(require("./lib/server"));
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", {
|
||||||
|
value: true
|
||||||
|
});
|
||||||
|
var _exportNames = {};
|
||||||
|
Object.defineProperty(exports, "default", {
|
||||||
|
enumerable: true,
|
||||||
|
get: function get() {
|
||||||
|
return _index.default;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(_index).forEach(function (key) {
|
||||||
|
if (key === "default" || key === "__esModule") return;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||||
|
if (key in exports && exports[key] === _index[key]) return;
|
||||||
|
Object.defineProperty(exports, key, {
|
||||||
|
enumerable: true,
|
||||||
|
get: function get() {
|
||||||
|
return _index[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
120
packages/plugins/charts/src/client/ChartBlockEngine.tsx
Normal file
120
packages/plugins/charts/src/client/ChartBlockEngine.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { useCompile, useRequest } from '@nocobase/client';
|
||||||
|
import { Empty, Spin } from 'antd';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ChartBlockEngineDesigner } from './ChartBlockEngineDesigner';
|
||||||
|
import chartRenderComponentsMap from './chartRenderComponents';
|
||||||
|
import { lang } from './locale';
|
||||||
|
import { templates } from './templates';
|
||||||
|
|
||||||
|
export interface IQueryConfig {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
export interface IChartConfig {
|
||||||
|
type: string;
|
||||||
|
template: string;
|
||||||
|
metric: string;
|
||||||
|
dimension: string;
|
||||||
|
category?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChartBlockEngineMetaData {
|
||||||
|
query: IQueryConfig;
|
||||||
|
chart: IChartConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartRenderComponent = ({
|
||||||
|
chartBlockEngineMetaData,
|
||||||
|
}: {
|
||||||
|
chartBlockEngineMetaData: ChartBlockEngineMetaData;
|
||||||
|
}): JSX.Element => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const chartType = chartBlockEngineMetaData.chart.type;
|
||||||
|
const renderComponent = templates.get(chartType)?.renderComponent;
|
||||||
|
const RenderComponent = chartRenderComponentsMap.get(renderComponent); //G2Plot | Echarts | D3 |Table
|
||||||
|
const chartConfig = chartBlockEngineMetaData.chart;
|
||||||
|
const { loading, dataSet, error } = useGetDataSet(chartBlockEngineMetaData.query.id);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Empty description={<span>May be this chart block's query data has been deleted,please check!</span>} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [currentConfig, setCurrentConfig] = useState<IChartConfig>({} as any);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentConfig(chartConfig);
|
||||||
|
}, [JSON.stringify(chartConfig)]);
|
||||||
|
|
||||||
|
if (currentConfig.type !== chartConfig.type) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (renderComponent) {
|
||||||
|
case 'G2Plot': {
|
||||||
|
let finalChartOptions;
|
||||||
|
finalChartOptions = templates.get(chartType)?.defaultChartOptions;
|
||||||
|
let template;
|
||||||
|
try {
|
||||||
|
template = JSON5.parse(chartConfig?.template);
|
||||||
|
} catch (e) {
|
||||||
|
template = {};
|
||||||
|
}
|
||||||
|
const config = compile(
|
||||||
|
{
|
||||||
|
...finalChartOptions,
|
||||||
|
...template,
|
||||||
|
data: dataSet,
|
||||||
|
},
|
||||||
|
{ ...chartConfig, category: chartConfig?.category ?? '' },
|
||||||
|
);
|
||||||
|
if (config && chartConfig) {
|
||||||
|
const { dimension, metric, category } = chartConfig;
|
||||||
|
if (!metric || !dimension) {
|
||||||
|
return <>{lang('Please check the chart config')}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <>{loading ? <Spin /> : <RenderComponent plot={chartConfig.type} config={config} />}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetDataSet = (chartQueryId: number) => {
|
||||||
|
const { data, loading, error } = useRequest({
|
||||||
|
url: `/chartsQueries:getData/${chartQueryId}`,
|
||||||
|
});
|
||||||
|
const dataSet = data?.data;
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
dataSet: dataSet,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChartBlockEngine = ({ chartBlockEngineMetaData }: { chartBlockEngineMetaData: ChartBlockEngineMetaData }) => {
|
||||||
|
let renderComponent;
|
||||||
|
const chartType = chartBlockEngineMetaData?.chart?.type;
|
||||||
|
|
||||||
|
if (chartType) {
|
||||||
|
renderComponent = templates.get(chartType)?.renderComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chartType || !renderComponent) {
|
||||||
|
return <>{lang('Please check the chart config')}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ChartRenderComponent chartBlockEngineMetaData={chartBlockEngineMetaData} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ChartBlockEngine.Designer = ChartBlockEngineDesigner;
|
||||||
|
|
||||||
|
export { ChartBlockEngine };
|
238
packages/plugins/charts/src/client/ChartBlockEngineDesigner.tsx
Normal file
238
packages/plugins/charts/src/client/ChartBlockEngineDesigner.tsx
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { SchemaOptionsContext, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import {
|
||||||
|
APIClientProvider,
|
||||||
|
GeneralSchemaDesigner,
|
||||||
|
i18n,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
SchemaSettings,
|
||||||
|
useAPIClient,
|
||||||
|
useCompile,
|
||||||
|
useDesignable,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ChartBlockEngineMetaData } from './ChartBlockEngine';
|
||||||
|
import { Options } from './ChartBlockInitializer';
|
||||||
|
import DataSetPreviewTable from './DataSetPreviewTable';
|
||||||
|
import { useFieldsById } from './hooks';
|
||||||
|
import { lang } from './locale';
|
||||||
|
import { templates } from './templates';
|
||||||
|
|
||||||
|
export const jsonConfigDesc = (title: string, link: string) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{lang('Json config references: ')}
|
||||||
|
<a href={link} target="_blank">
|
||||||
|
{lang(title)}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateJSON = {
|
||||||
|
validator: `{{(value, rule)=> {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const val = JSON.parse(value);
|
||||||
|
if(!isNaN(val)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}}`,
|
||||||
|
message: '{{t("Invalid JSON format",{ ns: "charts" })}}',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChartBlockEngineDesigner = () => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const { chartBlockEngineMetaData } = fieldSchema?.['x-component-props'];
|
||||||
|
return (
|
||||||
|
<GeneralSchemaDesigner>
|
||||||
|
<ChartBlockEngineDesignerInitializer chartBlockEngineMetaData={chartBlockEngineMetaData} />
|
||||||
|
<SchemaSettings.Divider />
|
||||||
|
<SchemaSettings.Remove
|
||||||
|
removeParentsIfNoChildren
|
||||||
|
breakRemoveOn={{
|
||||||
|
'x-component': 'Grid',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GeneralSchemaDesigner>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChartBlockEngineDesignerInitializer = (props) => {
|
||||||
|
const { chartBlockEngineMetaData }: { chartBlockEngineMetaData: ChartBlockEngineMetaData } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const options = useContext(SchemaOptionsContext);
|
||||||
|
const { dn } = useDesignable();
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const field = useField();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { chart, query } = chartBlockEngineMetaData;
|
||||||
|
const { fields } = useFieldsById(query.id);
|
||||||
|
const dataSource = fields.map((field) => {
|
||||||
|
return {
|
||||||
|
label: field.name,
|
||||||
|
value: field.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<SchemaSettings.Item
|
||||||
|
onClick={async () => {
|
||||||
|
FormDialog(
|
||||||
|
{
|
||||||
|
okText: compile('{{t("Submit")}}'),
|
||||||
|
title: lang('Edit chart block'),
|
||||||
|
width: 1200,
|
||||||
|
bodyStyle: { background: '#f0f2f5', maxHeight: '65vh', overflow: 'auto' },
|
||||||
|
},
|
||||||
|
(form) => {
|
||||||
|
const [chartBlockEngineMetaData, setChartBlockEngineMetaData] = useState<ChartBlockEngineMetaData>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const chartBlockEngineMetaData = {
|
||||||
|
query: {
|
||||||
|
id: query?.id,
|
||||||
|
},
|
||||||
|
chart: form.values, //TODO
|
||||||
|
};
|
||||||
|
setChartBlockEngineMetaData(chartBlockEngineMetaData);
|
||||||
|
}, [form.values.type]);
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={api}>
|
||||||
|
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||||
|
<section
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* left*/}
|
||||||
|
<Card
|
||||||
|
bordered={false}
|
||||||
|
title={i18n.t('Chart config')}
|
||||||
|
size={'default'}
|
||||||
|
className={css`
|
||||||
|
flex: 1;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaComponent
|
||||||
|
scope={{ dataSource, JSON5, jsonConfigDesc }}
|
||||||
|
components={{ Options }}
|
||||||
|
schema={{
|
||||||
|
properties: {
|
||||||
|
// title: {
|
||||||
|
// title: lang('Chart title'),
|
||||||
|
// 'x-component': 'Input',
|
||||||
|
// 'x-decorator': 'FormItem',
|
||||||
|
// },
|
||||||
|
type: {
|
||||||
|
title: t('Chart type'),
|
||||||
|
required: true,
|
||||||
|
'x-component': 'CustomSelect',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: [...templates.values()].map((template) => {
|
||||||
|
return {
|
||||||
|
title: template.title,
|
||||||
|
key: template.type,
|
||||||
|
description: template.description,
|
||||||
|
group: template.group,
|
||||||
|
iconId: template.iconId,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Options',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
</Card>
|
||||||
|
{/* right*/}
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
flex: 1;
|
||||||
|
min-width: 600px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Card size={'default'} title={lang('Chart preview')}>
|
||||||
|
{/* Chart Preview*/}
|
||||||
|
{chartBlockEngineMetaData && (
|
||||||
|
<>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
properties: {
|
||||||
|
chartPreview: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'CardItem',
|
||||||
|
'x-component': 'ChartBlockEngine',
|
||||||
|
'x-component-props': {
|
||||||
|
chartBlockEngineMetaData: chartBlockEngineMetaData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
size={'default'}
|
||||||
|
title={lang('Data preview')}
|
||||||
|
className={css`
|
||||||
|
margin-top: 24px;
|
||||||
|
overflow: scroll;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/*Data preview*/}
|
||||||
|
{chartBlockEngineMetaData?.query?.id && (
|
||||||
|
<DataSetPreviewTable queryId={chartBlockEngineMetaData?.query?.id} fields={fields} />
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.open({
|
||||||
|
initialValues: { ...chart }, //reset before chartBlockMetaData
|
||||||
|
})
|
||||||
|
.then((values) => {
|
||||||
|
//patch updates
|
||||||
|
values = {
|
||||||
|
query,
|
||||||
|
chart: values,
|
||||||
|
};
|
||||||
|
field.title = values.chart.title;
|
||||||
|
fieldSchema['title'] = values.chart.title;
|
||||||
|
field.componentProps.chartBlockEngineMetaData = values;
|
||||||
|
fieldSchema['x-component-props'].chartBlockEngineMetaData = values;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema: {
|
||||||
|
'x-uid': fieldSchema['x-uid'],
|
||||||
|
'x-component-props': fieldSchema['x-component-props'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children || props.title || lang('Edit chart block')}
|
||||||
|
</SchemaSettings.Item>
|
||||||
|
);
|
||||||
|
};
|
216
packages/plugins/charts/src/client/ChartBlockInitializer.tsx
Normal file
216
packages/plugins/charts/src/client/ChartBlockInitializer.tsx
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { observer, RecursionField, Schema, SchemaOptionsContext, useField, useForm } from '@formily/react';
|
||||||
|
import {
|
||||||
|
APIClientProvider,
|
||||||
|
FormProvider,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
useAPIClient,
|
||||||
|
useCompile,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { ChartBlockEngineMetaData } from './ChartBlockEngine';
|
||||||
|
import { jsonConfigDesc } from './ChartBlockEngineDesigner';
|
||||||
|
import { ChartQueryBlockInitializer, ChartQueryMetadata } from './ChartQueryBlockInitializer';
|
||||||
|
import DataSetPreviewTable from './DataSetPreviewTable';
|
||||||
|
import { lang, useChartsTranslation } from './locale';
|
||||||
|
import { templates } from './templates';
|
||||||
|
|
||||||
|
export const Options = observer((props) => {
|
||||||
|
const form = useForm<ChartFormInterface>();
|
||||||
|
const field = useField<Field>();
|
||||||
|
const [s, setSchema] = useState(new Schema({}));
|
||||||
|
const [chartType, setChartType] = useState(form.values.type);
|
||||||
|
useEffect(() => {
|
||||||
|
// form.clearFormGraph('options.*');
|
||||||
|
setChartType(form?.values?.type);
|
||||||
|
if (chartType !== form?.values?.type) {
|
||||||
|
form.clearFormGraph('options.*');
|
||||||
|
}
|
||||||
|
if (form.values.type) {
|
||||||
|
const template = templates.get(form.values.type);
|
||||||
|
setSchema(new Schema(template.configurableProperties || {}));
|
||||||
|
}
|
||||||
|
}, [form.values.type]);
|
||||||
|
return <RecursionField schema={s} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ChartFormInterface {
|
||||||
|
type: string;
|
||||||
|
template: string;
|
||||||
|
metric: string;
|
||||||
|
dimension: string;
|
||||||
|
category?: string;
|
||||||
|
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChartBlockInitializer = (props) => {
|
||||||
|
const { insert } = props;
|
||||||
|
const options = useContext(SchemaOptionsContext);
|
||||||
|
const api = useAPIClient();
|
||||||
|
const compile = useCompile();
|
||||||
|
const { t } = useChartsTranslation();
|
||||||
|
return (
|
||||||
|
<ChartQueryBlockInitializer
|
||||||
|
{...props}
|
||||||
|
componentType={'Charts'}
|
||||||
|
onCreateBlockSchema={async ({ item: chartQueryMetadata }: { item: ChartQueryMetadata }) => {
|
||||||
|
const dataSource = chartQueryMetadata?.fields.map((field) => {
|
||||||
|
return {
|
||||||
|
label: field.name,
|
||||||
|
value: field.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let values = await FormDialog(
|
||||||
|
{
|
||||||
|
okText: compile('{{t("Submit")}}'),
|
||||||
|
title: lang('Create chart block'),
|
||||||
|
width: 1200,
|
||||||
|
bodyStyle: { background: '#f0f2f5', maxHeight: '65vh', overflow: 'auto' },
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const form = useForm<ChartFormInterface>();
|
||||||
|
const [chartBlockEngineMetaData, setChartBlockEngineMetaData] = useState<ChartBlockEngineMetaData>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
const chartBlockEngineMetaData = {
|
||||||
|
query: {
|
||||||
|
id: chartQueryMetadata?.id,
|
||||||
|
},
|
||||||
|
chart: form.values, //TODO
|
||||||
|
};
|
||||||
|
setChartBlockEngineMetaData(chartBlockEngineMetaData);
|
||||||
|
}, [form.values.type]);
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={api}>
|
||||||
|
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||||
|
<section
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* left*/}
|
||||||
|
<Card
|
||||||
|
title={lang('Chart config')}
|
||||||
|
size={'default'}
|
||||||
|
className={css`
|
||||||
|
flex: 1;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FormProvider form={form}>
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaComponent
|
||||||
|
scope={{ dataSource, JSON5, jsonConfigDesc }}
|
||||||
|
components={{ Options }}
|
||||||
|
schema={{
|
||||||
|
properties: {
|
||||||
|
// title: {
|
||||||
|
// title: lang('Chart title'),
|
||||||
|
// 'x-component': 'Input',
|
||||||
|
// 'x-decorator': 'FormItem',
|
||||||
|
// },
|
||||||
|
type: {
|
||||||
|
title: lang('Chart type'),
|
||||||
|
required: true,
|
||||||
|
'x-component': 'CustomSelect',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
enum: [...templates.values()].map((template) => {
|
||||||
|
return {
|
||||||
|
title: template.title,
|
||||||
|
key: template.type,
|
||||||
|
description: template.description,
|
||||||
|
group: template.group,
|
||||||
|
iconId: template.iconId,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Options',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
</FormProvider>
|
||||||
|
</Card>
|
||||||
|
{/* right*/}
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
flex: 1;
|
||||||
|
min-width: 600px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Card size={'default'} title={lang('Chart preview')}>
|
||||||
|
{/* Chart Preview*/}
|
||||||
|
{chartBlockEngineMetaData && (
|
||||||
|
<>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
properties: {
|
||||||
|
chartPreview: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'CardItem',
|
||||||
|
'x-component': 'ChartBlockEngine',
|
||||||
|
'x-component-props': {
|
||||||
|
chartBlockEngineMetaData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
size={'default'}
|
||||||
|
title={lang('Data preview')}
|
||||||
|
className={css`
|
||||||
|
margin-top: 24px;
|
||||||
|
overflow: scroll;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/*Data preview*/}
|
||||||
|
{chartBlockEngineMetaData?.query?.id && (
|
||||||
|
<DataSetPreviewTable
|
||||||
|
queryId={chartBlockEngineMetaData?.query?.id}
|
||||||
|
fields={chartQueryMetadata?.fields}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).open({
|
||||||
|
initialValues: {},
|
||||||
|
});
|
||||||
|
if (values) {
|
||||||
|
const chartBlockEngineMetaData: ChartBlockEngineMetaData = {
|
||||||
|
query: {
|
||||||
|
id: chartQueryMetadata.id,
|
||||||
|
},
|
||||||
|
chart: values,
|
||||||
|
};
|
||||||
|
insert({
|
||||||
|
type: 'void',
|
||||||
|
title: values?.title,
|
||||||
|
'x-designer': 'ChartBlockEngine.Designer',
|
||||||
|
'x-decorator': 'CardItem',
|
||||||
|
'x-component': 'ChartBlockEngine',
|
||||||
|
'x-component-props': {
|
||||||
|
chartBlockEngineMetaData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,136 @@
|
|||||||
|
import { TableOutlined } from '@ant-design/icons';
|
||||||
|
import { FormDialog, FormLayout } from '@formily/antd';
|
||||||
|
import { SchemaOptionsContext } from '@formily/react';
|
||||||
|
import {
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentOptions,
|
||||||
|
SchemaInitializer,
|
||||||
|
SchemaInitializerButtonContext,
|
||||||
|
useAPIClient,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useChartQueryMetadataContext } from './ChartQueryMetadataProvider';
|
||||||
|
import { lang } from './locale';
|
||||||
|
import { getQueryTypeSchema } from './settings/queryTypes';
|
||||||
|
|
||||||
|
export interface ChartQueryMetadata {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
fields: { name: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChartQueryBlockInitializer = (props) => {
|
||||||
|
const defaultItems: any = [
|
||||||
|
{
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: lang('Select query data'),
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const { templateWrap, onCreateBlockSchema, componentType, createBlockSchema, insert, ...others } = props;
|
||||||
|
const { setVisible } = useContext(SchemaInitializerButtonContext);
|
||||||
|
const [items, setItems] = useState(defaultItems);
|
||||||
|
const apiClient = useAPIClient();
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
const options = useContext(SchemaOptionsContext);
|
||||||
|
const onAddQuery = (info) => {
|
||||||
|
FormDialog(
|
||||||
|
{
|
||||||
|
sql: lang('Add SQL query'),
|
||||||
|
json: lang('Add JSON query'),
|
||||||
|
}[info.key],
|
||||||
|
() => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||||
|
<FormLayout layout={'vertical'}>
|
||||||
|
<SchemaComponent
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: lang('Title'),
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
options: getQueryTypeSchema(info.key),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormLayout>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.open({
|
||||||
|
initialValues: {
|
||||||
|
type: info.key,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(async (values) => {
|
||||||
|
try {
|
||||||
|
const { data } = await apiClient.resource('chartsQueries')?.create?.({ values });
|
||||||
|
const items = (await ctx.refresh()) as any;
|
||||||
|
const item = items.find((item) => item.id === data?.data?.id);
|
||||||
|
onCreateBlockSchema({ item });
|
||||||
|
setVisible(false);
|
||||||
|
} catch (error) {}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const chartQueryMetadata = ctx.data;
|
||||||
|
if (chartQueryMetadata && Array.isArray(chartQueryMetadata)) {
|
||||||
|
setItems(
|
||||||
|
[
|
||||||
|
chartQueryMetadata.length > 0
|
||||||
|
? {
|
||||||
|
type: 'itemGroup',
|
||||||
|
title: '{{t("Select chart query", {ns: "charts"})}}',
|
||||||
|
children: chartQueryMetadata,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
chartQueryMetadata.length > 0
|
||||||
|
? {
|
||||||
|
type: 'divider',
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
,
|
||||||
|
{
|
||||||
|
type: 'subMenu',
|
||||||
|
title: lang('Add chart query'),
|
||||||
|
// component: AddChartQuery,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
key: 'sql',
|
||||||
|
type: 'item',
|
||||||
|
title: 'SQL',
|
||||||
|
onClick: onAddQuery,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'json',
|
||||||
|
type: 'item',
|
||||||
|
title: 'JSON',
|
||||||
|
onClick: onAddQuery,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
].filter(Boolean),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<SchemaInitializer.Item
|
||||||
|
icon={<TableOutlined />}
|
||||||
|
{...others}
|
||||||
|
onClick={async ({ item }) => {
|
||||||
|
onCreateBlockSchema({ item });
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
items={items}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,62 @@
|
|||||||
|
import { useAPIClient, useRequest } from '@nocobase/client';
|
||||||
|
import { Spin } from 'antd';
|
||||||
|
import React, { createContext, useContext, useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const ChartQueryMetadataContext = createContext({
|
||||||
|
refresh: () => {},
|
||||||
|
data: [] as any[],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChartQueryMetadataProvider: React.FC = (props) => {
|
||||||
|
const api = useAPIClient();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
resource: 'chartsQueries',
|
||||||
|
action: 'listMetadata',
|
||||||
|
params: {
|
||||||
|
paginate: false,
|
||||||
|
sort: ['-id'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const service = useRequest(options, {
|
||||||
|
manual: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isAdminPage = location.pathname.startsWith('/admin');
|
||||||
|
const token = api.auth.getToken() || '';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAdminPage && token) {
|
||||||
|
service.run();
|
||||||
|
}
|
||||||
|
}, [isAdminPage, token]);
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
const { data } = await api.request(options);
|
||||||
|
service.mutate(data);
|
||||||
|
return data?.data || [];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (service.loading) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartQueryMetadataContext.Provider
|
||||||
|
value={{
|
||||||
|
refresh,
|
||||||
|
data: service.data?.data,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</ChartQueryMetadataContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChartQueryMetadataContext = () => {
|
||||||
|
return useContext(ChartQueryMetadataContext);
|
||||||
|
};
|
73
packages/plugins/charts/src/client/DataSetPreviewTable.tsx
Normal file
73
packages/plugins/charts/src/client/DataSetPreviewTable.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import {
|
||||||
|
BlockSchemaComponentProvider,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
SchemaComponent,
|
||||||
|
SchemaComponentProvider,
|
||||||
|
TableV2,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { Empty, Spin } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useGetDataSet } from './ChartBlockEngine';
|
||||||
|
|
||||||
|
export default ({ queryId, fields }: { queryId: number; fields }) => {
|
||||||
|
const { dataSet, loading, error } = useGetDataSet(queryId);
|
||||||
|
const columns = {};
|
||||||
|
if (fields) {
|
||||||
|
for (const field of fields) {
|
||||||
|
columns[field.name] = {
|
||||||
|
type: 'void',
|
||||||
|
title: field.name,
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-component-props': {
|
||||||
|
// width: 200,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[field.name]: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'array',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
scroll: { y: 300 },
|
||||||
|
},
|
||||||
|
default: dataSet,
|
||||||
|
properties: columns,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Empty description={<span>May be this chart block's query data has been deleted,please check!</span>} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Spin />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider scope={{ dataSet }} components={{ TableV2, Input, FormItem }}>
|
||||||
|
<BlockSchemaComponentProvider>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</BlockSchemaComponentProvider>
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
99
packages/plugins/charts/src/client/Icons.tsx
Normal file
99
packages/plugins/charts/src/client/Icons.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { Icon } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const RadarChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M523 573.5c-3.9 0.7-7.9 1.1-12 1.1-5.3 0-10.4-0.6-15.2-1.8l-62.2 189.7 167.4-76L523 573.5z"></path>
|
||||||
|
<path d="M398.3 767.3l68.9-210.1c-6.4-6-11.5-13.3-15-21.4l-192.6 52.7L398.3 767.3z"></path>
|
||||||
|
<path d="M528 448.9c4.1 1.1 8 2.6 11.7 4.5l72.5-61.8L528 263.7 528 448.9z"></path>
|
||||||
|
<path d="M259.6 555.4 447.3 504c0.4-4.2 1.3-8.3 2.5-12.2L270.9 373.4 259.6 555.4z"></path>
|
||||||
|
<path d="M655.5 396.7l208.3 185.7 30.2 4.8c4.8-24.6 7.4-49.9 7.4-75.9 0-95.9-34.6-183.7-92-251.6L655.5 396.7z"></path>
|
||||||
|
<path d="M794.4 563.4 630.9 417.7l-67 57.1c5.6 8.3 9.3 18 10.6 28.4L794.4 563.4z"></path>
|
||||||
|
<path d="M496 448.4 496 239.6l-218.8 99.6 189.4 125.4C474.7 456.8 484.8 451.1 496 448.4z"></path>
|
||||||
|
<path d="M619.3 713.3 420 803.8l-19.1 81.6c35 10.3 72.1 15.9 110.4 15.9 74.4 0 143.9-20.8 203-56.9L619.3 713.3z"></path>
|
||||||
|
<path d="M648.7 699.5l92.3 127.2c63.3-46.2 112.2-111 138.6-186.4L648.7 699.5z"></path>
|
||||||
|
<path d="M386.2 804 226.1 597.7l-87.9 27.4c34.9 114.7 121.3 207 232.2 250L386.2 804z"></path>
|
||||||
|
<path d="M226.9 564.3 240.2 353l-64.4-40.5c-34.6 58.2-54.4 126.2-54.4 198.8 0 28.4 3 56.2 8.8 82.9L226.9 564.3z"></path>
|
||||||
|
<path d="M842.8 609.8l-272.7-74.7c-4 9.5-10.1 17.9-17.9 24.4l77.3 112.1L842.8 609.8z"></path>
|
||||||
|
<path d="M528 205.4l108.8 165.2 151-134.5c-67-67.3-158.4-110.2-259.7-114.5L528.1 205.4z"></path>
|
||||||
|
<path d="M245.7 318.4l250.3-114 0-82.8c-125 4.8-234.8 68.5-302.8 164L245.7 318.4z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
const FunnelChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M533.6 784.5c6.6 16.4-1.3 35-17.6 41.7l0 0c-16.4 6.6-35-1.3-41.7-17.6L129.9 236c-6.6-16.4 1.3-35 17.6-41.7l0 0c16.4-6.6 35 1.3 41.7 17.6L533.6 784.5z"></path>
|
||||||
|
<path d="M536 812.2c-6.9 16.3-25.7 23.9-41.9 17l0 0c-16.3-6.9-23.9-25.7-17-41.9l357.6-575.7c6.9-16.3 25.7-23.9 41.9-17l0 0c16.3 6.9 23.9 25.7 17 41.9L536 812.2z"></path>
|
||||||
|
<path d="M895.4 224c0 17.7-14.3 32-32 32l-704 0c-17.7 0-32-14.3-32-32l0 0c0-17.7 14.3-32 32-32l704 0C881 192 895.4 206.3 895.4 224L895.4 224z"></path>
|
||||||
|
<path d="M278.6 384l465 0 0 64-465 0 0-64Z"></path>
|
||||||
|
<path d="M391.6 576l232.5 0 0 64-232.5 0 0-64Z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
const ScatterChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M191 864c0 17.7-14.3 32-32 32l0 0c-17.7 0-32-14.3-32-32L127 160c0-17.7 14.3-32 32-32l0 0c17.7 0 32 14.3 32 32L191 864z"></path>
|
||||||
|
<path d="M159 896c-17.7 0-32-14.3-32-32l0 0c0-17.7 14.3-32 32-32l712 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L159 896z"></path>
|
||||||
|
<path d="M745.6 765.9"></path>
|
||||||
|
<path d="M307.6 244.3m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M560 479.2m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M391.9 387.7m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M540.4 244.3m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M732.9 292.3m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M792.9 717.9m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M307.6 527.2m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
<path d="M439.9 662.4m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
const ColumnChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M191 864c0 17.7-14.3 32-32 32l0 0c-17.7 0-32-14.3-32-32L127 160c0-17.7 14.3-32 32-32l0 0c17.7 0 32 14.3 32 32L191 864z"></path>
|
||||||
|
<path d="M159 896c-17.7 0-32-14.3-32-32l0 0c0-17.7 14.3-32 32-32l712 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L159 896z"></path>
|
||||||
|
<path d="M745.6 765.9"></path>
|
||||||
|
<path d="M342.1 734.3c0 17.5-14.2 31.7-31.7 31.7l0 0c-17.5 0-31.7-14.2-31.7-31.7L278.7 489c0-17.5 14.2-31.7 31.7-31.7l0 0c17.5 0 31.7 14.2 31.7 31.7L342.1 734.3z"></path>
|
||||||
|
<path d="M493.8 734.3c0 17.5-14.2 31.7-31.7 31.7l0 0c-17.5 0-31.7-14.2-31.7-31.7L430.4 387c0-17.5 14.2-31.7 31.7-31.7l0 0c17.5 0 31.7 14.2 31.7 31.7L493.8 734.3z"></path>
|
||||||
|
<path d="M797.3 734.3c0 17.5-14.2 31.7-31.7 31.7l0 0c-17.5 0-31.7-14.2-31.7-31.7L733.9 234.5c0-17.5 14.2-31.7 31.7-31.7l0 0c17.5 0 31.7 14.2 31.7 31.7L797.3 734.3z"></path>
|
||||||
|
<path d="M645.5 734.3c0 17.5-14.2 31.7-31.7 31.7l0 0c-17.5 0-31.7-14.2-31.7-31.7l0-97.2c0-17.5 14.2-31.7 31.7-31.7l0 0c17.5 0 31.7 14.2 31.7 31.7L645.5 734.3z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const BarChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M131.072 156.16c0-17.92 14.336-31.744 31.744-31.744 17.92 0 31.744 14.336 31.744 31.744v712.192c0 17.92-14.336 31.744-31.744 31.744-17.92 0-31.744-14.336-31.744-31.744V156.16zM292.864 338.944c-17.408 0-31.744-14.336-31.744-31.744 0-17.408 14.336-31.744 31.744-31.744h245.248c17.408 0 31.744 14.336 31.744 31.744 0 17.408-14.336 31.744-31.744 31.744h-245.248zM292.864 490.496c-17.408 0-31.744-14.336-31.744-31.744 0-17.408 14.336-31.744 31.744-31.744H640c17.408 0 31.744 14.336 31.744 31.744 0 17.408-14.336 31.744-31.744 31.744h-347.136zM292.864 794.112c-17.408 0-31.744-14.336-31.744-31.744 0-17.408 14.336-31.744 31.744-31.744h499.712c17.408 0 31.744 14.336 31.744 31.744 0 17.408-14.336 31.744-31.744 31.744h-499.712zM292.864 642.56c-17.408 0-31.744-14.336-31.744-31.744 0-17.408 14.336-31.744 31.744-31.744h97.28c17.408 0 31.744 14.336 31.744 31.744 0 17.408-14.336 31.744-31.744 31.744h-97.28z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const LineChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M191 864c0 17.7-14.3 32-32 32l0 0c-17.7 0-32-14.3-32-32L127 160c0-17.7 14.3-32 32-32l0 0c17.7 0 32 14.3 32 32L191 864z"></path>
|
||||||
|
<path d="M159 896c-17.7 0-32-14.3-32-32l0 0c0-17.7 14.3-32 32-32l712 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L159 896z"></path>
|
||||||
|
<path d="M307.1 686.3c-12.4 12.4-32.4 12.4-44.8 0l0 0c-12.4-12.4-12.4-32.4 0-44.8l173.4-173.4c12.4-12.4 32.4-12.4 44.8 0l0 0c12.4 12.4 12.4 32.4 0 44.8L307.1 686.3z"></path>
|
||||||
|
<path d="M608.8 640c-12.4 12.4-32.4 12.4-44.8 0l0 0c-12.4-12.4-12.4-32.4 0-44.8l212.7-212.7c12.4-12.4 32.4-12.4 44.8 0l0 0c12.4 12.4 12.4 32.4 0 44.8L608.8 640z"></path>
|
||||||
|
<path d="M608 595.3c12.4 12.4 12.4 32.4 0 44.8l0 0c-12.4 12.4-32.4 12.4-44.8 0L436 512.8c-12.4-12.4-12.4-32.4 0-44.8l0 0c12.4-12.4 32.4-12.4 44.8 0L608 595.3z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const PieChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M489.7 511 489 511 489 126.9c-201 12.3-360.1 179.2-360.1 383.3 0 212.1 171.9 384 384 384 106.5 0 202.9-43.4 272.5-113.4L489.7 511z"></path>
|
||||||
|
<path d="M773.1 237.2l6-6c-62.7-59.6-143.5-95.8-228.2-104l0 304.7L773.1 237.2z"></path>
|
||||||
|
<path d="M819.5 281.6 564.1 505.1 822 739c3-4 5.9-8.1 8.7-12.3 41.4-61.3 65.6-135.2 65.6-214.7C896.3 425.6 867.7 345.8 819.5 281.6z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AreaChart = () => (
|
||||||
|
<svg width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M191 864c0 17.7-14.3 32-32 32l0 0c-17.7 0-32-14.3-32-32L127 160c0-17.7 14.3-32 32-32l0 0c17.7 0 32 14.3 32 32L191 864z"></path>
|
||||||
|
<path d="M159 896c-17.7 0-32-14.3-32-32l0 0c0-17.7 14.3-32 32-32l712 0c17.7 0 32 14.3 32 32l0 0c0 17.7-14.3 32-32 32L159 896z"></path>
|
||||||
|
<path d="M830.6 338.2 830.6 338.2c0.9-9.2-2.1-18.6-9.1-25.7-12.4-12.4-32.4-12.4-44.8 0L586 503.3 480.8 398.1c-6.2-6.2-14.4-9.3-22.5-9.3-8.1 0-16.3 3.1-22.5 9.3L262.4 571.5c-6 6-9.1 13.8-9.3 21.6 0 0.1 0 0.1 0 0.2l0 111.8c0 35.3 28.7 64 64 64l449.7 0c33.8 0 61.4-26.1 63.8-59.3l0 0 0-0.2c0.1-1.5 0.2-3 0.2-4.5L830.8 593.3c0-0.3-0.1-0.7-0.2-1L830.6 338.2z"></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
Icon.register({
|
||||||
|
'icon-area': (props) => <Icon component={AreaChart} {...props} />,
|
||||||
|
'icon-pie': (props) => <Icon component={PieChart} {...props} />,
|
||||||
|
'icon-radar': (props) => <Icon component={RadarChart} {...props} />,
|
||||||
|
'icon-funnel': (props) => <Icon component={FunnelChart} {...props} />,
|
||||||
|
'icon-line': (props) => <Icon component={LineChart} {...props} />,
|
||||||
|
'icon-bar': (props) => <Icon component={BarChart} {...props} />,
|
||||||
|
'icon-column': (props) => <Icon component={ColumnChart} {...props} />,
|
||||||
|
'icon-scatter': (props) => <Icon component={ScatterChart} {...props} />,
|
||||||
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { G2Plot } from '@nocobase/client';
|
||||||
|
import DataSetPreviewTable from '../DataSetPreviewTable';
|
||||||
|
|
||||||
|
const chartRenderComponentsMap = new Map();
|
||||||
|
chartRenderComponentsMap.set('G2Plot', G2Plot);
|
||||||
|
chartRenderComponentsMap.set('DataSetPreviewTable', DataSetPreviewTable);
|
||||||
|
export default chartRenderComponentsMap;
|
19
packages/plugins/charts/src/client/hooks/index.ts
Normal file
19
packages/plugins/charts/src/client/hooks/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useChartQueryMetadataContext } from '../ChartQueryMetadataProvider';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const useFieldsById = (queryId: number) => {
|
||||||
|
const [fields, setFields] = useState([]);
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
useEffect(() => {
|
||||||
|
const chartQueryList = ctx?.data;
|
||||||
|
if (chartQueryList && Array.isArray(chartQueryList)) {
|
||||||
|
const currentQuery = chartQueryList.find((chartQuery) => chartQuery.id === queryId);
|
||||||
|
setFields(currentQuery?.fields);
|
||||||
|
}
|
||||||
|
}, [queryId]);
|
||||||
|
return {
|
||||||
|
fields,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useFieldsById };
|
101
packages/plugins/charts/src/client/index.tsx
Normal file
101
packages/plugins/charts/src/client/index.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { registerValidateRules } from '@formily/core';
|
||||||
|
import {
|
||||||
|
SchemaComponentOptions,
|
||||||
|
SchemaInitializerContext,
|
||||||
|
SettingsCenterProvider,
|
||||||
|
useAPIClient,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { ChartBlockEngine } from './ChartBlockEngine';
|
||||||
|
import { ChartBlockInitializer } from './ChartBlockInitializer';
|
||||||
|
import { ChartQueryMetadataProvider } from './ChartQueryMetadataProvider';
|
||||||
|
import './Icons';
|
||||||
|
import { lang } from './locale';
|
||||||
|
import { CustomSelect } from './select';
|
||||||
|
import { QueriesTable } from './settings/QueriesTable';
|
||||||
|
|
||||||
|
registerValidateRules({
|
||||||
|
json5: (value, rule) => {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const val = JSON5.parse(value);
|
||||||
|
if (!isNaN(val)) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
message: lang('Invalid JSON format'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
message: lang('Invalid JSON format'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default React.memo((props) => {
|
||||||
|
const api = useAPIClient();
|
||||||
|
const items = useContext(SchemaInitializerContext);
|
||||||
|
const children = items.BlockInitializers.items[0].children;
|
||||||
|
if (children) {
|
||||||
|
const hasChartItem = children.some((child) => child?.component === 'ChartBlockInitializer');
|
||||||
|
if (!hasChartItem) {
|
||||||
|
children.push({
|
||||||
|
key: 'chart',
|
||||||
|
type: 'item',
|
||||||
|
icon: 'PieChartOutlined',
|
||||||
|
title: '{{t("Chart",{ns:"charts"})}}',
|
||||||
|
component: 'ChartBlockInitializer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const validateSQL = (sql) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
api
|
||||||
|
.request({
|
||||||
|
url: 'chartsQueries:validate',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
sql,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
resolve(data?.data?.errorMessage);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
resolve('Invalid SQL');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ChartQueryMetadataProvider>
|
||||||
|
<SettingsCenterProvider
|
||||||
|
settings={{
|
||||||
|
charts: {
|
||||||
|
title: '{{t("Charts", {ns:"charts"})}}',
|
||||||
|
icon: 'PieChartOutlined',
|
||||||
|
tabs: {
|
||||||
|
queries: {
|
||||||
|
title: '{{t("Queries", {ns:"charts"})}}',
|
||||||
|
component: QueriesTable,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SchemaComponentOptions
|
||||||
|
scope={{ validateSQL }}
|
||||||
|
components={{ CustomSelect, ChartBlockInitializer, ChartBlockEngine }}
|
||||||
|
>
|
||||||
|
<SchemaInitializerContext.Provider value={items}>{props.children}</SchemaInitializerContext.Provider>
|
||||||
|
</SchemaComponentOptions>
|
||||||
|
</SettingsCenterProvider>
|
||||||
|
</ChartQueryMetadataProvider>
|
||||||
|
);
|
||||||
|
});
|
23
packages/plugins/charts/src/client/locale/en-US.ts
Normal file
23
packages/plugins/charts/src/client/locale/en-US.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
Cancel: 'Cancel',
|
||||||
|
Submit: 'Submit',
|
||||||
|
Actions: 'Actions',
|
||||||
|
Title: 'Title',
|
||||||
|
Enable: 'Enable',
|
||||||
|
'SAML manager': 'SAML manager',
|
||||||
|
'SAML Providers': 'SAML Providers',
|
||||||
|
'Redirect url': 'Redirect url',
|
||||||
|
'SP entity id': 'SP entity id',
|
||||||
|
'Add provider': 'Add',
|
||||||
|
'Edit provider': 'Edit',
|
||||||
|
'Client id': 'Client id',
|
||||||
|
'Entity id or issuer': 'Entity id or issuer',
|
||||||
|
'Login Url': 'Login Url',
|
||||||
|
'Public cert': 'Public cert',
|
||||||
|
'Delete provider': 'Delete',
|
||||||
|
'Are you sure you want to delete it?': 'Are you sure you want to delete it?',
|
||||||
|
'Sign in button name, which will be displayed on the sign in page':
|
||||||
|
'Sign in button name, which will be displayed on the sign in page',
|
||||||
|
};
|
18
packages/plugins/charts/src/client/locale/index.ts
Normal file
18
packages/plugins/charts/src/client/locale/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { i18n } from '@nocobase/client';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const NAMESPACE = 'charts';
|
||||||
|
|
||||||
|
// i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
||||||
|
// i18n.addResources('en-US', NAMESPACE, enUS);
|
||||||
|
// i18n.addResources('ja-JP', NAMESPACE, jaJP);
|
||||||
|
// i18n.addResources('ru-RU', NAMESPACE, ruRU);
|
||||||
|
// i18n.addResources('tr-TR', NAMESPACE, trTR);
|
||||||
|
|
||||||
|
export function lang(key: string) {
|
||||||
|
return i18n.t(key, { ns: NAMESPACE });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useChartsTranslation() {
|
||||||
|
return useTranslation(NAMESPACE);
|
||||||
|
}
|
1
packages/plugins/charts/src/client/locale/ja-JP.ts
Normal file
1
packages/plugins/charts/src/client/locale/ja-JP.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {};
|
1
packages/plugins/charts/src/client/locale/ru-RU.ts
Normal file
1
packages/plugins/charts/src/client/locale/ru-RU.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {};
|
1
packages/plugins/charts/src/client/locale/tr-TR.ts
Normal file
1
packages/plugins/charts/src/client/locale/tr-TR.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {};
|
63
packages/plugins/charts/src/client/locale/zh-CN.ts
Normal file
63
packages/plugins/charts/src/client/locale/zh-CN.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
export default {
|
||||||
|
Edit: '编辑',
|
||||||
|
Delete: '删除',
|
||||||
|
Cancel: '取消',
|
||||||
|
Submit: '提交',
|
||||||
|
Actions: '操作',
|
||||||
|
Title: '名称',
|
||||||
|
Enable: '启用',
|
||||||
|
Chart: '图表',
|
||||||
|
Charts: '图表',
|
||||||
|
Queries: '查询列表',
|
||||||
|
'Select chart query': '选择图表数据',
|
||||||
|
'Select query data': '选择查询数据',
|
||||||
|
Type: '类型',
|
||||||
|
'Chart type': '图表类型',
|
||||||
|
'Chart title': '图表标题',
|
||||||
|
'Basic charts': '基础图表',
|
||||||
|
'More charts': '更多图表',
|
||||||
|
'Chart config': '图表配置',
|
||||||
|
'Add query': '添加查询',
|
||||||
|
'Edit query': '编辑查询',
|
||||||
|
'Invalid JSON format,must be an object array.': '无效的JSON格式,必须是对象数组。',
|
||||||
|
Area: '面积图',
|
||||||
|
Bar: '条形图',
|
||||||
|
Column: '柱状图',
|
||||||
|
Funnel: '漏斗图',
|
||||||
|
Line: '折线图',
|
||||||
|
Pie: '饼图',
|
||||||
|
Radar: '雷达图',
|
||||||
|
Scatter: '散点图',
|
||||||
|
'Edit chart block': '编辑图表区块',
|
||||||
|
'Chart preview': '图表预览',
|
||||||
|
'Delete queries': '删除查询列表',
|
||||||
|
'Delete query': '删除查询',
|
||||||
|
'Add chart query': '添加图表查询',
|
||||||
|
'Add SQL query': '添加SQL查询',
|
||||||
|
'Add JSON query': '添加JSON查询',
|
||||||
|
'Data preview': '数据预览',
|
||||||
|
'Category axis / Dimension': '类别轴 / 纬度',
|
||||||
|
'Value axis / Metrics': '值轴 / 度量',
|
||||||
|
'JSON config': 'JSON 配置',
|
||||||
|
'Json config references': 'JSON配置参考',
|
||||||
|
'Create chart block': '创建图表区块',
|
||||||
|
'Invalid JSON format': '无效的JSON格式',
|
||||||
|
'Json config references: ': 'JSON配置参考: ',
|
||||||
|
'Sector Angle / Metric': '扇形角 / 度量',
|
||||||
|
'Sector label / Dimensional': '扇形标签 / 纬度',
|
||||||
|
'Color legend / Dimensional': '颜色系列 / 纬度',
|
||||||
|
'Funnel Layer Width/Metrics': '漏斗层宽度 / 度量',
|
||||||
|
'Branch Tags/Dimensions': '分支标签 / 纬度',
|
||||||
|
'Branch Length/Metrics': '分支长度 / 度量',
|
||||||
|
'Please check the chart config': '请检查图表配置',
|
||||||
|
'1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)':
|
||||||
|
'1 个「时间」或「有序名词」字段,1 个「数值」字段,1 个「无序名词」字段(可选)',
|
||||||
|
'1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」':
|
||||||
|
'1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」',
|
||||||
|
'1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」':
|
||||||
|
'1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」',
|
||||||
|
'1 「Unordered Noun」 field, 1 「Numeric」 field': '1 个「无序名词」字段,1 个「数值」字段',
|
||||||
|
'1 「Time」 or 「Order Noun」 field, 1 「Value」 field': '1 个「时间」或「有序名词」字段,1 个「数值」字段',
|
||||||
|
'1~ 2 「Unordered Noun」 fields, 1 「Numeric」 field': '1 ~ 2 个「无序名词」字段,1 个「数值」字段',
|
||||||
|
'1 「Numeric」 field, 0~ 1 「Unordered Noun」 field': '1 个「数值」字段,0 ~ 1 个「无序名词」字段',
|
||||||
|
};
|
127
packages/plugins/charts/src/client/select/CustomSelect.tsx
Normal file
127
packages/plugins/charts/src/client/select/CustomSelect.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
|
import { isValid } from '@formily/shared';
|
||||||
|
import { Icon } from '@nocobase/client';
|
||||||
|
import type { SelectProps } from 'antd';
|
||||||
|
import { Popover, Select as AntdSelect } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { lang } from '../locale';
|
||||||
|
import { ReadPretty } from './ReadPretty';
|
||||||
|
|
||||||
|
type Props = SelectProps<any, any> & { objectValue?: boolean; onChange?: (v: any) => void; multiple: boolean };
|
||||||
|
|
||||||
|
const isEmptyObject = (val: any) => !isValid(val) || (typeof val === 'object' && Object.keys(val).length === 0);
|
||||||
|
|
||||||
|
const { Option, OptGroup } = AntdSelect;
|
||||||
|
const filterOption = (input, option) => (option?.label ?? '').toLowerCase().includes((input || '').toLowerCase());
|
||||||
|
|
||||||
|
const InternalSelect = connect(
|
||||||
|
(props: Props) => {
|
||||||
|
const { ...others } = props;
|
||||||
|
const { options, ...othersProps } = { ...others };
|
||||||
|
const mode = props.mode || props.multiple ? 'multiple' : undefined;
|
||||||
|
const group1 = options.filter((option) => option.group === 2);
|
||||||
|
const group2 = options.filter((option) => option.group === 1);
|
||||||
|
return (
|
||||||
|
<AntdSelect
|
||||||
|
showSearch
|
||||||
|
filterOption={filterOption}
|
||||||
|
allowClear
|
||||||
|
{...othersProps}
|
||||||
|
onChange={(changed) => {
|
||||||
|
props.onChange?.(changed === undefined ? null : changed);
|
||||||
|
}}
|
||||||
|
mode={mode}
|
||||||
|
>
|
||||||
|
<OptGroup label={lang('Basic charts')}>
|
||||||
|
{group1.map((option) => (
|
||||||
|
<Option value={option.key} label={lang(option.title)}>
|
||||||
|
<Popover
|
||||||
|
placement={'right'}
|
||||||
|
zIndex={99999999999}
|
||||||
|
content={() => (
|
||||||
|
<span>
|
||||||
|
{lang(option?.description)
|
||||||
|
?.split(',')
|
||||||
|
.map((item) => (
|
||||||
|
<div>{item}</div>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Icon type={option.iconId} />
|
||||||
|
<span role="img" aria-label={lang(option.title)}>
|
||||||
|
{lang(option.title)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</OptGroup>
|
||||||
|
<OptGroup label={lang('More charts')}>
|
||||||
|
{group2.map((option) => (
|
||||||
|
<Option value={option.key} label={lang(option.title)}>
|
||||||
|
<Popover
|
||||||
|
placement={'right'}
|
||||||
|
zIndex={99999999999}
|
||||||
|
content={() => (
|
||||||
|
<span>
|
||||||
|
{lang(option?.description)
|
||||||
|
?.split(',')
|
||||||
|
.map((item) => (
|
||||||
|
<div>{item}</div>
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css`
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Icon type={option.iconId} />
|
||||||
|
<span role="img" aria-label={lang(option.title)}>
|
||||||
|
{lang(option.title)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</OptGroup>
|
||||||
|
</AntdSelect>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mapProps(
|
||||||
|
{
|
||||||
|
dataSource: 'options',
|
||||||
|
loading: true,
|
||||||
|
},
|
||||||
|
(props, field) => {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props?.suffixIcon,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
mapReadPretty(ReadPretty),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CustomSelect = InternalSelect as unknown as typeof InternalSelect & {
|
||||||
|
ReadPretty: typeof ReadPretty;
|
||||||
|
};
|
||||||
|
|
||||||
|
CustomSelect.ReadPretty = ReadPretty;
|
||||||
|
|
||||||
|
export default CustomSelect;
|
36
packages/plugins/charts/src/client/select/ReadPretty.tsx
Normal file
36
packages/plugins/charts/src/client/select/ReadPretty.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { isArrayField } from '@formily/core';
|
||||||
|
import { observer, useField } from '@formily/react';
|
||||||
|
import { isValid } from '@formily/shared';
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { defaultFieldNames, getCurrentOptions } from './shared';
|
||||||
|
import { useCompile } from '@nocobase/client';
|
||||||
|
|
||||||
|
type Composed = {
|
||||||
|
Select?: React.FC<any>;
|
||||||
|
Object?: React.FC<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadPretty = observer((props: any) => {
|
||||||
|
const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
|
||||||
|
const field = useField<any>();
|
||||||
|
const compile = useCompile();
|
||||||
|
|
||||||
|
if (!isValid(props.value)) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
if (isArrayField(field) && field?.value?.length === 0) {
|
||||||
|
return <div />;
|
||||||
|
}
|
||||||
|
const dataSource = field.dataSource || props.options || [];
|
||||||
|
const options = getCurrentOptions(field.value, dataSource, fieldNames);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{options.map((option, key) => (
|
||||||
|
<Tag key={key} color={option[fieldNames.color]} icon={option.icon}>
|
||||||
|
{compile(option[fieldNames.label])}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
38
packages/plugins/charts/src/client/select/index.md
Normal file
38
packages/plugins/charts/src/client/select/index.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
nav:
|
||||||
|
path: /client
|
||||||
|
group:
|
||||||
|
path: /schema-components
|
||||||
|
---
|
||||||
|
|
||||||
|
# Select
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### 单选
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
|
### 多选
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx" />
|
||||||
|
|
||||||
|
### 值为 Object 类型的 Select
|
||||||
|
|
||||||
|
<code src="./demos/demo3.tsx" />
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
基于 Ant Design 的 [Select](https://ant.design/components/select/#API),相关扩展属性有:
|
||||||
|
|
||||||
|
- `objectValue` 值为 object 类型
|
||||||
|
- `fieldNames` 默认值有区别
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const defaultFieldNames = {
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
color: 'color',
|
||||||
|
options: 'children',
|
||||||
|
};
|
||||||
|
```
|
2
packages/plugins/charts/src/client/select/index.ts
Normal file
2
packages/plugins/charts/src/client/select/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './CustomSelect';
|
||||||
|
export * from './shared';
|
36
packages/plugins/charts/src/client/select/shared.ts
Normal file
36
packages/plugins/charts/src/client/select/shared.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { castArray } from 'lodash';
|
||||||
|
|
||||||
|
export const defaultFieldNames = {
|
||||||
|
label: 'label',
|
||||||
|
value: 'value',
|
||||||
|
color: 'color',
|
||||||
|
options: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrentOptions = (values, dataSource, fieldNames) => {
|
||||||
|
function flatData(data) {
|
||||||
|
let newArr = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const children = data[i][fieldNames.options];
|
||||||
|
if (Array.isArray(children)) {
|
||||||
|
newArr.push(...flatData(children));
|
||||||
|
}
|
||||||
|
newArr.push({ ...data[i] });
|
||||||
|
}
|
||||||
|
return newArr;
|
||||||
|
}
|
||||||
|
const result = flatData(dataSource);
|
||||||
|
values = castArray(values)
|
||||||
|
.filter((item) => item != null)
|
||||||
|
.map((val) => (typeof val === 'object' ? val[fieldNames.value] : val));
|
||||||
|
const findOptions = (options: any[]) => {
|
||||||
|
if (!options) return [];
|
||||||
|
let current = [];
|
||||||
|
for (const value of values) {
|
||||||
|
const option = options.find((v) => v[fieldNames.value] === value) || { value: value, label: value };
|
||||||
|
current.push(option);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
return findOptions(result);
|
||||||
|
};
|
161
packages/plugins/charts/src/client/settings/AddNewQuery.tsx
Normal file
161
packages/plugins/charts/src/client/settings/AddNewQuery.tsx
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { DownOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { createForm } from '@formily/core';
|
||||||
|
import { ISchema, useForm } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import {
|
||||||
|
ActionContext,
|
||||||
|
SchemaComponent,
|
||||||
|
useActionContext,
|
||||||
|
useRecord,
|
||||||
|
useResourceActionContext,
|
||||||
|
useResourceContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { Button, Dropdown, Menu } from 'antd';
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { useChartQueryMetadataContext } from '../ChartQueryMetadataProvider';
|
||||||
|
import { getQueryTypeSchema } from './queryTypes';
|
||||||
|
import { lang } from '../locale';
|
||||||
|
|
||||||
|
const useCreateAction = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
const form = useForm();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
const { resource } = useResourceContext();
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
await resource.create({ values: form.values });
|
||||||
|
setVisible(false);
|
||||||
|
await form.reset();
|
||||||
|
refresh();
|
||||||
|
ctx.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useUpdateAction = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
const form = useForm();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
const { resource, targetKey } = useResourceContext();
|
||||||
|
const { [targetKey]: filterByTk } = useRecord();
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await form.submit();
|
||||||
|
await resource.update({ filterByTk, values: form.values });
|
||||||
|
setVisible(false);
|
||||||
|
await form.reset();
|
||||||
|
refresh();
|
||||||
|
ctx.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const useCloseAction = () => {
|
||||||
|
const { setVisible } = useActionContext();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
setVisible(false);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSchema = (initialValue, { form, isNewRecord }) => {
|
||||||
|
const type = initialValue.type;
|
||||||
|
const schema: ISchema = {
|
||||||
|
type: 'void',
|
||||||
|
name: uid(),
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
form,
|
||||||
|
// initialValue: JSON.parse(JSON.stringify(initialValue)),
|
||||||
|
},
|
||||||
|
title: isNewRecord ? lang('Add query') : lang('Edit query'),
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: lang('Title'),
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
options: getQueryTypeSchema(type),
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
cancel: {
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: lang('Cancel'),
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useCloseAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
'x-component': 'Action',
|
||||||
|
title: lang('Submit'),
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ useSubmitAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AddNewQuery = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [schema, setSchema] = useState({});
|
||||||
|
const form = useMemo(() => createForm(), []);
|
||||||
|
const menu = (
|
||||||
|
<Menu
|
||||||
|
onClick={(info) => {
|
||||||
|
setVisible(true);
|
||||||
|
form.setValues({ type: info.key });
|
||||||
|
setSchema(getSchema({ type: info.key }, { form, isNewRecord: true }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu.Item key={'json'}>JSON</Menu.Item>
|
||||||
|
<Menu.Item key={'sql'}>SQL</Menu.Item>
|
||||||
|
<Menu.Item disabled key={'api'}>
|
||||||
|
API
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item disabled>Collection</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
|
<Dropdown overlay={menu}>
|
||||||
|
<Button icon={<PlusOutlined />} type={'primary'}>
|
||||||
|
{lang('Add query')} <DownOutlined />
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<SchemaComponent schema={schema} scope={{ useCloseAction, useSubmitAction: useCreateAction }} />
|
||||||
|
</ActionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditQuery = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const record = useRecord();
|
||||||
|
const form = useMemo(() => createForm(), []);
|
||||||
|
const schema = getSchema(record, { form, isNewRecord: false });
|
||||||
|
return (
|
||||||
|
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||||
|
<a
|
||||||
|
onClick={() => {
|
||||||
|
form.setValues(record);
|
||||||
|
setVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{lang('Edit')}
|
||||||
|
</a>
|
||||||
|
<SchemaComponent schema={schema} scope={{ useCloseAction, useSubmitAction: useUpdateAction }} />
|
||||||
|
</ActionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
import { useRecord } from '@nocobase/client';
|
||||||
|
import { Table } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ConfigureFields = () => {
|
||||||
|
const record = useRecord();
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '字段标识',
|
||||||
|
dataIndex: 'name',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
dataSource={record.fields || []}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
26
packages/plugins/charts/src/client/settings/QueriesTable.tsx
Normal file
26
packages/plugins/charts/src/client/settings/QueriesTable.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { SchemaComponent } from '@nocobase/client';
|
||||||
|
import { Card } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AddNewQuery, EditQuery } from './AddNewQuery';
|
||||||
|
import { ConfigureFields } from './ConfigureFields';
|
||||||
|
import {
|
||||||
|
chartsQueriesSchema,
|
||||||
|
useDestroyAllSelectedQueriesAction,
|
||||||
|
useDestroyQueryItemAction,
|
||||||
|
} from './schemas/chartsQueries';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
export const QueriesTable = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Card bordered={false}>
|
||||||
|
<SchemaComponent
|
||||||
|
scope={{ JSON5, useDestroyQueryItemAction, useDestroyAllSelectedQueriesAction }}
|
||||||
|
schema={chartsQueriesSchema}
|
||||||
|
components={{ AddNewQuery, EditQuery, ConfigureFields }}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
64
packages/plugins/charts/src/client/settings/queryTypes.ts
Normal file
64
packages/plugins/charts/src/client/settings/queryTypes.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
export const json: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
data: {
|
||||||
|
title: 'JSON',
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
maxRows: 20,
|
||||||
|
minRows: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sql: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
sql: {
|
||||||
|
title: 'SQL',
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-validator': {
|
||||||
|
triggerType: 'onBlur',
|
||||||
|
validator: '{{validateSQL}}',
|
||||||
|
},
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: {
|
||||||
|
maxRows: 20,
|
||||||
|
minRows: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const api: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
api: {
|
||||||
|
title: 'API',
|
||||||
|
required: true,
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const types = {
|
||||||
|
json,
|
||||||
|
sql,
|
||||||
|
api,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getQueryTypeSchema = (type) => {
|
||||||
|
return cloneDeep(types[type]);
|
||||||
|
};
|
@ -0,0 +1,319 @@
|
|||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
import {
|
||||||
|
useActionContext,
|
||||||
|
useRecord,
|
||||||
|
useRequest,
|
||||||
|
useResourceActionContext,
|
||||||
|
useResourceContext,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { useChartQueryMetadataContext } from '../../ChartQueryMetadataProvider';
|
||||||
|
|
||||||
|
const collection = {
|
||||||
|
name: 'chartsQueries',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'integer',
|
||||||
|
name: 'title',
|
||||||
|
interface: 'input',
|
||||||
|
uiSchema: {
|
||||||
|
title: '{{t("Title",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
required: true,
|
||||||
|
} as ISchema,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
name: 'type',
|
||||||
|
interface: 'select',
|
||||||
|
uiSchema: {
|
||||||
|
title: '{{t("Type",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Select',
|
||||||
|
required: true,
|
||||||
|
enum: [
|
||||||
|
{ label: '{{t("API")}}', value: 'api' },
|
||||||
|
{ label: '{{t("SQL")}}', value: 'sql' },
|
||||||
|
{ label: '{{t("JSON")}}', value: 'json' },
|
||||||
|
],
|
||||||
|
} as ISchema,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDestroyQueryItemAction = () => {
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
const { refresh } = useResourceActionContext();
|
||||||
|
const { resource, targetKey } = useResourceContext();
|
||||||
|
const { [targetKey]: filterByTk } = useRecord();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await resource.destroy({ filterByTk });
|
||||||
|
refresh();
|
||||||
|
ctx.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDestroyAllSelectedQueriesAction = () => {
|
||||||
|
const ctx = useChartQueryMetadataContext();
|
||||||
|
const { state, setState, refresh } = useResourceActionContext();
|
||||||
|
const { resource, targetKey } = useResourceContext();
|
||||||
|
return {
|
||||||
|
async run() {
|
||||||
|
await resource.destroy({
|
||||||
|
filterByTk: state?.selectedRowKeys || [],
|
||||||
|
});
|
||||||
|
setState?.({ selectedRowKeys: [] });
|
||||||
|
refresh();
|
||||||
|
ctx.refresh();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const chartsQueriesSchema: ISchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
block1: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'ResourceActionProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection,
|
||||||
|
resourceName: 'chartsQueries',
|
||||||
|
request: {
|
||||||
|
resource: 'chartsQueries',
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 50,
|
||||||
|
sort: ['-id'],
|
||||||
|
appends: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-component': 'CollectionProvider',
|
||||||
|
'x-component-props': {
|
||||||
|
collection,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
delete: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Delete") }}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ useDestroyAllSelectedQueriesAction }}',
|
||||||
|
confirm: {
|
||||||
|
title: '{{t("Delete queries",{ns:"charts"})}}',
|
||||||
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Add query")}}',
|
||||||
|
'x-component': 'AddNewQuery',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues(options) {
|
||||||
|
const ctx = useActionContext();
|
||||||
|
return useRequest(
|
||||||
|
() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: {
|
||||||
|
name: `s_${uid()}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{ ...options, refreshDeps: [ctx.visible] },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: '{{t("Add query",{ns:"charts"})}}',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
cancel: {
|
||||||
|
title: '{{t("Cancel",{ns:"charts"})}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ cm.useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: '{{t("Submit",{ns:"charts"})}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ cm.useCreateAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: 'void',
|
||||||
|
'x-uid': 'input',
|
||||||
|
'x-component': 'Table.Void',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
useDataSource: '{{ cm.useDataSourceFromRAC }}',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'number',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'void',
|
||||||
|
'x-decorator': 'Table.Column.Decorator',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Actions")}}',
|
||||||
|
'x-component': 'Table.Column',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
fields: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Configure fields")}}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
title: '{{t("Configure fields")}}',
|
||||||
|
properties: {
|
||||||
|
configure: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ConfigureFields',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{t("Edit")}}',
|
||||||
|
'x-component': 'EditQuery',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
drawer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer',
|
||||||
|
'x-decorator': 'Form',
|
||||||
|
'x-decorator-props': {
|
||||||
|
useValues: '{{ cm.useValuesFromRecord }}',
|
||||||
|
},
|
||||||
|
title: '{{t("Edit")}}',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Action.Drawer.Footer',
|
||||||
|
properties: {
|
||||||
|
cancel: {
|
||||||
|
title: '{{t("Cancel",{ns:"charts"})}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
useAction: '{{ cm.useCancelAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
title: '{{t("Submit",{ns:"charts"})}}',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
useAction: '{{ cm.useUpdateAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Delete") }}',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
confirm: {
|
||||||
|
title: '{{t("Delete query",{ns:"charts"})}}',
|
||||||
|
content: "{{t('Are you sure you want to delete it?')}}",
|
||||||
|
},
|
||||||
|
useAction: '{{ useDestroyQueryItemAction }}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,64 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
yField: '{{metric}}',
|
||||||
|
xField: '{{dimension}}',
|
||||||
|
seriesField: '{{category}}',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const areaTemplate = {
|
||||||
|
description: '1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)',
|
||||||
|
type: 'Area',
|
||||||
|
title: 'Area',
|
||||||
|
iconId: 'icon-area',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category axis / Dimension",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Value axis / Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Area | G2Plot","https://g2plot.antv.antgroup.com/api/plots/area")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
80
packages/plugins/charts/src/client/templates/BarTemplate.tsx
Normal file
80
packages/plugins/charts/src/client/templates/BarTemplate.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
appendPadding: 10,
|
||||||
|
isGroup: true,
|
||||||
|
yField: '{{dimension}}',
|
||||||
|
xField: '{{metric}}',
|
||||||
|
seriesField: '{{category}}',
|
||||||
|
label: {
|
||||||
|
// 可手动配置 label 数据标签位置
|
||||||
|
position: 'middle', // 'top', 'bottom', 'middle',
|
||||||
|
// 配置样式
|
||||||
|
style: {
|
||||||
|
fill: '#FFFFFF',
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
label: {
|
||||||
|
autoHide: true,
|
||||||
|
autoRotate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const barTemplate = {
|
||||||
|
description: '1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」',
|
||||||
|
type: 'Bar',
|
||||||
|
title: 'Bar',
|
||||||
|
iconId: 'icon-bar',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category axis / Dimension",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Value axis / Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Bar | G2Plot","https://g2plot.antv.antgroup.com/api/plots/bar")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,81 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
appendPadding: 10,
|
||||||
|
isGroup: true,
|
||||||
|
yField: '{{metric}}',
|
||||||
|
xField: '{{dimension}}',
|
||||||
|
seriesField: '{{category}}',
|
||||||
|
label: {
|
||||||
|
// 可手动配置 label 数据标签位置
|
||||||
|
position: 'middle', // 'top', 'bottom', 'middle',
|
||||||
|
// 配置样式
|
||||||
|
style: {
|
||||||
|
fill: '#FFFFFF',
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
label: {
|
||||||
|
autoHide: true,
|
||||||
|
autoRotate: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const columnTemplate = {
|
||||||
|
description: '1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」',
|
||||||
|
type: 'Column',
|
||||||
|
title: 'Column',
|
||||||
|
iconId: 'icon-column',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category axis / Dimension",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Value axis / Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Column | G2Plot","https://g2plot.antv.antgroup.com/api/plots/column")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,65 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
xField: '{{dimension}}',
|
||||||
|
yField: '{{metric}}',
|
||||||
|
seriesField: '{{category}}',
|
||||||
|
legend: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const funnelTemplate = {
|
||||||
|
description: '1 「Unordered Noun」 field, 1 「Numeric」 field',
|
||||||
|
type: 'Funnel',
|
||||||
|
title: 'Funnel',
|
||||||
|
iconId: 'icon-funnel',
|
||||||
|
group: 1,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Sector label / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Funnel Layer Width/Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Funnel | G2Plot","https://g2plot.antv.antgroup.com/api/plots/funnel")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,72 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
yField: '{{metric}}',
|
||||||
|
xField: '{{dimension}}',
|
||||||
|
seriesField: '{{category}}',
|
||||||
|
xAxis: {
|
||||||
|
//type: 'time',
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
// label: {
|
||||||
|
// formatter: '{{(v) => `${v}`.replace(/\d{1,3}(?=(\d{3})+$)/g, (s) => `${s},`)}}',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lineTemplate = {
|
||||||
|
description: '1 「Time」 or 「Order Noun」 field, 1 「Value」 field',
|
||||||
|
type: 'Line',
|
||||||
|
title: 'Line',
|
||||||
|
iconId: 'icon-line',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category axis / Dimension",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Value axis / Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Line | G2Plot","https://g2plot.antv.antgroup.com/api/plots/line")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
68
packages/plugins/charts/src/client/templates/PieTemplate.tsx
Normal file
68
packages/plugins/charts/src/client/templates/PieTemplate.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
appendPadding: 10,
|
||||||
|
angleField: '{{metric}}',
|
||||||
|
colorField: '{{dimension}}',
|
||||||
|
radius: 0.9,
|
||||||
|
label: {
|
||||||
|
type: 'inner',
|
||||||
|
offset: '-30%',
|
||||||
|
content: '{{({percent}) => `${(percent * 100).toFixed(0)}%`}}',
|
||||||
|
style: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interactions: [{ type: 'element-active' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const pieTemplate = {
|
||||||
|
description: '1 「Time」 or 「Order Noun」 field, 1 「Value」 field',
|
||||||
|
title: 'Pie',
|
||||||
|
type: 'Pie',
|
||||||
|
iconId: 'icon-pie',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Sector label / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Sector Angle / Metric",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Pie | G2Plot","https://g2plot.antv.antgroup.com/api/plots/pie")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,71 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
xField: '{{dimension}}',
|
||||||
|
yField: '{{metric}}',
|
||||||
|
appendPadding: [0, 10, 0, 10],
|
||||||
|
xAxis: {
|
||||||
|
tickLine: null,
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
label: false,
|
||||||
|
grid: {
|
||||||
|
alternateColor: 'rgba(0, 0, 0, 0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 开启辅助点
|
||||||
|
point: {
|
||||||
|
size: 2,
|
||||||
|
},
|
||||||
|
area: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const radarTemplate = {
|
||||||
|
description: '1~ 2 「Unordered Noun」 fields, 1 「Numeric」 field',
|
||||||
|
type: 'Radar',
|
||||||
|
title: 'Radar',
|
||||||
|
iconId: 'icon-radar',
|
||||||
|
group: 1,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Branch Tags/Dimensions",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Branch Length/Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Radar | G2Plot","https://g2plot.antv.antgroup.com/api/plots/radar")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,90 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
appendPadding: 10,
|
||||||
|
xField: '{{metric}}',
|
||||||
|
yField: '{{dimension}}',
|
||||||
|
colorField: '{{category}}',
|
||||||
|
shape: 'circle',
|
||||||
|
size: 4,
|
||||||
|
yAxis: {
|
||||||
|
nice: true,
|
||||||
|
line: {
|
||||||
|
style: {
|
||||||
|
stroke: '#aaa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
min: -100,
|
||||||
|
grid: {
|
||||||
|
line: {
|
||||||
|
style: {
|
||||||
|
stroke: '#eee',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
style: {
|
||||||
|
stroke: '#aaa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scatterTemplate = {
|
||||||
|
description: '1 「Numeric」 field, 0~ 1 「Unordered Noun」 field',
|
||||||
|
type: 'Scatter',
|
||||||
|
title: 'Scatter',
|
||||||
|
iconId: 'icon-scatter',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'G2Plot',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {
|
||||||
|
dimension: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Category axis / Dimension",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
metric: {
|
||||||
|
required: true,
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Value axis / Metrics",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: 'string',
|
||||||
|
title: '{{t("Color legend / Dimensional",{ns:"charts"})}}',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
enum: '{{dataSource}}',
|
||||||
|
},
|
||||||
|
jsonConfig: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'div',
|
||||||
|
properties: {
|
||||||
|
template: {
|
||||||
|
required: true,
|
||||||
|
title: '{{t("JSON config",{ns:"charts"})}}',
|
||||||
|
type: 'string',
|
||||||
|
default: JSON5.stringify(chartConfig, null, 2),
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
autoSize: { minRows: 8, maxRows: 16 },
|
||||||
|
},
|
||||||
|
description: '{{jsonConfigDesc("Scatter | G2Plot","https://g2plot.antv.antgroup.com/api/plots/scatter")}}',
|
||||||
|
'x-validator': { json5: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,48 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
|
||||||
|
const validateJSON = {
|
||||||
|
validator: `{{(value, rule)=> {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const val = JSON5.parse(value);
|
||||||
|
if(!isNaN(val)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}}`,
|
||||||
|
message: '{{t("Invalid JSON format",{ ns: "charts" })}}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
appendPadding: 10,
|
||||||
|
angleField: '{{metric}}',
|
||||||
|
colorField: '{{dimension}}',
|
||||||
|
radius: 0.9,
|
||||||
|
label: {
|
||||||
|
type: 'inner',
|
||||||
|
offset: '-30%',
|
||||||
|
content: '{{({percent}) => `${(percent * 100).toFixed(0)}%`}}',
|
||||||
|
style: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interactions: [{ type: 'element-active' }],
|
||||||
|
};
|
||||||
|
export const tableTemplate = {
|
||||||
|
title: '表格展示',
|
||||||
|
type: 'DataSetPreviewTable',
|
||||||
|
group: 2,
|
||||||
|
renderComponent: 'DataSetPreviewTable',
|
||||||
|
defaultChartOptions: chartConfig,
|
||||||
|
configurableProperties: {
|
||||||
|
type: 'void',
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
};
|
21
packages/plugins/charts/src/client/templates/index.ts
Normal file
21
packages/plugins/charts/src/client/templates/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { pieTemplate } from './PieTemplate';
|
||||||
|
import { barTemplate } from './BarTemplate';
|
||||||
|
import { columnTemplate } from './ColumnTemplate';
|
||||||
|
import { lineTemplate } from './LineTemplate';
|
||||||
|
import { areaTemplate } from './AreaTemplate';
|
||||||
|
import { tableTemplate } from './TableTemplate';
|
||||||
|
import { scatterTemplate } from './ScatterTemplate';
|
||||||
|
import { radarTemplate } from './RadarTemplate';
|
||||||
|
import { funnelTemplate } from './FunnelTemplate';
|
||||||
|
|
||||||
|
export const templates = new Map();
|
||||||
|
|
||||||
|
templates.set('Pie', pieTemplate);
|
||||||
|
templates.set('Line', lineTemplate);
|
||||||
|
templates.set('Area', areaTemplate);
|
||||||
|
templates.set('Bar', barTemplate);
|
||||||
|
templates.set('Column', columnTemplate);
|
||||||
|
templates.set('Scatter', scatterTemplate);
|
||||||
|
templates.set('Radar', radarTemplate);
|
||||||
|
templates.set('Funnel', funnelTemplate);
|
||||||
|
// templates.set('DataSetPreviewTable', tableTemplate);
|
39
packages/plugins/charts/src/client/utils.ts
Normal file
39
packages/plugins/charts/src/client/utils.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
import { uid } from '@formily/shared';
|
||||||
|
|
||||||
|
const validateArray = (value) => {
|
||||||
|
try {
|
||||||
|
value = JSON5.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
return 'Please input validate dataset';
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (
|
||||||
|
value.every((item) => {
|
||||||
|
return typeof item === 'object' && Object.keys(item).length > 1;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return 'Please input validate dataset';
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseDataSetString = (str) => {
|
||||||
|
const dataSetDataArray = JSON5.parse(str);
|
||||||
|
if (Array.isArray(dataSetDataArray)) {
|
||||||
|
if (
|
||||||
|
dataSetDataArray.every((item) => {
|
||||||
|
return typeof item === 'object' && Object.keys(item).length > 1;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dataSetDataArray.map((item) => {
|
||||||
|
if (!item?.id) {
|
||||||
|
item.id = uid();
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return dataSetDataArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { validateArray, parseDataSetString };
|
1
packages/plugins/charts/src/index.ts
Normal file
1
packages/plugins/charts/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './server';
|
44
packages/plugins/charts/src/server/actions/chartsQueries.ts
Normal file
44
packages/plugins/charts/src/server/actions/chartsQueries.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import JSON5 from 'json5';
|
||||||
|
import { query } from '../query';
|
||||||
|
|
||||||
|
export const getData = async (ctx, next) => {
|
||||||
|
const { filterByTk } = ctx.action.params;
|
||||||
|
const r = ctx.db.getRepository('chartsQueries');
|
||||||
|
const instance = await r.findOne({ filterByTk });
|
||||||
|
const result = await query[instance.type](instance.options, { db: ctx.db, skipError: true });
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
ctx.body = JSON5.parse(result);
|
||||||
|
} else {
|
||||||
|
ctx.body = result;
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validate = async (ctx, next) => {
|
||||||
|
const { values } = ctx.action.params;
|
||||||
|
ctx.body = {
|
||||||
|
errorMessage: '',
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await query.sql(values, { db: ctx.db, validateSQL: true });
|
||||||
|
} catch (error) {
|
||||||
|
ctx.body = {
|
||||||
|
errorMessage: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listMetadata = async (ctx, next) => {
|
||||||
|
const r = ctx.db.getRepository('chartsQueries');
|
||||||
|
const items = await r.find({ sort: '-id' });
|
||||||
|
ctx.body = items.map((item) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
title: item.title,
|
||||||
|
type: item.type,
|
||||||
|
fields: item.fields,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return next();
|
||||||
|
};
|
0
packages/plugins/charts/src/server/actions/index.ts
Normal file
0
packages/plugins/charts/src/server/actions/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { defineCollection } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'chartsQueries',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'options',
|
||||||
|
type: 'json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fields',
|
||||||
|
type: 'json',
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
1
packages/plugins/charts/src/server/index.ts
Normal file
1
packages/plugins/charts/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './plugin';
|
61
packages/plugins/charts/src/server/plugin.ts
Normal file
61
packages/plugins/charts/src/server/plugin.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { getData, listMetadata, validate } from './actions/chartsQueries';
|
||||||
|
import { query } from './query';
|
||||||
|
|
||||||
|
export class ChartsPlugin extends Plugin {
|
||||||
|
syncFields = async (instance, { transaction }) => {
|
||||||
|
const _data = await query[instance.type](instance.options, { db: this.db, transaction, validateSQL: true });
|
||||||
|
let data;
|
||||||
|
if (typeof _data === 'string') {
|
||||||
|
data = JSON5.parse(_data);
|
||||||
|
} else {
|
||||||
|
data = _data;
|
||||||
|
}
|
||||||
|
const d = Array.isArray(data) ? data?.[0] : data;
|
||||||
|
const fields = Object.keys(d || {}).map((f) => {
|
||||||
|
return {
|
||||||
|
name: f,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
instance.set('fields', fields);
|
||||||
|
};
|
||||||
|
|
||||||
|
afterAdd() {}
|
||||||
|
|
||||||
|
beforeLoad() {
|
||||||
|
this.app.db.on('chartsQueries.beforeCreate', this.syncFields);
|
||||||
|
this.app.db.on('chartsQueries.beforeUpdate', this.syncFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
await this.db.import({
|
||||||
|
directory: resolve(__dirname, 'collections'),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.resourcer.registerActionHandlers({
|
||||||
|
'chartsQueries:getData': getData,
|
||||||
|
'chartsQueries:listMetadata': listMetadata,
|
||||||
|
'chartsQueries:validate': validate,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.acl.registerSnippet({
|
||||||
|
name: 'pm.charts.queries',
|
||||||
|
actions: ['chartsQueries:*'],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.app.acl.allow('chartsQueries', 'getData', 'loggedIn');
|
||||||
|
this.app.acl.allow('chartsQueries', 'listMetadata', 'loggedIn');
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(options?: InstallOptions) {}
|
||||||
|
|
||||||
|
async afterEnable() {}
|
||||||
|
|
||||||
|
async afterDisable() {}
|
||||||
|
|
||||||
|
async remove() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartsPlugin;
|
39
packages/plugins/charts/src/server/query.ts
Normal file
39
packages/plugins/charts/src/server/query.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Database } from '@nocobase/database';
|
||||||
|
|
||||||
|
export const query = {
|
||||||
|
api: async (options) => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
json: async (options) => {
|
||||||
|
return options.data || [];
|
||||||
|
},
|
||||||
|
sql: async (
|
||||||
|
options,
|
||||||
|
{
|
||||||
|
db,
|
||||||
|
transaction,
|
||||||
|
skipError,
|
||||||
|
validateSQL,
|
||||||
|
}: { db: Database; transaction?: any; skipError?: boolean; validateSQL?: boolean },
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// 分号截取,只取第一段
|
||||||
|
const sql: string = options.sql.trim().split(';').shift();
|
||||||
|
if (!sql) {
|
||||||
|
throw new Error('SQL is empty');
|
||||||
|
}
|
||||||
|
if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
|
||||||
|
throw new Error('Only select query allowed');
|
||||||
|
}
|
||||||
|
const [data] = await db.sequelize.query(sql, { transaction });
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
if (skipError) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default query;
|
2
packages/plugins/charts/src/server/shared/index.ts
Normal file
2
packages/plugins/charts/src/server/shared/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
const choicesTypeInterfaceArray = ['radioGroup', 'select'];
|
||||||
|
export { choicesTypeInterfaceArray };
|
@ -33,6 +33,7 @@
|
|||||||
"@nocobase/plugin-users": "0.9.1-alpha.1",
|
"@nocobase/plugin-users": "0.9.1-alpha.1",
|
||||||
"@nocobase/plugin-verification": "0.9.1-alpha.1",
|
"@nocobase/plugin-verification": "0.9.1-alpha.1",
|
||||||
"@nocobase/plugin-workflow": "0.9.1-alpha.1",
|
"@nocobase/plugin-workflow": "0.9.1-alpha.1",
|
||||||
|
"@nocobase/plugin-charts": "0.9.1-alpha.1",
|
||||||
"@nocobase/server": "0.9.1-alpha.1"
|
"@nocobase/server": "0.9.1-alpha.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -23,6 +23,7 @@ export class PresetNocoBase extends Plugin {
|
|||||||
'duplicator',
|
'duplicator',
|
||||||
'iframe-block',
|
'iframe-block',
|
||||||
'formula-field',
|
'formula-field',
|
||||||
|
'charts',
|
||||||
];
|
];
|
||||||
|
|
||||||
localPlugins = [
|
localPlugins = [
|
||||||
|
@ -37,7 +37,7 @@ export const HelloBlockInitializer = (props) => {
|
|||||||
|
|
||||||
export default React.memo((props) => {
|
export default React.memo((props) => {
|
||||||
const items = useContext(SchemaInitializerContext);
|
const items = useContext(SchemaInitializerContext);
|
||||||
const children = items.BlockInitializers.items[2].children;
|
const children = items.BlockInitializers.items[1].children;
|
||||||
children.push({
|
children.push({
|
||||||
key: 'hello',
|
key: 'hello',
|
||||||
type: 'item',
|
type: 'item',
|
||||||
|
25
yarn.lock
25
yarn.lock
@ -6442,7 +6442,14 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||||
|
|
||||||
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
|
"@types/react-dom@^16.9.8":
|
||||||
|
version "16.9.18"
|
||||||
|
resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-16.9.18.tgz#1fda8b84370b1339d639a797a84c16d5a195b419"
|
||||||
|
integrity sha512-lmNARUX3+rNF/nmoAFqasG0jAA7q6MeGZK/fdeLwY3kAA4NPgHHrG5bNQe2B5xmD4B+x6Z6h0rEJQ7MEEgQxsw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "^16"
|
||||||
|
|
||||||
|
"@types/react-dom@^17.0.0":
|
||||||
version "17.0.11"
|
version "17.0.11"
|
||||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
||||||
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
||||||
@ -6502,7 +6509,7 @@
|
|||||||
"@types/history" "*"
|
"@types/history" "*"
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
|
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
|
||||||
version "17.0.34"
|
version "17.0.34"
|
||||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
||||||
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
||||||
@ -6511,6 +6518,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/react@^16", "@types/react@^16.9.43":
|
||||||
|
version "16.14.35"
|
||||||
|
resolved "https://registry.npmmirror.com/@types/react/-/react-16.14.35.tgz#9d3cf047d85aca8006c4776693124a5be90ee429"
|
||||||
|
integrity sha512-NUEiwmSS1XXtmBcsm1NyRRPYjoZF2YTE89/5QiLt5mlGffYK9FQqOKuOLuXNrjPQV04oQgaZG+Yq02ZfHoFyyg==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@1.17.1":
|
"@types/resolve@1.17.1":
|
||||||
version "1.17.1"
|
version "1.17.1"
|
||||||
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||||
@ -15613,6 +15629,11 @@ json5@^2.1.0, json5@^2.2.1:
|
|||||||
resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||||
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||||
|
|
||||||
|
json5@^2.2.3:
|
||||||
|
version "2.2.3"
|
||||||
|
resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||||
|
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
jsonfile@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
Loading…
Reference in New Issue
Block a user