This commit is contained in:
muyun.my 2020-09-22 11:10:17 +08:00
parent 47f55cafce
commit 4dd2eefc90
16 changed files with 367 additions and 318 deletions

View File

@ -139,7 +139,7 @@ export default {
type: 'PanelIcon', type: 'PanelIcon',
props: { props: {
align: 'top', align: 'top',
icon: 'wenjian', icon: 'shujuyuan',
description: '数据源面板', description: '数据源面板',
panelProps: { panelProps: {
floatable: true, floatable: true,

View File

@ -1,7 +1,7 @@
{ {
"name": "@ali/lowcode-plugin-components-pane", "name": "@ali/lowcode-plugin-components-pane",
"version": "1.0.7-0", "version": "0.1.0-beta.0",
"description": "alibaba lowcode editor component-list plugin", "description": "低代码引擎数据源配置面板",
"files": [ "files": [
"es/", "es/",
"lib/" "lib/"

View File

@ -0,0 +1,5 @@
## 插件开发
[https://yuque.antfin-inc.com/ali-lowcode/docs/ip4awq](插件开发文档)。
本地开发需要在 v14.4.0 的 node 环境下进行。

View File

@ -2,9 +2,49 @@
对页面的数据源进行管理(新建,编辑,导入)。 对页面的数据源进行管理(新建,编辑,导入)。
一个 pluginProps 的例子
```
{
importPlugins: [
{
name: 'code',
title: '源码',
content: DataSourceImportPluginCode,
},
],
dataSourceTypes: [
{
type: 'mopen',
schema: {
type: 'object',
properties: {
options: {
type: 'object',
properties: {
uri: {
title: 'api',
},
v: {
title: 'v',
type: 'string',
},
appKey: {
title: 'appKey',
type: 'string',
},
},
},
},
},
},
],
}
```
## 数据源类型定义 ## 数据源类型定义
内置 fetch 和 mtop 类型,支持传入自定义类型。 内置 fetchmtopjsonp 类型,支持传入自定义类型。
``` ```
type DataSourceType = { type DataSourceType = {
@ -29,17 +69,15 @@ interface DataSourcePaneImportPlugin {
componentProps?: DataSourcePaneImportPluginCustomProps; componentProps?: DataSourcePaneImportPluginCustomProps;
} }
interface DataSourcePaneImportPluginCustomProps { interface DataSourcePaneImportPluginComponentProps {
[customPropName: string]: any; onImport?: (dataSourceList: DataSourceConfig[]) => void;
onCancel?: () => void;
dataSourceTypes?: DataSourceType[];
} }
interface DataSourcePaneImportPluginComponentProps extends DataSourcePaneImportPluginCustomProps { interface DataSourcePaneImportPluginCustomProps extends DataSourcePaneImportPluginComponentProps {
onChange: (dataSourceList: DataSourceConfig[]) => void; [customPropName: string]: any;
} }
``` ```
## 插件开发
[https://yuque.antfin-inc.com/ali-lowcode/docs/ip4awq](插件开发文档)。
本地开发需要在 v14.4.0 的 node 环境下进行。

View File

@ -2,14 +2,15 @@ TODO
--- ---
* 多语言 * 多语言
* ICON
* 支持变量
* 定制样式 * 定制样式
* 不使用 bind * 不使用 bind
* 表达式 setter 的联想 * 表达式 setter 的联想
* [later]表达式和其他类型的切换 * [later]表达式和其他类型的切换
## 提案
* mock
* 变量,上下文的提案 * 变量,上下文的提案
* mock 的提案
## 问题 ## 问题

View File

@ -1,13 +1,14 @@
{ {
"name": "@ali/lowcode-plugin-datasource-pane", "name": "@ali/lowcode-plugin-datasource-pane",
"version": "1.0.7-0", "version": "0.1.0-beta.3",
"description": "低代码引擎数据源面板", "description": "低代码引擎数据源面板",
"main": "lib/index.js", "main": "lib/index.js",
"files": [ "files": [
"lib" "lib"
], ],
"scripts": { "scripts": {
"build": "tsc", "build": "build-scripts build",
"cloud-build": "build-scripts build --skip-demo",
"test": "ava", "test": "ava",
"test:snapshot": "ava --update-snapshots" "test:snapshot": "ava --update-snapshots"
}, },
@ -29,6 +30,7 @@
"monaco-editor": "^0.20.0" "monaco-editor": "^0.20.0"
}, },
"dependencies": { "dependencies": {
"@alib/build-scripts": "^0.1.3",
"@ali/lowcode-editor-setters": "^1.0.7-0", "@ali/lowcode-editor-setters": "^1.0.7-0",
"@alifd/next": "^1.20.28", "@alifd/next": "^1.20.28",
"@formily/next": "^1.3.2", "@formily/next": "^1.3.2",
@ -40,5 +42,9 @@
"react-monaco-editor": "^0.40.0", "react-monaco-editor": "^0.40.0",
"styled-components": "^5.2.0", "styled-components": "^5.2.0",
"traverse": "^0.6.6" "traverse": "^0.6.6"
},
"homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-plugin-datasource-pane@0.1.0-beta.1/build/index.html",
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
} }
} }

View File

@ -1,20 +1,17 @@
// @todo schema default // @todo schema default
import React, { PureComponent, ReactElement, FC } from 'react'; import React, { PureComponent } from 'react';
import { Button } from '@alifd/next'; import { Button } from '@alifd/next';
import { SchemaForm, FormButtonGroup, Submit } from '@formily/next'; import { SchemaForm, FormButtonGroup, Submit } from '@formily/next';
import { ArrayTable, Input, Switch, NumberPicker } from '@formily/next-components'; import { ArrayTable, Input, Switch, NumberPicker } from '@formily/next-components';
import _isPlainObject from 'lodash/isPlainObject'; import _isPlainObject from 'lodash/isPlainObject';
import _isArray from 'lodash/isArray'; import _isArray from 'lodash/isArray';
import _isNumber from 'lodash/isNumber';
import _isString from 'lodash/isString';
import _isBoolean from 'lodash/isBoolean';
import _cloneDeep from 'lodash/cloneDeep'; import _cloneDeep from 'lodash/cloneDeep';
import _mergeWith from 'lodash/mergeWith'; import _mergeWith from 'lodash/mergeWith';
import _get from 'lodash/get'; import _get from 'lodash/get';
import _tap from 'lodash/tap';
import traverse from 'traverse'; import traverse from 'traverse';
import { DataSourceConfig } from '@ali/lowcode-types';
import { ParamValue, JSFunction } from './form-components'; import { ParamValue, JSFunction } from './form-components';
import { DataSourceType, DataSourceConfig } from './types'; import { DataSourceType } from './types';
// @todo $ref // @todo $ref
@ -89,11 +86,11 @@ const SCHEMA = {
export interface DataSourceFormProps { export interface DataSourceFormProps {
dataSourceType: DataSourceType; dataSourceType: DataSourceType;
dataSource?: DataSourceConfig; dataSource?: DataSourceConfig;
omComplete?: (dataSource: DataSourceConfig) => void; onComplete?: (dataSource: DataSourceConfig) => void;
onCancel?: () => void;
} }
export interface DataSourceFormState { export type DataSourceFormState = {};
}
/** /**
* ID * ID
@ -101,65 +98,63 @@ export interface DataSourceFormState {
export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourceFormState> { export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourceFormState> {
state = {}; state = {};
handleFormSubmit = (formData) => { handleFormSubmit = (formData: any) => {
// @todo mutable? // @todo mutable?
if (_isArray(_get(formData, 'options.params'))) { if (_isArray(_get(formData, 'options.params'))) {
formData.options.params = formData.options.params.reduce((acc, cur) => { formData.options.params = formData.options.params.reduce((acc: any, cur: any) => {
if (!cur.name) return; if (!cur.name) return acc;
acc[cur.name] = cur.value; acc[cur.name] = cur.value;
return acc; return acc;
}, {}); }, {});
} }
if (_isArray(_get(formData, 'options.headers'))) { if (_isArray(_get(formData, 'options.headers'))) {
formData.options.headers = formData.options.headers.reduce((acc, cur) => { formData.options.headers = formData.options.headers.reduce((acc: any, cur: any) => {
if (!cur.name) return; if (!cur.name) return acc;
acc[cur.name] = cur.value; acc[cur.name] = cur.value;
return acc; return acc;
}, {}); }, {});
} }
console.log('submit', formData); // console.log('submit', formData);
this.props?.onComplete(formData); this.props.onComplete?.(formData);
}; };
deriveInitialData = (dataSource = {}) => { handleCancel = () => {
this.props.onCancel?.();
};
deriveInitialData = (dataSource: object = {}) => {
const { dataSourceType } = this.props; const { dataSourceType } = this.props;
const result = _cloneDeep(dataSource); const result: any = _cloneDeep(dataSource);
if (_isPlainObject(_get(result, 'options.params'))) { if (_isPlainObject(_get(result, 'options.params'))) {
result.options.params = Object.keys(result.options.params).reduce( result.options.params = Object.keys(result.options.params).reduce((acc: any, cur: any) => {
(acc, cur) => {
acc.push({ acc.push({
name: cur, name: cur,
value: result.options.params[cur] value: result.options.params[cur],
}); });
return acc; return acc;
}, }, []);
[]
);
} }
if (_isPlainObject(_get(result, 'options.headers'))) { if (_isPlainObject(_get(result, 'options.headers'))) {
result.options.headers = Object.keys(result.options.headers).reduce( result.options.headers = Object.keys(result.options.headers).reduce((acc: any, cur: any) => {
(acc, cur) => {
acc.push({ acc.push({
name: cur, name: cur,
value: result.options.headers[cur] value: result.options.headers[cur],
}); });
return acc; return acc;
}, }, []);
[]
);
} }
result.type = dataSourceType.type; result.type = dataSourceType.type;
return result; return result;
} };
deriveSchema = () => { deriveSchema = () => {
const { dataSourceType } = this.props; const { dataSourceType } = this.props;
// @todo 减小覆盖的风险 // @todo 减小覆盖的风险
const formSchema = _mergeWith({}, SCHEMA, dataSourceType.schema, (objValue, srcValue) => { const formSchema: any = _mergeWith({}, SCHEMA, dataSourceType.schema, (objValue, srcValue) => {
if (_isArray(objValue)) { if (_isArray(objValue)) {
return srcValue; return srcValue;
} }
@ -185,16 +180,11 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
type: 'string', type: 'string',
'x-component': 'ParamValue', 'x-component': 'ParamValue',
'x-component-props': { 'x-component-props': {
types: [ types: ['string', 'boolean', 'expression', 'number'],
'string', },
'boolean',
'expression',
'number'
],
}, },
}, },
}, },
}
}; };
delete formSchema.properties.options.properties.params.properties; delete formSchema.properties.options.properties.params.properties;
} }
@ -218,9 +208,7 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
type: 'string', type: 'string',
'x-component': 'ParamValue', 'x-component': 'ParamValue',
'x-component-props': { 'x-component-props': {
types: [ types: ['string'],
'string'
],
}, },
}, },
}, },
@ -257,8 +245,12 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
Switch, Switch,
JSFunction, JSFunction,
}} }}
labelCol={4} labelCol={{
wrapperCol={19} span: 4,
}}
wrapperCol={{
span: 19,
}}
schema={this.deriveSchema()} schema={this.deriveSchema()}
initialValues={this.deriveInitialData(dataSource)} initialValues={this.deriveInitialData(dataSource)}
> >

View File

@ -1,60 +1,46 @@
import React, { PureComponent, createRef } from 'react'; import React, { PureComponent } from 'react';
import { Button, Input, Radio, NumberPicker, Switch } from '@alifd/next';
import { connect } from '@formily/react-schema-renderer'; import { connect } from '@formily/react-schema-renderer';
import _isPlainObject from 'lodash/isPlainObject';
import _isArray from 'lodash/isArray';
import _isNumber from 'lodash/isNumber';
import _isString from 'lodash/isString';
import _isBoolean from 'lodash/isBoolean';
import _get from 'lodash/get';
import _tap from 'lodash/tap';
import MonacoEditor, { EditorWillMount } from 'react-monaco-editor'; import MonacoEditor, { EditorWillMount } from 'react-monaco-editor';
import _noop from 'lodash/noop';
const { Group: RadioGroup } = Radio; import { editor } from 'monaco-editor';
export interface JSFunctionProps { export interface JSFunctionProps {
className: string; className: string;
value: any; value: any;
onChange?: () => void; onChange?: (val: any) => void;
} }
export interface JSFunctionState { export type JSFunctionState = {};
}
class JSFunctionComp extends PureComponent<JSFunctionProps, JSFunctionState> { class JSFunctionComp extends PureComponent<JSFunctionProps, JSFunctionState> {
static isFieldComponent = true; static isFieldComponent = true;
private monacoRef = createRef<editor.IStandaloneCodeEditor>(); static defaultProps = {
onChange: _noop,
};
constructor(props) { private monacoRef: any = null;
super(props);
this.handleEditorChange = this.handleEditorChange.bind(this);
}
handleEditorChange = () => { handleEditorChange = () => {
if (this.monacoRef.current) { if (this.monacoRef) {
if ( if (!(this.monacoRef as any).getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
!(this.monacoRef.current as editor.IStandaloneCodeEditor) this.props.onChange?.((this.monacoRef as any)?.getModels()?.[0]?.getValue());
.getModelMarkers()
.find((marker: editor.IMarker) => marker.owner === 'json')
) {
this.props?.onChange(this.monacoRef.current?.getModels()?.[0]?.getValue());
} }
} }
}; };
handleEditorWillMount: EditorWillMount = (editor) => { handleEditorWillMount: EditorWillMount = (editor) => {
(this.monacoRef as MutableRefObject<editor.IStandaloneCodeEditor>).current = editor?.editor; this.monacoRef = editor?.editor;
}; };
render() { render() {
const { value, onChange } = this.props; const { value } = this.props;
return ( return (
<MonacoEditor <MonacoEditor
theme="vs-dark" theme="vs-dark"
width={400} width={400}
height={150} height={150}
defaulvValue={value} defaultValue={value}
language="js" language="js"
onChange={this.handleEditorChange} onChange={this.handleEditorChange}
editorWillMount={this.handleEditorWillMount} editorWillMount={this.handleEditorWillMount}

View File

@ -19,12 +19,12 @@ type ParamValueType = 'string' | 'number' | 'boolean' | 'expression';
export interface ParamValueProps { export interface ParamValueProps {
className: string; className: string;
value: any; value: any;
onChange?: () => void; onChange?: (value: any) => void;
types: ParamValueType[]; types: ParamValueType[];
} }
export interface ParamValueState { export interface ParamValueState {
type: 'string' | 'number' | 'boolean' | ''; type: ParamValueType;
} }
const TYPE_LABEL_MAP = { const TYPE_LABEL_MAP = {
@ -41,16 +41,17 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
types: ['string', 'boolean', 'number', 'expression'], types: ['string', 'boolean', 'number', 'expression'],
}; };
state = { state: ParamValueState = {
type: '', type: 'string',
}; };
constructor(props) { constructor(props: ParamValueProps) {
super(props); super(props);
this.state.type = this.getTypeFromValue(this.props.value); this.state.type = this.getTypeFromValue(this.props.value);
} }
getTypeFromValue = (value) => { // @todo
getTypeFromValue = (value: any) => {
if (_isBoolean(value)) { if (_isBoolean(value)) {
return 'boolean'; return 'boolean';
} else if (_isNumber(value)) { } else if (_isNumber(value)) {
@ -62,22 +63,26 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
}; };
// @todo 需要再 bind 一次? // @todo 需要再 bind 一次?
handleChange = (value) => { handleChange = (value: any) => {
this.props?.onChange(value); this.props?.onChange?.(value);
}; };
componentDidUpdate(prevProps) { componentDidUpdate(prevProps: ParamValueProps) {
if (this.props.value !== prevProps.value) { if (this.props.value !== prevProps.value) {
this.setState({ this.setState({
value: this.props.value,
type: this.getTypeFromValue(this.props.value), type: this.getTypeFromValue(this.props.value),
}); });
} }
} }
handleTypeChange = (type) => { handleTypeChange = (type: string) => {
this.setState(({ value }) => { this.setState(
let nextValue = value || ''; {
type: type as ParamValueType,
},
() => {
let nextValue = this.props.value || '';
const { type } = this.state;
if (type === 'string') { if (type === 'string') {
nextValue = nextValue.toString(); nextValue = nextValue.toString();
} else if (type === 'number') { } else if (type === 'number') {
@ -87,11 +92,9 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
} else if (type === 'expression') { } else if (type === 'expression') {
nextValue = ''; nextValue = '';
} }
return { this.props.onChange?.(nextValue);
value: nextValue, },
type, );
};
});
}; };
renderTypeSelect = () => { renderTypeSelect = () => {
@ -107,6 +110,7 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
value: item, value: item,
}))} }))}
value={type} value={type}
onChange={this.handleTypeChange}
/> />
); );
} }
@ -134,10 +138,10 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
return ( return (
<div className="param-value"> <div className="param-value">
{this.renderTypeSelect()} {this.renderTypeSelect()}
{type === 'string' && <Input onChange={this.handleChange.bind(this)} value={value} />} {type === 'string' && <Input onChange={this.handleChange} value={value} />}
{type === 'boolean' && <Switch onChange={this.handleChange.bind(this)} checked={value} />} {type === 'boolean' && <Switch onChange={this.handleChange} checked={value} />}
{type === 'number' && <NumberPicker onChange={this.handleChange.bind(this)} value={value} />} {type === 'number' && <NumberPicker onChange={this.handleChange} value={value} />}
{type === 'expression' && <ExpressionSetter onChange={this.handleChange.bind(this)} value={value} />} {type === 'expression' && <ExpressionSetter onChange={this.handleChange} value={value} />}
</div> </div>
); );
} }

View File

@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/indent */
// @todo 缩进问题
/** /**
* *
* @todo editor types * @todo editor types
*/ */
import React, { PureComponent, createRef, MutableRefObject } from 'react'; import React, { PureComponent } from 'react';
import { Button } from '@alifd/next'; import { Button } from '@alifd/next';
import _noop from 'lodash/noop'; import _noop from 'lodash/noop';
import _isArray from 'lodash/isArray'; import _isArray from 'lodash/isArray';
@ -35,7 +37,7 @@ export class DataSourceImportPluginCode extends PureComponent<
type: 'http', type: 'http',
id: 'test', id: 'test',
}, },
] ],
}; };
state = { state = {
@ -43,7 +45,7 @@ export class DataSourceImportPluginCode extends PureComponent<
isCodeValid: true, isCodeValid: true,
}; };
private monacoRef = createRef<editor.IStandaloneCodeEditor>(); private monacoRef: any;
constructor(props: DataSourceImportPluginCodeProps) { constructor(props: DataSourceImportPluginCodeProps) {
super(props); super(props);
@ -77,14 +79,12 @@ export class DataSourceImportPluginCode extends PureComponent<
}; };
handleComplete = () => { handleComplete = () => {
if (this.monacoRef.current) { if (this.monacoRef) {
if ( if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
!(this.monacoRef.current as editor.IStandaloneCodeEditor)
.getModelMarkers()
.find((marker: editor.IMarker) => marker.owner === 'json')
) {
this.setState({ isCodeValid: true }); this.setState({ isCodeValid: true });
this.props?.onImport(this.deriveValue(JSON.parse(_last(this.monacoRef.current.getModels()).getValue()))); const model: any = _last(this.monacoRef.getModels());
if (!model) return;
this.props.onImport?.(this.deriveValue(JSON.parse(model.getValue())));
return; return;
} }
} }
@ -92,30 +92,28 @@ export class DataSourceImportPluginCode extends PureComponent<
}; };
handleEditorChange = () => { handleEditorChange = () => {
if (this.monacoRef.current) { if (this.monacoRef) {
if ( if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
!(this.monacoRef.current as editor.IStandaloneCodeEditor)
.getModelMarkers()
.find((marker: editor.IMarker) => marker.owner === 'json')
) {
this.setState({ isCodeValid: true }); this.setState({ isCodeValid: true });
} }
} }
}; };
handleEditorWillMount: EditorWillMount = (editor) => { handleEditorWillMount: EditorWillMount = (editor) => {
(this.monacoRef as MutableRefObject<editor.IStandaloneCodeEditor>).current = editor?.editor; this.monacoRef = editor?.editor;
// @todo 格式化一次 // @todo 格式化一次
}; };
handleCodeChagne = (code) => { handleCodeChagne = (code: string) => {
this.setState({ code }); this.setState({ code });
} };
render() { render() {
const { onCancel = _noop } = this.props; const { onCancel = _noop } = this.props;
const { code, isCodeValid } = this.state; const { code, isCodeValid } = this.state;
// @todo
// formatOnType formatOnPaste
return ( return (
<div className="lowcode-plugin-datasource-import-plugin-code"> <div className="lowcode-plugin-datasource-import-plugin-code">
<MonacoEditor <MonacoEditor
@ -126,13 +124,13 @@ export class DataSourceImportPluginCode extends PureComponent<
language="json" language="json"
onChange={this.handleEditorChange} onChange={this.handleEditorChange}
editorWillMount={this.handleEditorWillMount} editorWillMount={this.handleEditorWillMount}
formatOnType
formatOnPaste
/> />
{!isCodeValid && <p className="error-msg"></p>} {!isCodeValid && <p className="error-msg"></p>}
<p className="btns"> <p className="btns">
<Button onClick={onCancel}></Button> <Button onClick={onCancel}></Button>
<Button type="primary" onClick={this.handleComplete}></Button> <Button type="primary" onClick={this.handleComplete}>
</Button>
</p> </p>
</div> </div>
); );

View File

@ -19,9 +19,9 @@ export interface DataSourcePaneState {
active: boolean; active: boolean;
} }
const BUILTIN_DATASOURCE_TYPES = [ const BUILTIN_DATASOURCE_TYPES: DataSourceType[] = [
{ {
type: 'http', type: 'fetch',
schema: { schema: {
type: 'object', type: 'object',
properties: { properties: {
@ -56,9 +56,25 @@ const BUILTIN_DATASOURCE_TYPES = [
}, },
}, },
}, },
{
type: 'jsonp',
schema: {
type: 'object',
properties: {
options: {
type: 'object',
properties: {
method: {
enum: ['GET'],
},
},
},
},
},
},
]; ];
const BUILTIN_IMPORT_PLUGINS = [ const BUILTIN_IMPORT_PLUGINS: DataSourcePaneImportPlugin[] = [
{ {
name: 'default', name: 'default',
title: '源码', title: '源码',
@ -98,7 +114,7 @@ export default class DataSourcePanePlugin extends PureComponent<DataSourcePanePr
} }
render() { render() {
const { importPlugins, dataSourceTypes, editor } = this.props; const { importPlugins, dataSourceTypes = [], editor } = this.props;
const { active } = this.state; const { active } = this.state;
if (!active) return null; if (!active) return null;
@ -114,3 +130,5 @@ export default class DataSourcePanePlugin extends PureComponent<DataSourcePanePr
); );
} }
} }
export * from './types';

View File

@ -10,6 +10,13 @@ import { DataSourceType } from './types';
const { Column: TableCol } = Table; const { Column: TableCol } = Table;
function deriveTypeFromValue(val: any) {
if (_isBoolean(val)) return 'bool';
if (_isNumber(val)) return 'number';
if (_isPlainObject(val)) return 'obj';
return 'string';
}
export interface DataSourceListProps { export interface DataSourceListProps {
dataSourceTypes: DataSourceType[]; dataSourceTypes: DataSourceType[];
dataSource: DataSourceConfig[]; dataSource: DataSourceConfig[];
@ -56,7 +63,7 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
deriveListDataSource = () => { deriveListDataSource = () => {
const { filteredType, keyword } = this.state; const { filteredType, keyword } = this.state;
const { dataSource } = this.props; const { dataSource, dataSourceTypes } = this.props;
return ( return (
dataSource dataSource
@ -69,6 +76,7 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
<div className="datasource-item-id" title={item.id}> <div className="datasource-item-id" title={item.id}>
{item.id} {item.id}
</div> </div>
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
<Balloon <Balloon
trigger={<Button size="small"></Button>} trigger={<Button size="small"></Button>}
align="b" align="b"
@ -105,27 +113,24 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
dataIndex="value" dataIndex="value"
cell={(val: any) => ( cell={(val: any) => (
<div> <div>
<Tag> <Tag>{deriveTypeFromValue(val)}</Tag>
{_isBoolean(val)
? 'bool'
: _isNumber(val)
? 'number'
: _isPlainObject(val)
? 'obj'
: 'string'}
</Tag>
{val.toString()} {val.toString()}
</div> </div>
)} )}
/> />
</Table> </Table>
</Balloon> </Balloon>
)}
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
<Button size="small" onClick={this.handleEditDataSource.bind(this, item.id)}> <Button size="small" onClick={this.handleEditDataSource.bind(this, item.id)}>
</Button> </Button>
)}
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
<Button size="small" onClick={this.handleDuplicateDataSource.bind(this, item.id)}> <Button size="small" onClick={this.handleDuplicateDataSource.bind(this, item.id)}>
</Button> </Button>
)}
<Button size="small" onClick={this.handleRemoveDataSource.bind(this, item.id)}> <Button size="small" onClick={this.handleRemoveDataSource.bind(this, item.id)}>
</Button> </Button>
@ -142,6 +147,7 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
render() { render() {
const { dataSourceTypes } = this.props; const { dataSourceTypes } = this.props;
const { filteredType } = this.state;
return ( return (
<div className="lowcode-plugin-datasource-pane-list"> <div className="lowcode-plugin-datasource-pane-list">
@ -149,11 +155,18 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
hasClear hasClear
onSearch={this.handleSearch} onSearch={this.handleSearch}
filterProps={{}} filterProps={{}}
defaultFilterValue={dataSourceTypes?.[0]?.type} defaultFilterValue={filteredType}
filter={dataSourceTypes.map((type) => ({ filter={[
{
label: '全部',
value: '',
},
].concat(
dataSourceTypes.map((type) => ({
label: type?.type, label: type?.type,
value: type?.type, value: type?.type,
}))} })),
)}
onFilterChange={this.handleSearchFilterChange} onFilterChange={this.handleSearchFilterChange}
/> />
<div className="datasource-list"> <div className="datasource-list">

View File

@ -1,10 +1,10 @@
import { createIntl } from '@ali/lowcode-editor-core'; import { createIntl } from '@ali/lowcode-editor-core';
import en_US from './en-US.json'; import enUS from './en-US.json';
import zh_CN from './zh-CN.json'; import zhCN from './zh-CN.json';
const { intl, intlNode, getLocale, setLocale } = createIntl({ const { intl, intlNode, getLocale, setLocale } = createIntl({
'en-US': en_US, 'en-US': enUS,
'zh-CN': zh_CN, 'zh-CN': zhCN,
}); });
export { intl, intlNode, getLocale, setLocale }; export { intl, intlNode, getLocale, setLocale };

View File

@ -6,7 +6,6 @@ import { DataSource, DataSourceConfig } from '@ali/lowcode-types';
import { Tab, Button, MenuButton, Message, Dialog } from '@alifd/next'; import { Tab, Button, MenuButton, Message, Dialog } from '@alifd/next';
import _cloneDeep from 'lodash/cloneDeep'; import _cloneDeep from 'lodash/cloneDeep';
import _uniqueId from 'lodash/uniqueId'; import _uniqueId from 'lodash/uniqueId';
import _startsWith from 'lodash/startsWith';
import _isArray from 'lodash/isArray'; import _isArray from 'lodash/isArray';
import _get from 'lodash/get'; import _get from 'lodash/get';
import List from './list'; import List from './list';
@ -29,15 +28,12 @@ export interface DataSourcePaneProps {
onSchemaChange?: (schema: DataSource) => void; onSchemaChange?: (schema: DataSource) => void;
} }
export interface TabItemProps { export interface TabItem {
key: string; key: string;
title: string; title: string;
closeable: boolean; closeable: boolean;
data: any; data?: any;
} content?: any;
export interface TabItem {
tabItemProps: TabItemProps;
} }
export interface DataSourcePaneState { export interface DataSourcePaneState {
@ -47,32 +43,18 @@ export interface DataSourcePaneState {
} }
export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourcePaneState> { export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourcePaneState> {
state = { state: DataSourcePaneState = {
dataSourceList: [...(this.props.defaultSchema?.list || [])], dataSourceList: [...(this.props.defaultSchema?.list || [])],
tabItems: [ tabItems: [
{ {
tabItemProps: {
key: TAB_ITEM_LIST, key: TAB_ITEM_LIST,
title: '数据源列表', title: '数据源列表',
closeable: false, closeable: false,
}, },
},
], ],
activeTabKey: TAB_ITEM_LIST, activeTabKey: TAB_ITEM_LIST,
}; };
constructor(props) {
super(props);
// this.handleDataSourceListChange = this.handleDataSourceListChange.bind(this);
// this.handleImportDataSourceList = this.handleImportDataSourceList.bind(this);
// this.handleCreateDataSource = this.handleCreateDataSource.bind(this);
// this.handleUpdateDataSource = this.handleUpdateDataSource.bind(this);
// this.handleRemoveDataSource = this.handleRemoveDataSource.bind(this);
// this.handleDuplicateDataSource = this.handleDuplicateDataSource.bind(this);
// this.handleEditDataSource = this.handleEditDataSource.bind(this);
// this.handleTabChange = this.handleTabChange.bind(this);
}
handleDataSourceListChange = (dataSourceList?: DataSourceConfig[]) => { handleDataSourceListChange = (dataSourceList?: DataSourceConfig[]) => {
if (dataSourceList) { if (dataSourceList) {
this.setState({ dataSourceList }); this.setState({ dataSourceList });
@ -99,16 +81,16 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
return; return;
} }
const repeatedDataSourceList = toImport.filter( const repeatedDataSourceList = toImport.filter(
item => !!this.state.dataSourceList.find( (item) => !!this.state.dataSourceList.find((dataSource) => dataSource.id === item.id),
dataSource => dataSource.id === item.id
)
); );
if (repeatedDataSourceList.length > 0) { if (repeatedDataSourceList.length > 0) {
Dialog.confirm({ Dialog.confirm({
content: `数据源(${repeatedDataSourceList.map(item => item.id).join('')})已存在,如果导入会替换原数据源,是否继续?`, content: `数据源(${repeatedDataSourceList
.map((item) => item.id)
.join('')}`,
onOk: () => { onOk: () => {
importDataSourceList(); importDataSourceList();
} },
}); });
return; return;
} }
@ -131,14 +113,12 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
}, },
); );
}; };
if (this.state.dataSourceList.find( if (this.state.dataSourceList.find((dataSource) => dataSource.id === toCreate.id)) {
dataSource => dataSource.id === toCreate.id
)) {
Dialog.confirm({ Dialog.confirm({
content: `数据源(${toCreate.id})已存在,如果导入会替换原数据源,是否继续?`, content: `数据源(${toCreate.id})已存在,如果导入会替换原数据源,是否继续?`,
onOk: () => { onOk: () => {
create(); create();
} },
}); });
return; return;
} }
@ -179,12 +159,13 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
content: `确定要删除吗?`, content: `确定要删除吗?`,
onOk: () => { onOk: () => {
remove(); remove();
} },
}); });
}; };
handleDuplicateDataSource = (dataSourceId: string) => { handleDuplicateDataSource = (dataSourceId: string) => {
const target = this.state.dataSourceList.find((item) => item.id === dataSourceId); const target = this.state.dataSourceList.find((item) => item.id === dataSourceId);
if (!target) return;
const cloned = _cloneDeep(target); const cloned = _cloneDeep(target);
this.openCreateDataSourceTab({ this.openCreateDataSourceTab({
@ -195,6 +176,7 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
handleEditDataSource = (dataSourceId: string) => { handleEditDataSource = (dataSourceId: string) => {
const target = this.state.dataSourceList.find((item) => item.id === dataSourceId); const target = this.state.dataSourceList.find((item) => item.id === dataSourceId);
if (!target) return;
const cloned = _cloneDeep(target); const cloned = _cloneDeep(target);
this.openEditDataSourceTab({ this.openEditDataSourceTab({
@ -207,22 +189,18 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
this.setState({ activeTabKey }); this.setState({ activeTabKey });
}; };
openCreateDataSourceTab = (dataSourceTypeName: string) => { openCreateDataSourceTab = (dataSource: DataSourceConfig) => {
const { tabItems } = this.state; const { tabItems } = this.state;
const { dataSourceTypes } = this.props; const { dataSourceTypes = [] } = this.props;
if (!tabItems.find((item) => item.tabItemProps.key === TAB_ITEM_CREATE)) { if (!tabItems.find((item) => item.key === TAB_ITEM_CREATE)) {
this.setState(({ tabItems }) => ({ this.setState(({ tabItems }) => ({
tabItems: tabItems.concat({ tabItems: tabItems.concat({
tabItemProps: {
key: TAB_ITEM_CREATE, key: TAB_ITEM_CREATE,
title: `添加数据源`, title: `添加数据源`,
closeable: true, closeable: true,
data: { data: {
dataSourceType: dataSourceTypes?.find( dataSourceType: dataSourceTypes?.find((type) => type.type === dataSource.type),
type => type.type === dataSourceTypeName
),
},
}, },
}), }),
})); }));
@ -232,23 +210,31 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
} }
}; };
handleCreateDataSourceBtnClick = (dataSourceType: string) => {
this.openCreateDataSourceTab({
type: dataSourceType,
} as DataSourceConfig);
};
handleCreateDataSourceMenuBtnClick = (dataSourceType: string) => {
this.openCreateDataSourceTab({
type: dataSourceType,
} as DataSourceConfig);
};
openEditDataSourceTab = (dataSource: DataSourceConfig) => { openEditDataSourceTab = (dataSource: DataSourceConfig) => {
const { tabItems } = this.state; const { tabItems } = this.state;
const { dataSourceTypes } = this.props; const { dataSourceTypes = [] } = this.props;
if (!tabItems.find((item) => item.tabItemProps.key === TAB_ITEM_EDIT)) { if (!tabItems.find((item) => item.key === TAB_ITEM_EDIT)) {
this.setState(({ tabItems }) => ({ this.setState(({ tabItems }) => ({
tabItems: tabItems.concat({ tabItems: tabItems.concat({
tabItemProps: {
key: TAB_ITEM_EDIT, key: TAB_ITEM_EDIT,
title: '修改数据源', title: '修改数据源',
closeable: true, closeable: true,
data: { data: {
dataSource, dataSource,
dataSourceType: dataSourceTypes?.find( dataSourceType: dataSourceTypes?.find((type) => type.type === dataSource.type),
type => type.type === dataSource.type
),
},
}, },
}), }),
})); }));
@ -256,14 +242,13 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
this.setState({ activeTabKey: TAB_ITEM_EDIT }); this.setState({ activeTabKey: TAB_ITEM_EDIT });
}; };
openImportDataSourceTab = (selectedImportPluginName) => { openImportDataSourceTab = (selectedImportPluginName: string) => {
const { tabItems } = this.state; const { tabItems } = this.state;
const { importPlugins } = this.props; const { importPlugins } = this.props;
if (!tabItems.find((item) => item.tabItemProps.key === `${TAB_ITEM_IMPORT}_${selectedImportPluginName}`)) { if (!tabItems.find((item) => item.key === `${TAB_ITEM_IMPORT}_${selectedImportPluginName}`)) {
this.setState(({ tabItems }) => ({ this.setState(({ tabItems }) => ({
tabItems: tabItems.concat({ tabItems: tabItems.concat({
tabItemProps: {
key: TAB_ITEM_IMPORT, key: TAB_ITEM_IMPORT,
title: `导入数据源-${selectedImportPluginName}`, title: `导入数据源-${selectedImportPluginName}`,
closeable: true, closeable: true,
@ -271,7 +256,6 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
importPlugins?.find((plugin) => selectedImportPluginName === plugin.name), importPlugins?.find((plugin) => selectedImportPluginName === plugin.name),
'component', 'component',
), ),
},
}), }),
})); }));
this.setState({ activeTabKey: TAB_ITEM_IMPORT }); this.setState({ activeTabKey: TAB_ITEM_IMPORT });
@ -283,11 +267,11 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
closeTab = (tabKey: any) => { closeTab = (tabKey: any) => {
this.setState( this.setState(
({ tabItems }) => ({ ({ tabItems }) => ({
tabItems: tabItems.filter((item) => item.tabItemProps.key !== tabKey), tabItems: tabItems.filter((item) => item.key !== tabKey),
}), }),
() => { () => {
this.setState(({ tabItems }) => ({ this.setState(({ tabItems }) => ({
activeTabKey: _get(tabItems, '[0].tabItemProps.key') activeTabKey: _get(tabItems, '[0].key'),
})); }));
}, },
); );
@ -298,14 +282,14 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
// @todo onSelect 不行? // @todo onSelect 不行?
return [ return [
_isArray(dataSourceTypes) && dataSourceTypes.length > 1 ? ( _isArray(dataSourceTypes) && dataSourceTypes.length > 0 ? (
<MenuButton label="新建" onItemClick={this.openCreateDataSourceTab}> <MenuButton label="新建" onItemClick={this.handleCreateDataSourceMenuBtnClick}>
{dataSourceTypes.map((type) => ( {dataSourceTypes.map((type) => (
<MenuButtonItem key={type.type}>{type.type}</MenuButtonItem> <MenuButtonItem key={type.type}>{type.type}</MenuButtonItem>
))} ))}
</MenuButton> </MenuButton>
) : _isArray(dataSourceTypes) && dataSourceTypes.length === 1 ? ( ) : _isArray(dataSourceTypes) && dataSourceTypes.length === 1 ? (
<Button onClick={this.openCreateDataSourceTab.bind(this, dataSourceTypes[0].type)}></Button> <Button onClick={this.handleCreateDataSourceBtnClick.bind(this, dataSourceTypes[0].type)}></Button>
) : null, ) : null,
_isArray(importPlugins) && importPlugins.length > 1 ? ( _isArray(importPlugins) && importPlugins.length > 1 ? (
<MenuButton label="导入" onItemClick={this.openImportDataSourceTab}> <MenuButton label="导入" onItemClick={this.openImportDataSourceTab}>
@ -322,7 +306,7 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
// 更通用的处理 // 更通用的处理
renderTabItemContentByKey = (tabItemKey: any, data: any) => { renderTabItemContentByKey = (tabItemKey: any, data: any) => {
const { dataSourceList, tabItems } = this.state; const { dataSourceList, tabItems } = this.state;
const { dataSourceTypes } = this.props; const { dataSourceTypes = [] } = this.props;
if (tabItemKey === TAB_ITEM_LIST) { if (tabItemKey === TAB_ITEM_LIST) {
return ( return (
@ -336,6 +320,7 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
); );
} else if (tabItemKey === TAB_ITEM_EDIT) { } else if (tabItemKey === TAB_ITEM_EDIT) {
const dataSourceType = dataSourceTypes.find((type) => type.type === data?.dataSource.type); const dataSourceType = dataSourceTypes.find((type) => type.type === data?.dataSource.type);
if (dataSourceType) {
return ( return (
<DataSourceForm <DataSourceForm
dataSourceType={dataSourceType} dataSourceType={dataSourceType}
@ -344,8 +329,8 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
onCancel={this.closeTab.bind(this, TAB_ITEM_EDIT)} onCancel={this.closeTab.bind(this, TAB_ITEM_EDIT)}
/> />
); );
}
} else if (tabItemKey === TAB_ITEM_CREATE) { } else if (tabItemKey === TAB_ITEM_CREATE) {
const tabItemData = tabItems.find((tabItem) => tabItem.tabItemProps.key === tabItemKey);
return ( return (
<DataSourceForm <DataSourceForm
dataSourceType={data?.dataSourceType} dataSourceType={data?.dataSourceType}
@ -354,18 +339,23 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
/> />
); );
} else if (tabItemKey === TAB_ITEM_IMPORT) { } else if (tabItemKey === TAB_ITEM_IMPORT) {
const tabItemData = tabItems.find((tabItem) => tabItem.tabItemProps.key === tabItemKey); const tabItemData = tabItems.find((tabItem) => tabItem.key === tabItemKey);
if (tabItemData) { if (tabItemData) {
const Content = tabItemData.tabItemProps.content; const Content = tabItemData.content;
return <Content dataSourceTypes={dataSourceTypes} onCancel={this.closeTab.bind(this, tabItemKey)} onImport={this.handleImportDataSourceList} />; return (
<Content
dataSourceTypes={dataSourceTypes}
onCancel={this.closeTab.bind(this, tabItemKey)}
onImport={this.handleImportDataSourceList}
/>
);
} }
} }
return null; return null;
}; };
render() { render() {
const { dataSourceList, activeTabKey, tabItems } = this.state; const { activeTabKey, tabItems } = this.state;
const { importPlugins, dataSourceTypes } = this.props;
return ( return (
<div className="lowcode-plugin-datasource-pane"> <div className="lowcode-plugin-datasource-pane">
@ -375,10 +365,8 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
onChange={this.handleTabChange} onChange={this.handleTabChange}
onClose={this.closeTab} onClose={this.closeTab}
> >
{tabItems.map((item) => ( {tabItems.map((item: TabItem) => (
<TabItem {...item.tabItemProps}> <TabItem {...item}>{this.renderTabItemContentByKey(item.key, item.data)}</TabItem>
{this.renderTabItemContentByKey(item.tabItemProps.key, item.tabItemProps.data)}
</TabItem>
))} ))}
</Tab> </Tab>
</div> </div>

View File

@ -1,6 +1,6 @@
import { JSONSchema4 } from '@types/json-schema'; import { JSONSchema6 } from 'json-schema';
export type DataSourceType = { export type DataSourceType = {
type: string; type: string;
schema: JSONSchema4 schema: JSONSchema6;
}; };

View File

@ -9,11 +9,11 @@ export interface DataSourcePaneImportPlugin {
componentProps?: DataSourcePaneImportPluginCustomProps; componentProps?: DataSourcePaneImportPluginCustomProps;
} }
export interface DataSourcePaneImportPluginCustomProps { export interface DataSourcePaneImportPluginCustomProps extends DataSourcePaneImportPluginComponentProps {
[customPropName: string]: any; [customPropName: string]: any;
} }
export interface DataSourcePaneImportPluginComponentProps extends DataSourcePaneImportPluginCustomProps { export interface DataSourcePaneImportPluginComponentProps {
dataSourceTypes: DataSourceType[]; dataSourceTypes: DataSourceType[];
onImport?: (dataSourceList: DataSourceConfig[]) => void; onImport?: (dataSourceList: DataSourceConfig[]) => void;
onCancel?: () => void; onCancel?: () => void;