mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-13 17:48:13 +00:00
refactor: 迁移datasource-pane to alilowcode-plugins group
This commit is contained in:
parent
06e34b1a43
commit
7caa5702aa
@ -1,76 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
<a name="1.0.21"></a>
|
||||
## [1.0.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.20...@ali/lowcode-plugin-datasource-pane@1.0.21) (2020-11-16)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.20"></a>
|
||||
## [1.0.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.19...@ali/lowcode-plugin-datasource-pane@1.0.20) (2020-11-10)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.19"></a>
|
||||
## [1.0.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.18...@ali/lowcode-plugin-datasource-pane@1.0.19) (2020-11-10)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.18"></a>
|
||||
## [1.0.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.17...@ali/lowcode-plugin-datasource-pane@1.0.18) (2020-11-05)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.17"></a>
|
||||
## [1.0.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.16...@ali/lowcode-plugin-datasource-pane@1.0.17) (2020-11-05)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.16"></a>
|
||||
## [1.0.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.15...@ali/lowcode-plugin-datasource-pane@1.0.16) (2020-11-05)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.15"></a>
|
||||
## [1.0.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-datasource-pane@1.0.14...@ali/lowcode-plugin-datasource-pane@1.0.15) (2020-11-04)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.14"></a>
|
||||
## 1.0.14 (2020-11-04)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
|
||||
<a name="1.0.13"></a>
|
||||
## 1.0.13 (2020-11-04)
|
||||
|
||||
|
||||
|
||||
|
||||
**Note:** Version bump only for package @ali/lowcode-plugin-datasource-pane
|
||||
@ -1,9 +0,0 @@
|
||||
## 插件开发
|
||||
|
||||
[https://yuque.antfin-inc.com/ali-lowcode/docs/ip4awq](插件开发文档)。
|
||||
|
||||
[lowcode 组件库文档](https://fusion.alibaba-inc.com/22117/design/style/icon?themeid=4579)。
|
||||
|
||||
[搭建协议](https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema)。
|
||||
|
||||
本地开发需要在 v14.4.0 的 node 环境下能完成 setup
|
||||
@ -1,83 +0,0 @@
|
||||
## 低代码引擎 - 数据源面板插件
|
||||
|
||||
对页面的数据源进行管理(新建,编辑,导入)。
|
||||
|
||||
一个 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,jsonp 类型,支持传入自定义类型。
|
||||
|
||||
```
|
||||
type DataSourceType = {
|
||||
type: string;
|
||||
optionsSchema: JSONSchema6
|
||||
};
|
||||
```
|
||||
|
||||
数据源类型需要在集团规范约束下扩展。目前只允许在 options 下添加扩展字段。
|
||||
|
||||
比如 mtop 类型,需要添加 options.v (版本)字段。
|
||||
|
||||
## 导入插件
|
||||
|
||||
默认支持源码导入,可以传入自定义插件。
|
||||
|
||||
```
|
||||
interface DataSourcePaneImportPlugin {
|
||||
name: string;
|
||||
title: string;
|
||||
component: React.ReactNode;
|
||||
componentProps?: DataSourcePaneImportPluginCustomProps;
|
||||
}
|
||||
|
||||
interface DataSourcePaneImportPluginComponentProps {
|
||||
onImport?: (dataSourceList: DataSourceConfig[]) => void;
|
||||
onCancel?: () => void;
|
||||
dataSourceTypes?: DataSourceType[];
|
||||
}
|
||||
|
||||
interface DataSourcePaneImportPluginCustomProps extends DataSourcePaneImportPluginComponentProps {
|
||||
[customPropName: string]: any;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
TODO
|
||||
---
|
||||
|
||||
* 多语言
|
||||
* 定制样式
|
||||
* 不使用 bind
|
||||
* 表达式 setter 的联想
|
||||
* [later]表达式和其他类型的切换
|
||||
|
||||
## 提案
|
||||
|
||||
* mock
|
||||
* 变量,上下文的提案
|
||||
|
||||
## 问题
|
||||
|
||||
* 变量,上下文放数据源里管理是否合适
|
||||
* mockUrl 和 mockData
|
||||
* 设计器的设计语言无法统一
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"build-plugin-component",
|
||||
"build-plugin-fusion"
|
||||
]
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
{
|
||||
"name": "@ali/lowcode-plugin-datasource-pane",
|
||||
"version": "1.0.21",
|
||||
"description": "低代码引擎数据源面板",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "build-scripts build",
|
||||
"cloud-build": "build-scripts build --skip-demo",
|
||||
"test": "ava",
|
||||
"test:snapshot": "ava --update-snapshots"
|
||||
},
|
||||
"ava": {
|
||||
"compileEnhancements": false,
|
||||
"snapshotDir": "test/fixtures/__snapshots__",
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"@types/react": "^16.9.49",
|
||||
"@types/traverse": "^0.6.32",
|
||||
"monaco-editor": "^0.20.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/lowcode-editor-setters": "^1.0.22",
|
||||
"@alib/build-scripts": "^0.1.3",
|
||||
"@alifd/next": "^1.20.28",
|
||||
"@formily/next": "^1.3.2",
|
||||
"@formily/next-components": "^1.3.2",
|
||||
"@formily/react-schema-renderer": "^1.3.2",
|
||||
"@types/traverse": "^0.6.32",
|
||||
"ajv": "^6.12.4",
|
||||
"lodash": "^4.17.20",
|
||||
"react-monaco-editor": "^0.40.0",
|
||||
"styled-components": "^5.2.0",
|
||||
"traverse": "^0.6.6"
|
||||
},
|
||||
"homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-plugin-datasource-pane@1.0.19/build/index.html",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npm.alibaba-inc.com"
|
||||
}
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Select } from '@alife/next';
|
||||
|
||||
interface Color {
|
||||
rgb: any;
|
||||
onChange: () => void;
|
||||
}
|
||||
|
||||
export interface PluginProps {
|
||||
value: string;
|
||||
onChange: any;
|
||||
}
|
||||
|
||||
export default class ClassNameView extends PureComponent<PluginProps> {
|
||||
static display = 'ClassName';
|
||||
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onChange: () => {},
|
||||
value: '',
|
||||
};
|
||||
|
||||
getClassNameList = () => {
|
||||
const { editor } = this.props.field;
|
||||
const schema = editor.get('designer').project.getSchema();
|
||||
const css = schema.componentsTree[0].css;
|
||||
const classNameList = [];
|
||||
const re = /\.?\w+[^{]+\{[^}]*\}/g;
|
||||
const list = css.match(re);
|
||||
list.map((item) => {
|
||||
if (item[0] === '.') {
|
||||
const className = item.substring(1, item.indexOf('{'));
|
||||
classNameList.push(className);
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return classNameList;
|
||||
};
|
||||
|
||||
|
||||
handleChange = (value) => {
|
||||
const { onChange } = this.props;
|
||||
onChange(value.join(' '));
|
||||
this.setState({
|
||||
selectValue: value,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react/no-deprecated
|
||||
componentWillMount() {
|
||||
const { value } = this.props;
|
||||
const classnameList = this.getClassNameList();
|
||||
const dataSource = [];
|
||||
classnameList.map((item) => {
|
||||
dataSource.push({
|
||||
value: item,
|
||||
label: item,
|
||||
});
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
let selectValue = [];
|
||||
if (value && value !== '') {
|
||||
selectValue = value.split(' ');
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
dataSource,
|
||||
selectValue,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { dataSource, selectValue } = this.state;
|
||||
return (
|
||||
<Select aria-label="tag mode" mode="tag" dataSource={dataSource} onChange={this.handleChange} value={selectValue} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,265 +0,0 @@
|
||||
// @todo schema default
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Button } from '@alifd/next';
|
||||
import { SchemaForm, FormButtonGroup, Submit } from '@formily/next';
|
||||
import { ArrayTable, Input, Switch, NumberPicker } from '@formily/next-components';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
import _isArray from 'lodash/isArray';
|
||||
import _cloneDeep from 'lodash/cloneDeep';
|
||||
import _mergeWith from 'lodash/mergeWith';
|
||||
import _get from 'lodash/get';
|
||||
import traverse from 'traverse';
|
||||
import { DataSourceConfig } from '@ali/lowcode-types';
|
||||
import { ParamValue, JSFunction } from './form-components';
|
||||
import { DataSourceType } from './types';
|
||||
|
||||
// @todo $ref
|
||||
|
||||
const SCHEMA = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
title: '类型',
|
||||
type: 'string',
|
||||
editable: false,
|
||||
},
|
||||
id: {
|
||||
type: 'string',
|
||||
title: '数据源 ID',
|
||||
required: true,
|
||||
},
|
||||
isInit: {
|
||||
title: '是否自动请求',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
dataHandler: {
|
||||
type: 'string',
|
||||
title: '单个数据结果处理函数',
|
||||
required: true,
|
||||
'x-component': 'JSFunction',
|
||||
default: 'function() {}',
|
||||
},
|
||||
options: {
|
||||
type: 'object',
|
||||
title: '请求参数',
|
||||
required: true,
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
title: '请求地址',
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
title: '请求参数',
|
||||
type: 'object',
|
||||
default: {},
|
||||
},
|
||||
method: {
|
||||
type: 'string',
|
||||
title: '请求方法',
|
||||
required: true,
|
||||
enum: ['GET', 'POST', 'OPTIONS', 'PUT', 'DELETE'],
|
||||
default: 'GET',
|
||||
},
|
||||
isCors: {
|
||||
type: 'boolean',
|
||||
title: '是否支持跨域',
|
||||
required: true,
|
||||
default: true,
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
title: '超时时长(毫秒)',
|
||||
default: 5000,
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
title: '请求头信息',
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export interface DataSourceFormProps {
|
||||
dataSourceType: DataSourceType;
|
||||
dataSource?: DataSourceConfig;
|
||||
onComplete?: (dataSource: DataSourceConfig) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
export type DataSourceFormState = {};
|
||||
|
||||
/**
|
||||
* 通过是否存在 ID 来决定读写状态
|
||||
*/
|
||||
export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourceFormState> {
|
||||
state = {};
|
||||
|
||||
handleFormSubmit = (formData: any) => {
|
||||
// @todo mutable?
|
||||
if (_isArray(_get(formData, 'options.params'))) {
|
||||
formData.options.params = formData.options.params.reduce((acc: any, cur: any) => {
|
||||
if (!cur.name) return acc;
|
||||
acc[cur.name] = cur.value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
if (_isArray(_get(formData, 'options.headers'))) {
|
||||
formData.options.headers = formData.options.headers.reduce((acc: any, cur: any) => {
|
||||
if (!cur.name) return acc;
|
||||
acc[cur.name] = cur.value;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
// console.log('submit', formData);
|
||||
this.props.onComplete?.(formData);
|
||||
};
|
||||
|
||||
handleCancel = () => {
|
||||
this.props.onCancel?.();
|
||||
};
|
||||
|
||||
deriveInitialData = (dataSource: object = {}) => {
|
||||
const { dataSourceType } = this.props;
|
||||
const result: any = _cloneDeep(dataSource);
|
||||
|
||||
if (_isPlainObject(_get(result, 'options.params'))) {
|
||||
result.options.params = Object.keys(result.options.params).reduce((acc: any, cur: any) => {
|
||||
acc.push({
|
||||
name: cur,
|
||||
value: result.options.params[cur],
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
if (_isPlainObject(_get(result, 'options.headers'))) {
|
||||
result.options.headers = Object.keys(result.options.headers).reduce((acc: any, cur: any) => {
|
||||
acc.push({
|
||||
name: cur,
|
||||
value: result.options.headers[cur],
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
result.type = dataSourceType.type;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
deriveSchema = () => {
|
||||
const { dataSourceType } = this.props;
|
||||
|
||||
// @todo 减小覆盖的风险
|
||||
const formSchema: any = _mergeWith({}, SCHEMA, dataSourceType.schema, (objValue, srcValue) => {
|
||||
if (_isArray(objValue)) {
|
||||
return srcValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (_get(formSchema, 'properties.options.properties.params')) {
|
||||
formSchema.properties.options.properties.params = {
|
||||
...formSchema.properties.options.properties.params,
|
||||
type: 'array',
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
operationsWidth: 100,
|
||||
},
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
title: '参数名',
|
||||
type: 'string',
|
||||
},
|
||||
value: {
|
||||
title: '参数值',
|
||||
type: 'string',
|
||||
'x-component': 'ParamValue',
|
||||
'x-component-props': {
|
||||
types: ['string', 'boolean', 'expression', 'number'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete formSchema.properties.options.properties.params.properties;
|
||||
}
|
||||
if (_get(formSchema, 'properties.options.properties.headers')) {
|
||||
formSchema.properties.options.properties.headers = {
|
||||
...formSchema.properties.options.properties.headers,
|
||||
type: 'array',
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
operationsWidth: 100,
|
||||
},
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
title: '参数名',
|
||||
type: 'string',
|
||||
},
|
||||
value: {
|
||||
title: '参数值',
|
||||
type: 'string',
|
||||
'x-component': 'ParamValue',
|
||||
'x-component-props': {
|
||||
types: ['string'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete formSchema.properties.options.properties.headers.properties;
|
||||
}
|
||||
|
||||
return traverse(formSchema).forEach(function(node) {
|
||||
if (node?.type && !node['x-component']) {
|
||||
if (node.type === 'string') {
|
||||
node['x-component'] = 'Input';
|
||||
} else if (node.type === 'number') {
|
||||
node['x-component'] = 'NumberPicker';
|
||||
} else if (node.type === 'boolean') {
|
||||
node['x-component'] = 'Switch';
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dataSource } = this.props;
|
||||
|
||||
return (
|
||||
<div className="lowcode-plugin-datasource-form">
|
||||
<SchemaForm
|
||||
onSubmit={this.handleFormSubmit}
|
||||
components={{
|
||||
ArrayTable,
|
||||
ParamValue,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Switch,
|
||||
JSFunction,
|
||||
}}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 19,
|
||||
}}
|
||||
schema={this.deriveSchema()}
|
||||
initialValues={this.deriveInitialData(dataSource)}
|
||||
>
|
||||
<FormButtonGroup offset={4}>
|
||||
<Submit>提交</Submit>
|
||||
<Button onClick={this.handleCancel}>取消</Button>
|
||||
</FormButtonGroup>
|
||||
</SchemaForm>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
/**
|
||||
* 表达式控件,在原类型基础上切换成表达式模式
|
||||
*/
|
||||
/* import React, { PureComponent, ReactElement, FC } from 'react';
|
||||
import { Button, Input, Radio, NumberPicker, Switch, Icon } from '@alifd/next';
|
||||
import { ArrayTable } from '@formily/next-components';
|
||||
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 { ExpressionSetter } from '@ali/lowcode-editor-setters';
|
||||
|
||||
const { Group: RadioGroup } = Radio;
|
||||
|
||||
export interface ExpressionProps {
|
||||
className: string;
|
||||
value: any;
|
||||
onChange?: () => void;
|
||||
type: 'string' | 'number' | 'boolan' | 'array';
|
||||
}
|
||||
|
||||
export interface ExpressionState {
|
||||
useExpression: false;
|
||||
}
|
||||
|
||||
class ExpressionComp extends PureComponent<ExpressionProps, ExpressionState> {
|
||||
static isFieldComponent = true;
|
||||
|
||||
state = {
|
||||
useExpression: '',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state.useExpression = this.isUseExpression(this.props.value);
|
||||
this.handleChange = this.handleChange;
|
||||
}
|
||||
|
||||
isUseExpression = (value: any) => {
|
||||
if (_isPlainObject(value) && value.type === 'JSFunction') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// @todo 需要再 bind 一次?
|
||||
handleChange = (value) => {
|
||||
this.props?.onChange(value);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.value !== prevProps.value) {
|
||||
this.setState({
|
||||
value: this.props.value,
|
||||
useExpression: this.isUseExpression(this.props.value),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleUseExpressionChange = (useExpression) => {
|
||||
this.setState(({ value }) => {
|
||||
let nextValue = value || '';
|
||||
if (useExpression) {
|
||||
nextValue = {
|
||||
type: 'JSFunction',
|
||||
value: ''
|
||||
};
|
||||
} else {
|
||||
nextVaule = null;
|
||||
}
|
||||
return {
|
||||
value: nextValue,
|
||||
useExpression,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
renderOriginal = () => {
|
||||
const { value, type } = this.props;
|
||||
|
||||
if (type === 'string') {
|
||||
return <Input onChange={this.handleChange} value={value} />;
|
||||
} else if (type === 'boolean') {
|
||||
return <Switch onChange={this.handleChange} checked={value} />;
|
||||
} else if (type === 'number') {
|
||||
return <NumberPicker onChange={this.handleChange} value={value} />;
|
||||
} else if (type === 'array') {
|
||||
return <ArrayTable onChange={this.handleChange} value={value} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
renderExpression = () => {
|
||||
const { value, type } = this.props;
|
||||
|
||||
// @todo 传入上下文才有智能提示
|
||||
return (
|
||||
<ExpressionSetter value={value} onChange={this.handleChange} />
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { useExpression } = this.state;
|
||||
return (
|
||||
<div>
|
||||
{!useExpression && this.renderOriginal()}
|
||||
{useExpression && this.renderExpression()}
|
||||
<Button onClick={this.handleUseExpressionChange}><Icon type="edit" /></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Expression = connect({
|
||||
getProps: (componentProps, fieldProps) => {
|
||||
debugger;
|
||||
}
|
||||
})(ExpressionComp); */
|
||||
@ -1,2 +0,0 @@
|
||||
export * from './param-value';
|
||||
export * from './jsfunction';
|
||||
@ -1,52 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from '@formily/react-schema-renderer';
|
||||
import MonacoEditor, { EditorWillMount } from 'react-monaco-editor';
|
||||
import _noop from 'lodash/noop';
|
||||
import { editor } from 'monaco-editor';
|
||||
|
||||
export interface JSFunctionProps {
|
||||
className: string;
|
||||
value: any;
|
||||
onChange?: (val: any) => void;
|
||||
}
|
||||
|
||||
export type JSFunctionState = {};
|
||||
|
||||
class JSFunctionComp extends PureComponent<JSFunctionProps, JSFunctionState> {
|
||||
static isFieldComponent = true;
|
||||
|
||||
static defaultProps = {
|
||||
onChange: _noop,
|
||||
};
|
||||
|
||||
private monacoRef: any = null;
|
||||
|
||||
handleEditorChange = () => {
|
||||
if (this.monacoRef) {
|
||||
if (!(this.monacoRef as any).getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
|
||||
this.props.onChange?.((this.monacoRef as any)?.getModels()?.[0]?.getValue());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleEditorWillMount: EditorWillMount = (editor) => {
|
||||
this.monacoRef = editor?.editor;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { value } = this.props;
|
||||
return (
|
||||
<MonacoEditor
|
||||
theme="vs-dark"
|
||||
width={400}
|
||||
height={150}
|
||||
defaultValue={value}
|
||||
language="js"
|
||||
onChange={this.handleEditorChange}
|
||||
editorWillMount={this.handleEditorWillMount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const JSFunction = connect()(JSFunctionComp);
|
||||
@ -1,7 +0,0 @@
|
||||
.param-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.param-value-type {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
@ -1,150 +0,0 @@
|
||||
import React, { PureComponent, ReactElement, FC } from 'react';
|
||||
import { Select, Input, Radio, NumberPicker, Switch } from '@alifd/next';
|
||||
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 { ExpressionSetter } from '@ali/lowcode-editor-setters';
|
||||
|
||||
import './param-value.scss';
|
||||
|
||||
const { Group: RadioGroup } = Radio;
|
||||
|
||||
type ParamValueType = 'string' | 'number' | 'boolean' | 'expression';
|
||||
|
||||
export interface ParamValueProps {
|
||||
className: string;
|
||||
value: any;
|
||||
onChange?: (value: any) => void;
|
||||
types: ParamValueType[];
|
||||
}
|
||||
|
||||
export interface ParamValueState {
|
||||
type: ParamValueType;
|
||||
}
|
||||
|
||||
const TYPE_LABEL_MAP = {
|
||||
string: '字符串',
|
||||
number: '数字',
|
||||
boolean: '布尔',
|
||||
expression: '表达式',
|
||||
};
|
||||
|
||||
class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
|
||||
static isFieldComponent = true;
|
||||
|
||||
static defaultProps = {
|
||||
types: ['string', 'boolean', 'number', 'expression'],
|
||||
};
|
||||
|
||||
state: ParamValueState = {
|
||||
type: 'string',
|
||||
};
|
||||
|
||||
constructor(props: ParamValueProps) {
|
||||
super(props);
|
||||
this.state.type = this.getTypeFromValue(this.props.value);
|
||||
}
|
||||
|
||||
// @todo
|
||||
getTypeFromValue = (value: any) => {
|
||||
if (_isBoolean(value)) {
|
||||
return 'boolean';
|
||||
} else if (_isNumber(value)) {
|
||||
return 'number';
|
||||
} else if (_isPlainObject(value) && value.type === 'JSFunction') {
|
||||
return 'expression';
|
||||
}
|
||||
return 'string';
|
||||
};
|
||||
|
||||
// @todo 需要再 bind 一次?
|
||||
handleChange = (value: any) => {
|
||||
this.props?.onChange?.(value);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: ParamValueProps) {
|
||||
if (this.props.value !== prevProps.value) {
|
||||
this.setState({
|
||||
type: this.getTypeFromValue(this.props.value),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleTypeChange = (type: string) => {
|
||||
this.setState(
|
||||
{
|
||||
type: type as ParamValueType,
|
||||
},
|
||||
() => {
|
||||
let nextValue = this.props.value || '';
|
||||
const { type } = this.state;
|
||||
if (type === 'string') {
|
||||
nextValue = nextValue.toString();
|
||||
} else if (type === 'number') {
|
||||
nextValue = nextValue * 1;
|
||||
} else if (type === 'boolean') {
|
||||
nextValue = nextValue === 'true' || nextValue;
|
||||
} else if (type === 'expression') {
|
||||
nextValue = '';
|
||||
}
|
||||
this.props.onChange?.(nextValue);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
renderTypeSelect = () => {
|
||||
const { type } = this.state;
|
||||
const { types } = this.props;
|
||||
|
||||
if (_isArray(types) && types.length > 2) {
|
||||
return (
|
||||
<Select
|
||||
className="param-value-type"
|
||||
dataSource={types.map((item) => ({
|
||||
label: TYPE_LABEL_MAP[item],
|
||||
value: item,
|
||||
}))}
|
||||
value={type}
|
||||
onChange={this.handleTypeChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (_isArray(types) && types.length > 1) {
|
||||
return (
|
||||
<RadioGroup
|
||||
className="param-value-type"
|
||||
shape="button"
|
||||
size="small"
|
||||
onChange={this.handleTypeChange}
|
||||
value={type}
|
||||
>
|
||||
{types.map((item) => (
|
||||
<Radio value={item}>TYPE_LABEL_MAP[item]</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { type } = this.state;
|
||||
const { value } = this.props;
|
||||
return (
|
||||
<div className="param-value">
|
||||
{this.renderTypeSelect()}
|
||||
{type === 'string' && <Input onChange={this.handleChange} value={value} />}
|
||||
{type === 'boolean' && <Switch onChange={this.handleChange} checked={value} />}
|
||||
{type === 'number' && <NumberPicker onChange={this.handleChange} value={value} />}
|
||||
{type === 'expression' && <ExpressionSetter onChange={this.handleChange} value={value} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ParamValue = connect()(ParamValueComp);
|
||||
@ -1,15 +0,0 @@
|
||||
.lowcode-plugin-datasource-import-plugin-code {
|
||||
height: unquote("calc(100vh - 48px - 48px - 42px)");
|
||||
overflow: auto;
|
||||
.error-msg {
|
||||
line-height: 24px;
|
||||
color: #f60;
|
||||
font-size: 12px;
|
||||
}
|
||||
.btns {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.next-btn {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
@ -1,138 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/indent */
|
||||
// @todo 缩进问题
|
||||
/**
|
||||
* 源码导入插件
|
||||
* @todo editor 关联 types,并提供详细的出错信息
|
||||
*/
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Button } from '@alifd/next';
|
||||
import _noop from 'lodash/noop';
|
||||
import _isArray from 'lodash/isArray';
|
||||
import _last from 'lodash/last';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
import MonacoEditor, { EditorWillMount } from 'react-monaco-editor';
|
||||
import { editor } from 'monaco-editor';
|
||||
import { DataSourceConfig } from '@ali/lowcode-types';
|
||||
import Ajv from 'ajv';
|
||||
import { DataSourcePaneImportPluginComponentProps } from '../types';
|
||||
|
||||
import './code.scss';
|
||||
|
||||
export interface DataSourceImportPluginCodeProps extends DataSourcePaneImportPluginComponentProps {
|
||||
defaultValue?: DataSourceConfig[];
|
||||
}
|
||||
|
||||
export interface DataSourceImportPluginCodeState {
|
||||
code: string;
|
||||
isCodeValid: boolean;
|
||||
}
|
||||
|
||||
export class DataSourceImportPluginCode extends PureComponent<
|
||||
DataSourceImportPluginCodeProps,
|
||||
DataSourceImportPluginCodeState
|
||||
> {
|
||||
static defaultProps = {
|
||||
defaultValue: [
|
||||
{
|
||||
type: 'http',
|
||||
id: 'test',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
state = {
|
||||
code: '',
|
||||
isCodeValid: true,
|
||||
};
|
||||
|
||||
private monacoRef: any;
|
||||
|
||||
constructor(props: DataSourceImportPluginCodeProps) {
|
||||
super(props);
|
||||
this.state.code = JSON.stringify(this.deriveValue(this.props.defaultValue));
|
||||
this.handleEditorWillMount = this.handleEditorWillMount.bind(this);
|
||||
this.handleEditorChange = this.handleEditorChange.bind(this);
|
||||
this.handleComplete = this.handleComplete.bind(this);
|
||||
}
|
||||
|
||||
deriveValue = (value: any) => {
|
||||
const { dataSourceTypes } = this.props;
|
||||
|
||||
if (!_isArray(dataSourceTypes) || dataSourceTypes.length === 0) return [];
|
||||
|
||||
let result = value;
|
||||
if (_isPlainObject(result)) {
|
||||
// 如果是对象则转化成数组
|
||||
result = [result];
|
||||
} else if (!_isArray(result)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
return (result as DataSourceConfig[]).filter((dataSource) => {
|
||||
if (!dataSource.type) return false;
|
||||
const dataSourceType = dataSourceTypes.find((type) => type.type === dataSource.type);
|
||||
if (!dataSourceType) return false;
|
||||
return ajv.validate(dataSourceType.schema, dataSource);
|
||||
});
|
||||
};
|
||||
|
||||
handleComplete = () => {
|
||||
if (this.monacoRef) {
|
||||
if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
|
||||
this.setState({ isCodeValid: true });
|
||||
const model: any = _last(this.monacoRef.getModels());
|
||||
if (!model) return;
|
||||
this.props.onImport?.(this.deriveValue(JSON.parse(model.getValue())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.setState({ isCodeValid: false });
|
||||
};
|
||||
|
||||
handleEditorChange = () => {
|
||||
if (this.monacoRef) {
|
||||
if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) {
|
||||
this.setState({ isCodeValid: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleEditorWillMount: EditorWillMount = (editor) => {
|
||||
this.monacoRef = editor?.editor;
|
||||
// @todo 格式化一次
|
||||
};
|
||||
|
||||
handleCodeChagne = (code: string) => {
|
||||
this.setState({ code });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { onCancel = _noop } = this.props;
|
||||
const { code, isCodeValid } = this.state;
|
||||
|
||||
// @todo
|
||||
// formatOnType formatOnPaste
|
||||
return (
|
||||
<div className="lowcode-plugin-datasource-import-plugin-code">
|
||||
<MonacoEditor
|
||||
theme="vs-dark"
|
||||
width={800}
|
||||
height={400}
|
||||
defaultValue={code}
|
||||
language="json"
|
||||
onChange={this.handleEditorChange}
|
||||
editorWillMount={this.handleEditorWillMount}
|
||||
/>
|
||||
{!isCodeValid && <p className="error-msg">格式有误</p>}
|
||||
<p className="btns">
|
||||
<Button onClick={onCancel}>取消</Button>
|
||||
<Button type="primary" onClick={this.handleComplete}>
|
||||
确认
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './code';
|
||||
@ -1,97 +0,0 @@
|
||||
.lowcode-plugin-datasource-pane {
|
||||
margin: 0 8px;
|
||||
>.next-tabs {
|
||||
>.next-tabs-bar {
|
||||
.next-tabs-nav-extra {
|
||||
.next-btn {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lowcode-plugin-datasource-pane-list {
|
||||
margin: 8px;
|
||||
.next-search {
|
||||
width: 100%;
|
||||
.next-search-left {
|
||||
height: 28px !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
.next-before {
|
||||
height: 28px !important;
|
||||
.next-select {
|
||||
height: 28px !important;
|
||||
}
|
||||
}
|
||||
.next-search-input {
|
||||
height: 28px !important;
|
||||
input {
|
||||
height: 28px !important;
|
||||
}
|
||||
}
|
||||
.next-input {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
}
|
||||
.next-after {
|
||||
// height: 28px !important;
|
||||
.next-btn {
|
||||
height: 30px !important;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
.next-icon:before {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.datasource-list {
|
||||
margin-top: 8px;
|
||||
height: unquote("calc(100vh - 48px - 48px - 42px - 28px - 8px - 8px)");
|
||||
overflow: auto;
|
||||
.next-virtual-list-wrapper > div > ul > li {
|
||||
border-top: 1px solid #ddd;
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.datasource-item {
|
||||
margin: 8px;
|
||||
.datasource-item-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
height: 28px;
|
||||
.datasource-item-id {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
.next-btn {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
.datasource-item-desc {
|
||||
.next-tag {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lowcode-plugin-datasource-form {
|
||||
height: unquote("calc(100vh - 48px - 48px - 42px)");
|
||||
overflow: auto;
|
||||
.next-form-item {
|
||||
.next-form-item-control {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,149 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PluginProps, DataSource } from '@ali/lowcode-types';
|
||||
import _get from 'lodash/get';
|
||||
import _set from 'lodash/set';
|
||||
import { DataSourcePane } from './pane';
|
||||
import { DataSourcePaneImportPlugin, DataSourceType } from './types';
|
||||
import { DataSourceImportPluginCode } from './import-plugins';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
export { DataSourceImportPluginCode };
|
||||
|
||||
const PLUGIN_NAME = 'dataSourcePane';
|
||||
|
||||
export interface DataSourcePaneProps extends PluginProps {
|
||||
importPlugins: DataSourcePaneImportPlugin[];
|
||||
dataSourceTypes?: DataSourceType[];
|
||||
}
|
||||
|
||||
export interface DataSourcePaneState {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
const BUILTIN_DATASOURCE_TYPES: DataSourceType[] = [
|
||||
{
|
||||
type: 'fetch',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'mtop',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uri: {
|
||||
title: 'api',
|
||||
},
|
||||
v: {
|
||||
title: 'v',
|
||||
type: 'string',
|
||||
},
|
||||
appKey: {
|
||||
title: 'appKey',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'jsonp',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
options: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
method: {
|
||||
enum: ['GET'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const BUILTIN_IMPORT_PLUGINS: DataSourcePaneImportPlugin[] = [
|
||||
{
|
||||
name: 'default',
|
||||
title: '源码',
|
||||
component: DataSourceImportPluginCode,
|
||||
},
|
||||
];
|
||||
|
||||
export default class DataSourcePanePlugin extends PureComponent<DataSourcePaneProps, DataSourcePaneState> {
|
||||
static displayName = 'DataSourcePanePlugin';
|
||||
|
||||
static defaultProps = {
|
||||
dataSourceTypes: [],
|
||||
importPlugins: [],
|
||||
};
|
||||
|
||||
state = {
|
||||
active: false,
|
||||
};
|
||||
|
||||
constructor(props: DataSourcePaneProps) {
|
||||
super(props);
|
||||
this.state.active = true;
|
||||
|
||||
const { editor } = this.props;
|
||||
// @todo pluginName, to unsubscribe
|
||||
// 第一次 active 事件不会触发监听器
|
||||
editor.on('skeleton.panel-dock.active', (pluginName, dock) => {
|
||||
if (pluginName === PLUGIN_NAME) {
|
||||
this.setState({ active: true });
|
||||
}
|
||||
});
|
||||
editor.on('skeleton.panel-dock.unactive', (pluginName, dock) => {
|
||||
if (pluginName === PLUGIN_NAME) {
|
||||
this.setState({ active: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleSchemaChange = (schema: DataSource) => {
|
||||
const { editor } = this.props;
|
||||
|
||||
// @TODO 姿势是否最优?
|
||||
if (editor.get('designer')) {
|
||||
const docSchema = editor.get('designer').project.getSchema();
|
||||
_set(docSchema, 'componentsTree[0].dataSource', schema);
|
||||
editor.get('designer').project.load(docSchema, true);
|
||||
// console.log('check datasorce save result', editor.get('designer').project.getSchema());
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { importPlugins, dataSourceTypes = [], editor } = this.props;
|
||||
const { active } = this.state;
|
||||
|
||||
if (!active) return null;
|
||||
|
||||
const projectSchema = editor.get('designer').project.getSchema() ?? {};
|
||||
|
||||
return (
|
||||
<DataSourcePane
|
||||
importPlugins={BUILTIN_IMPORT_PLUGINS.concat(importPlugins)}
|
||||
dataSourceTypes={BUILTIN_DATASOURCE_TYPES.concat(dataSourceTypes)}
|
||||
defaultSchema={_get(projectSchema, 'componentsTree[0].dataSource')}
|
||||
onSchemaChange={this.handleSchemaChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export * from './types';
|
||||
@ -1,178 +0,0 @@
|
||||
import React, { PureComponent, ReactElement, FC } from 'react';
|
||||
import { Button, Search, VirtualList, Tag, Balloon, Table } from '@alifd/next';
|
||||
import { DataSourceConfig } from '@ali/lowcode-types';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
import _isNumber from 'lodash/isNumber';
|
||||
import _isBoolean from 'lodash/isBoolean';
|
||||
import _isNil from 'lodash/isNil';
|
||||
import _tap from 'lodash/tap';
|
||||
import { DataSourceType } from './types';
|
||||
|
||||
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 {
|
||||
dataSourceTypes: DataSourceType[];
|
||||
dataSource: DataSourceConfig[];
|
||||
onEditDataSource?: (dataSourceId: string) => void;
|
||||
onDuplicateDataSource?: (dataSourceId: string) => void;
|
||||
onRemoveDataSource?: (dataSourceId: string) => void;
|
||||
}
|
||||
|
||||
export interface DataSourceListState {
|
||||
filteredType: string;
|
||||
keyword: string;
|
||||
}
|
||||
|
||||
type TableRow = {
|
||||
label: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
export default class DataSourceList extends PureComponent<DataSourceListProps, DataSourceListState> {
|
||||
state = {
|
||||
filteredType: '',
|
||||
keyword: '',
|
||||
};
|
||||
|
||||
handleSearchFilterChange = (filteredType: any) => {
|
||||
this.setState({ filteredType });
|
||||
};
|
||||
|
||||
handleSearch = (keyword: any) => {
|
||||
this.setState({ keyword });
|
||||
};
|
||||
|
||||
handleEditDataSource = (id: any) => {
|
||||
this.props.onEditDataSource?.(id);
|
||||
};
|
||||
|
||||
handleDuplicateDataSource = (id: any) => {
|
||||
this.props.onDuplicateDataSource?.(id);
|
||||
};
|
||||
|
||||
handleRemoveDataSource = (id: any) => {
|
||||
this.props.onRemoveDataSource?.(id);
|
||||
};
|
||||
|
||||
deriveListDataSource = () => {
|
||||
const { filteredType, keyword } = this.state;
|
||||
const { dataSource, dataSourceTypes } = this.props;
|
||||
|
||||
return (
|
||||
dataSource
|
||||
?.filter((item) => (filteredType ? item.type === filteredType : true))
|
||||
?.filter((item) => (keyword ? item.id.indexOf(keyword) !== -1 : true))
|
||||
?.map((item) => (
|
||||
<li key={item.id}>
|
||||
<div className="datasource-item">
|
||||
<div className="datasource-item-title">
|
||||
<div className="datasource-item-id" title={item.id}>
|
||||
{item.id}
|
||||
</div>
|
||||
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
|
||||
<Balloon
|
||||
trigger={<Button size="small">详情</Button>}
|
||||
align="b"
|
||||
alignEdge
|
||||
triggerType="hover"
|
||||
style={{ width: 300 }}
|
||||
>
|
||||
<Table
|
||||
dataSource={_tap(
|
||||
Object.keys(item.options || {}).reduce<TableRow[]>((acc, cur) => {
|
||||
// @todo 这里的 ts 处理得不好
|
||||
if (_isPlainObject(item.options[cur])) {
|
||||
Object.keys(item?.options?.[cur] || {}).forEach((curInOption) => {
|
||||
acc.push({
|
||||
label: `${cur}.${curInOption}`,
|
||||
value: (item?.options?.[cur] as any)?.[curInOption],
|
||||
});
|
||||
});
|
||||
} else if (!_isNil(item.options[cur])) {
|
||||
// @todo 排除 null
|
||||
acc.push({
|
||||
label: cur,
|
||||
value: item.options[cur],
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []),
|
||||
console.log,
|
||||
)}
|
||||
>
|
||||
<TableCol title="" dataIndex="label" />
|
||||
<TableCol
|
||||
title=""
|
||||
dataIndex="value"
|
||||
cell={(val: any) => (
|
||||
<div>
|
||||
<Tag>{deriveTypeFromValue(val)}</Tag>
|
||||
{val.toString()}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
</Balloon>
|
||||
)}
|
||||
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
|
||||
<Button size="small" onClick={this.handleEditDataSource.bind(this, item.id)}>
|
||||
编辑
|
||||
</Button>
|
||||
)}
|
||||
{!!dataSourceTypes.find((ds) => ds.type === item.type) && (
|
||||
<Button size="small" onClick={this.handleDuplicateDataSource.bind(this, item.id)}>
|
||||
复制
|
||||
</Button>
|
||||
)}
|
||||
<Button size="small" onClick={this.handleRemoveDataSource.bind(this, item.id)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<div className="datasource-item-desc">
|
||||
<Tag size="small">{item.type}</Tag>
|
||||
<Tag size="small">{item.isInit ? '自动' : '手动'}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)) || []
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dataSourceTypes } = this.props;
|
||||
const { filteredType } = this.state;
|
||||
|
||||
return (
|
||||
<div className="lowcode-plugin-datasource-pane-list">
|
||||
<Search
|
||||
hasClear
|
||||
onSearch={this.handleSearch}
|
||||
filterProps={{}}
|
||||
defaultFilterValue={filteredType}
|
||||
filter={[
|
||||
{
|
||||
label: '全部',
|
||||
value: '',
|
||||
},
|
||||
].concat(
|
||||
dataSourceTypes.map((type) => ({
|
||||
label: type?.type,
|
||||
value: type?.type,
|
||||
})),
|
||||
)}
|
||||
onFilterChange={this.handleSearchFilterChange}
|
||||
/>
|
||||
<div className="datasource-list">
|
||||
<VirtualList>{this.deriveListDataSource()}</VirtualList>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"DataSourcePane": "DataSource Pane"
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { createIntl } from '@ali/lowcode-editor-core';
|
||||
import enUS from './en-US.json';
|
||||
import zhCN from './zh-CN.json';
|
||||
|
||||
const { intl, intlNode, getLocale, setLocale } = createIntl({
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN,
|
||||
});
|
||||
|
||||
export { intl, intlNode, getLocale, setLocale };
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"DataSourcePane": "数据源面板"
|
||||
}
|
||||
@ -1,375 +0,0 @@
|
||||
/**
|
||||
* 面板,先通过 Dialog 呈现
|
||||
*/
|
||||
import React, { PureComponent } from 'react';
|
||||
import { DataSource, DataSourceConfig } from '@ali/lowcode-types';
|
||||
import { Tab, Button, MenuButton, Message, Dialog } from '@alifd/next';
|
||||
import _cloneDeep from 'lodash/cloneDeep';
|
||||
import _uniqueId from 'lodash/uniqueId';
|
||||
import _isArray from 'lodash/isArray';
|
||||
import _get from 'lodash/get';
|
||||
import List from './list';
|
||||
// import { DataSourceImportButton, DataSourceImportPluginCode } from './import';
|
||||
import { DataSourceForm } from './datasource-form';
|
||||
import { DataSourcePaneImportPlugin, DataSourceType } from './types';
|
||||
|
||||
const { Item: TabItem } = Tab;
|
||||
const { Item: MenuButtonItem } = MenuButton;
|
||||
|
||||
const TAB_ITEM_LIST = 'list';
|
||||
const TAB_ITEM_IMPORT = 'import';
|
||||
const TAB_ITEM_CREATE = 'create';
|
||||
const TAB_ITEM_EDIT = 'edit';
|
||||
|
||||
export interface DataSourcePaneProps {
|
||||
dataSourceTypes?: DataSourceType[];
|
||||
importPlugins?: DataSourcePaneImportPlugin[];
|
||||
defaultSchema?: DataSource;
|
||||
onSchemaChange?: (schema: DataSource) => void;
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
title: string;
|
||||
closeable: boolean;
|
||||
data?: any;
|
||||
content?: any;
|
||||
}
|
||||
|
||||
export interface DataSourcePaneState {
|
||||
dataSourceList: DataSourceConfig[];
|
||||
tabItems: TabItem[];
|
||||
activeTabKey: string;
|
||||
}
|
||||
|
||||
export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourcePaneState> {
|
||||
state: DataSourcePaneState = {
|
||||
dataSourceList: [...(this.props.defaultSchema?.list || [])],
|
||||
tabItems: [
|
||||
{
|
||||
key: TAB_ITEM_LIST,
|
||||
title: '数据源列表',
|
||||
closeable: false,
|
||||
},
|
||||
],
|
||||
activeTabKey: TAB_ITEM_LIST,
|
||||
};
|
||||
|
||||
handleDataSourceListChange = (dataSourceList?: DataSourceConfig[]) => {
|
||||
if (dataSourceList) {
|
||||
this.setState({ dataSourceList });
|
||||
}
|
||||
this.props.onSchemaChange?.({
|
||||
list: this.state.dataSourceList,
|
||||
});
|
||||
};
|
||||
|
||||
handleImportDataSourceList = (toImport: DataSourceConfig[]) => {
|
||||
const importDataSourceList = () => {
|
||||
this.closeTab(TAB_ITEM_IMPORT);
|
||||
this.setState(
|
||||
({ dataSourceList }) => ({
|
||||
dataSourceList: dataSourceList.concat(toImport),
|
||||
}),
|
||||
() => {
|
||||
this.handleDataSourceListChange();
|
||||
},
|
||||
);
|
||||
};
|
||||
if (!_isArray(toImport) || toImport.length === 0) {
|
||||
Message.error('没有找到可导入的数据源');
|
||||
return;
|
||||
}
|
||||
const repeatedDataSourceList = toImport.filter(
|
||||
(item) => !!this.state.dataSourceList.find((dataSource) => dataSource.id === item.id),
|
||||
);
|
||||
if (repeatedDataSourceList.length > 0) {
|
||||
Dialog.confirm({
|
||||
content: `数据源(${repeatedDataSourceList
|
||||
.map((item) => item.id)
|
||||
.join(',')})已存在,如果导入会替换原数据源,是否继续?`,
|
||||
onOk: () => {
|
||||
importDataSourceList();
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
importDataSourceList();
|
||||
};
|
||||
|
||||
handleCreateDataSource = (toCreate: DataSourceConfig) => {
|
||||
const create = () => {
|
||||
this.closeTab(TAB_ITEM_CREATE);
|
||||
this.setState(
|
||||
({ dataSourceList }) => ({
|
||||
dataSourceList: dataSourceList.concat([
|
||||
{
|
||||
...toCreate,
|
||||
},
|
||||
]),
|
||||
}),
|
||||
() => {
|
||||
this.handleDataSourceListChange();
|
||||
},
|
||||
);
|
||||
};
|
||||
if (this.state.dataSourceList.find((dataSource) => dataSource.id === toCreate.id)) {
|
||||
Dialog.confirm({
|
||||
content: `数据源(${toCreate.id})已存在,如果导入会替换原数据源,是否继续?`,
|
||||
onOk: () => {
|
||||
create();
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
create();
|
||||
};
|
||||
|
||||
handleUpdateDataSource = (toUpdate: DataSourceConfig) => {
|
||||
this.closeTab(TAB_ITEM_EDIT);
|
||||
this.setState(
|
||||
({ dataSourceList }) => {
|
||||
const nextDataSourceList = [...dataSourceList];
|
||||
const dataSourceUpdate = nextDataSourceList.find((dataSource) => dataSource.id === toUpdate.id);
|
||||
if (dataSourceUpdate) {
|
||||
Object.assign(dataSourceUpdate, toUpdate);
|
||||
}
|
||||
return {
|
||||
dataSourceList: nextDataSourceList,
|
||||
};
|
||||
},
|
||||
() => {
|
||||
this.handleDataSourceListChange();
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
handleRemoveDataSource = (dataSourceId: string) => {
|
||||
const remove = () => {
|
||||
this.setState(
|
||||
({ dataSourceList }) => ({
|
||||
dataSourceList: dataSourceList.filter((item) => item.id !== dataSourceId),
|
||||
}),
|
||||
() => {
|
||||
this.handleDataSourceListChange();
|
||||
},
|
||||
);
|
||||
};
|
||||
Dialog.confirm({
|
||||
content: `确定要删除吗?`,
|
||||
onOk: () => {
|
||||
remove();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
handleDuplicateDataSource = (dataSourceId: string) => {
|
||||
const target = this.state.dataSourceList.find((item) => item.id === dataSourceId);
|
||||
if (!target) return;
|
||||
const cloned = _cloneDeep(target);
|
||||
|
||||
this.openCreateDataSourceTab({
|
||||
...cloned,
|
||||
id: _uniqueId(`${cloned.id}_`),
|
||||
});
|
||||
};
|
||||
|
||||
handleEditDataSource = (dataSourceId: string) => {
|
||||
const target = this.state.dataSourceList.find((item) => item.id === dataSourceId);
|
||||
if (!target) return;
|
||||
const cloned = _cloneDeep(target);
|
||||
|
||||
this.openEditDataSourceTab({
|
||||
...cloned,
|
||||
});
|
||||
};
|
||||
|
||||
// @todo 没有识别出类型
|
||||
handleTabChange = (activeTabKey: any) => {
|
||||
this.setState({ activeTabKey });
|
||||
};
|
||||
|
||||
openCreateDataSourceTab = (dataSource: DataSourceConfig) => {
|
||||
const { tabItems } = this.state;
|
||||
const { dataSourceTypes = [] } = this.props;
|
||||
|
||||
if (!tabItems.find((item) => item.key === TAB_ITEM_CREATE)) {
|
||||
this.setState(({ tabItems }) => ({
|
||||
tabItems: tabItems.concat({
|
||||
key: TAB_ITEM_CREATE,
|
||||
title: `添加数据源`,
|
||||
closeable: true,
|
||||
data: {
|
||||
dataSourceType: dataSourceTypes?.find((type) => type.type === dataSource.type),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
this.setState({ activeTabKey: TAB_ITEM_CREATE });
|
||||
} else {
|
||||
Message.notice('当前已有一个新建数据源的 TAB 被大家');
|
||||
}
|
||||
};
|
||||
|
||||
handleCreateDataSourceBtnClick = (dataSourceType: string) => {
|
||||
this.openCreateDataSourceTab({
|
||||
type: dataSourceType,
|
||||
} as DataSourceConfig);
|
||||
};
|
||||
|
||||
handleCreateDataSourceMenuBtnClick = (dataSourceType: string) => {
|
||||
this.openCreateDataSourceTab({
|
||||
type: dataSourceType,
|
||||
} as DataSourceConfig);
|
||||
};
|
||||
|
||||
openEditDataSourceTab = (dataSource: DataSourceConfig) => {
|
||||
const { tabItems } = this.state;
|
||||
const { dataSourceTypes = [] } = this.props;
|
||||
|
||||
if (!tabItems.find((item) => item.key === TAB_ITEM_EDIT)) {
|
||||
this.setState(({ tabItems }) => ({
|
||||
tabItems: tabItems.concat({
|
||||
key: TAB_ITEM_EDIT,
|
||||
title: '修改数据源',
|
||||
closeable: true,
|
||||
data: {
|
||||
dataSource,
|
||||
dataSourceType: dataSourceTypes?.find((type) => type.type === dataSource.type),
|
||||
},
|
||||
}),
|
||||
}));
|
||||
}
|
||||
this.setState({ activeTabKey: TAB_ITEM_EDIT });
|
||||
};
|
||||
|
||||
openImportDataSourceTab = (selectedImportPluginName: string) => {
|
||||
const { tabItems } = this.state;
|
||||
const { importPlugins } = this.props;
|
||||
|
||||
if (!tabItems.find((item) => item.key === `${TAB_ITEM_IMPORT}_${selectedImportPluginName}`)) {
|
||||
this.setState(({ tabItems }) => ({
|
||||
tabItems: tabItems.concat({
|
||||
key: TAB_ITEM_IMPORT,
|
||||
title: `导入数据源-${selectedImportPluginName}`,
|
||||
closeable: true,
|
||||
content: _get(
|
||||
importPlugins?.find((plugin) => selectedImportPluginName === plugin.name),
|
||||
'component',
|
||||
),
|
||||
}),
|
||||
}));
|
||||
this.setState({ activeTabKey: TAB_ITEM_IMPORT });
|
||||
} else {
|
||||
Message.notice('当前已有一个导入数据源的 TAB');
|
||||
}
|
||||
};
|
||||
|
||||
closeTab = (tabKey: any) => {
|
||||
this.setState(
|
||||
({ tabItems }) => ({
|
||||
tabItems: tabItems.filter((item) => item.key !== tabKey),
|
||||
}),
|
||||
() => {
|
||||
this.setState(({ tabItems }) => ({
|
||||
activeTabKey: _get(tabItems, '[0].key'),
|
||||
}));
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
renderTabExtraContent = () => {
|
||||
const { importPlugins, dataSourceTypes } = this.props;
|
||||
|
||||
// @todo onSelect 不行?
|
||||
return [
|
||||
_isArray(dataSourceTypes) && dataSourceTypes.length > 0 ? (
|
||||
<MenuButton label="新建" onItemClick={this.handleCreateDataSourceMenuBtnClick}>
|
||||
{dataSourceTypes.map((type) => (
|
||||
<MenuButtonItem key={type.type}>{type.type}</MenuButtonItem>
|
||||
))}
|
||||
</MenuButton>
|
||||
) : _isArray(dataSourceTypes) && dataSourceTypes.length === 1 ? (
|
||||
<Button onClick={this.handleCreateDataSourceBtnClick.bind(this, dataSourceTypes[0].type)}>新建</Button>
|
||||
) : null,
|
||||
_isArray(importPlugins) && importPlugins.length > 1 ? (
|
||||
<MenuButton label="导入" onItemClick={this.openImportDataSourceTab}>
|
||||
{importPlugins.map((plugin) => (
|
||||
<MenuButtonItem key={plugin.name}>{plugin.name}</MenuButtonItem>
|
||||
))}
|
||||
</MenuButton>
|
||||
) : _isArray(importPlugins) && importPlugins.length === 1 ? (
|
||||
<Button onClick={this.openImportDataSourceTab.bind(this, importPlugins[0].name)}>导入</Button>
|
||||
) : null,
|
||||
];
|
||||
};
|
||||
|
||||
// 更通用的处理
|
||||
renderTabItemContentByKey = (tabItemKey: any, data: any) => {
|
||||
const { dataSourceList, tabItems } = this.state;
|
||||
const { dataSourceTypes = [] } = this.props;
|
||||
|
||||
if (tabItemKey === TAB_ITEM_LIST) {
|
||||
return (
|
||||
<List
|
||||
dataSourceTypes={dataSourceTypes}
|
||||
dataSource={dataSourceList}
|
||||
onEditDataSource={this.handleEditDataSource}
|
||||
onDuplicateDataSource={this.handleDuplicateDataSource}
|
||||
onRemoveDataSource={this.handleRemoveDataSource}
|
||||
/>
|
||||
);
|
||||
} else if (tabItemKey === TAB_ITEM_EDIT) {
|
||||
const dataSourceType = dataSourceTypes.find((type) => type.type === data?.dataSource.type);
|
||||
if (dataSourceType) {
|
||||
return (
|
||||
<DataSourceForm
|
||||
dataSourceType={dataSourceType}
|
||||
dataSource={data?.dataSource}
|
||||
onComplete={this.handleUpdateDataSource}
|
||||
onCancel={this.closeTab.bind(this, TAB_ITEM_EDIT)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (tabItemKey === TAB_ITEM_CREATE) {
|
||||
return (
|
||||
<DataSourceForm
|
||||
dataSourceType={data?.dataSourceType}
|
||||
onComplete={this.handleCreateDataSource}
|
||||
onCancel={this.closeTab.bind(this, tabItemKey)}
|
||||
/>
|
||||
);
|
||||
} else if (tabItemKey === TAB_ITEM_IMPORT) {
|
||||
const tabItemData = tabItems.find((tabItem) => tabItem.key === tabItemKey);
|
||||
if (tabItemData) {
|
||||
const Content = tabItemData.content;
|
||||
return (
|
||||
<Content
|
||||
dataSourceTypes={dataSourceTypes}
|
||||
onCancel={this.closeTab.bind(this, tabItemKey)}
|
||||
onImport={this.handleImportDataSourceList}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { activeTabKey, tabItems } = this.state;
|
||||
|
||||
return (
|
||||
<div className="lowcode-plugin-datasource-pane">
|
||||
<Tab
|
||||
activeKey={activeTabKey}
|
||||
extra={this.renderTabExtraContent()}
|
||||
onChange={this.handleTabChange}
|
||||
onClose={this.closeTab}
|
||||
>
|
||||
{tabItems.map((item: TabItem) => (
|
||||
<TabItem {...item}>{this.renderTabItemContentByKey(item.key, item.data)}</TabItem>
|
||||
))}
|
||||
</Tab>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import { JSONSchema6 } from 'json-schema';
|
||||
|
||||
export type DataSourceType = {
|
||||
type: string;
|
||||
schema: JSONSchema6;
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { DataSourceConfig } from '@ali/lowcode-types';
|
||||
import { DataSourceType } from './datasource-type';
|
||||
|
||||
// 导入插件
|
||||
export interface DataSourcePaneImportPlugin {
|
||||
name: string;
|
||||
title: string;
|
||||
component: React.ReactNode;
|
||||
componentProps?: DataSourcePaneImportPluginCustomProps;
|
||||
}
|
||||
|
||||
export interface DataSourcePaneImportPluginCustomProps extends DataSourcePaneImportPluginComponentProps {
|
||||
[customPropName: string]: any;
|
||||
}
|
||||
|
||||
export interface DataSourcePaneImportPluginComponentProps {
|
||||
dataSourceTypes: DataSourceType[];
|
||||
onImport?: (dataSourceList: DataSourceConfig[]) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from './datasource-type';
|
||||
export * from './import-plugin';
|
||||
@ -1,5 +0,0 @@
|
||||
import test from 'ava';
|
||||
|
||||
test('foobar', t => {
|
||||
t.pass();
|
||||
});
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"lib": ["esnext", "dom"],
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"./src/"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user