feat: 数据源面板

This commit is contained in:
muyun.my 2020-09-21 08:33:46 +08:00
parent 56eaff52c8
commit 47f55cafce
15 changed files with 424 additions and 155 deletions

View File

@ -13,7 +13,72 @@
"list": [
{
"type": "http",
"id": "http1",
"id": "请求商家数据,是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": true,
"options": {
"uri": "https://www.taobao.com",
"params": {
"a": 1,
"b": true,
"c": "3"
}
}
},
{
"type": "http",
"id": "请求商家数据2是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": true,
"options": {
"uri": "https://www.taobao.com",
"params": {
"a": 1,
"b": true,
"c": "3"
}
}
},
{
"type": "http",
"id": "请求商家数据3是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": true,
"options": {
"uri": "https://www.taobao.com",
"params": {
"a": 1,
"b": true,
"c": "3"
}
}
},
{
"type": "http",
"id": "请求商家数据4是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": true,
"options": {
"uri": "https://www.taobao.com",
"params": {
"a": 1,
"b": true,
"c": "3"
}
}
},
{
"type": "http",
"id": "请求商家数据5是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": false,
"options": {
"uri": "https://www.taobao.com",
"params": {
"a": 1,
"b": true,
"c": "3"
}
}
},
{
"type": "http",
"id": "请求商家数据6是一个 HTTP 请求,是一个 HTTP 请求,是一个 HTTP 请求",
"isInit": true,
"options": {
"uri": "https://www.taobao.com",

View File

@ -1,26 +1,10 @@
TODO
---
## 低代码引擎 - 数据源面板插件
* 多语言
* [later]表达式和其他类型的切换
* 现有场景代码的兼容
* class publich method bind issue
* ICON
* 支持变量
## 数据源面板
数据源管理
* 新建
* 编辑
* 导入
* 指定数据源类型
* 定制数据源导入插件
对页面的数据源进行管理(新建,编辑,导入)。
## 数据源类型定义
内置变量http 和 mtop 类型,支持传入自定义类型。
内置 fetch 和 mtop 类型,支持传入自定义类型。
```
type DataSourceType = {
@ -29,9 +13,7 @@ type DataSourceType = {
};
```
数据源类型需要在集团规范约束下扩展。
目前只允许在 options 下添加扩展字段。
数据源类型需要在集团规范约束下扩展。目前只允许在 options 下添加扩展字段。
比如 mtop 类型,需要添加 options.v (版本)字段。
@ -56,16 +38,8 @@ interface DataSourcePaneImportPluginComponentProps extends DataSourcePaneImportP
}
```
## 问题
* 变量,上下文放数据源里管理是否合适
* mockUrl 和 mockData
* 设计器的设计语言无法统一
## 插件开发
<https://yuque.antfin-inc.com/ali-lowcode/docs/ip4awq>
[https://yuque.antfin-inc.com/ali-lowcode/docs/ip4awq](插件开发文档)。
## node 版本
v14.4.0
本地开发需要在 v14.4.0 的 node 环境下进行。

View File

@ -0,0 +1,18 @@
TODO
---
* 多语言
* ICON
* 支持变量
* 定制样式
* 不使用 bind
* 表达式 setter 的联想
* [later]表达式和其他类型的切换
* 变量,上下文的提案
* mock 的提案
## 问题
* 变量,上下文放数据源里管理是否合适
* mockUrl 和 mockData
* 设计器的设计语言无法统一

View File

@ -1,7 +1,6 @@
{
"plugins": [
[
"build-plugin-component"
]
"build-plugin-component",
"build-plugin-fusion"
]
}

View File

@ -1,5 +1,6 @@
// @todo schema default
import React, { PureComponent, ReactElement, FC } 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';
@ -164,8 +165,6 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
}
});
debugger;
if (_get(formSchema, 'properties.options.properties.params')) {
formSchema.properties.options.properties.params = {
...formSchema.properties.options.properties.params,
@ -247,7 +246,7 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
const { dataSource } = this.props;
return (
<div className="lowcode-plugin-datasource-pane-datasource">
<div className="lowcode-plugin-datasource-form">
<SchemaForm
onSubmit={this.handleFormSubmit}
components={{
@ -258,13 +257,14 @@ export class DataSourceForm extends PureComponent<DataSourceFormProps, DataSourc
Switch,
JSFunction,
}}
labelCol={3}
wrapperCol={21}
labelCol={4}
wrapperCol={19}
schema={this.deriveSchema()}
initialValues={this.deriveInitialData(dataSource)}
>
<FormButtonGroup offset={7}>
<FormButtonGroup offset={4}>
<Submit></Submit>
<Button onClick={this.handleCancel}></Button>
</FormButtonGroup>
</SchemaForm>
</div>

View File

@ -0,0 +1,7 @@
.param-value {
display: flex;
flex-direction: row;
.param-value-type {
margin-right: 4px;
}
}

View File

@ -1,5 +1,5 @@
import React, { PureComponent, ReactElement, FC } from 'react';
import { Button, Input, Radio, NumberPicker, Switch } from '@alifd/next';
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';
@ -10,21 +10,37 @@ 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?: () => void;
types: ParamValueType[];
}
export interface ParamValueState {
type: 'string' | 'number' | 'boolean' | '';
}
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 = {
type: '',
};
@ -48,7 +64,7 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
// @todo 需要再 bind 一次?
handleChange = (value) => {
this.props?.onChange(value);
}
};
componentDidUpdate(prevProps) {
if (this.props.value !== prevProps.value) {
@ -78,19 +94,46 @@ class ParamValueComp extends PureComponent<ParamValueProps, ParamValueState> {
});
};
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}
/>
);
}
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>
{
<RadioGroup shape="button" size="small" onChange={this.handleTypeChange}>
<Radio value="string"></Radio>
<Radio value="boolean"></Radio>
<Radio value="number"></Radio>
<Radio value="expression"></Radio>
</RadioGroup>
}
<div className="param-value">
{this.renderTypeSelect()}
{type === 'string' && <Input onChange={this.handleChange.bind(this)} value={value} />}
{type === 'boolean' && <Switch onChange={this.handleChange.bind(this)} checked={value} />}
{type === 'number' && <NumberPicker onChange={this.handleChange.bind(this)} value={value} />}

View File

@ -0,0 +1,15 @@
.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;
}
}

View File

@ -14,6 +14,8 @@ import { DataSourceConfig } from '@ali/lowcode-types';
import Ajv from 'ajv';
import { DataSourcePaneImportPluginComponentProps } from '../types';
import './code.scss';
export interface DataSourceImportPluginCodeProps extends DataSourcePaneImportPluginComponentProps {
defaultValue?: DataSourceConfig[];
}
@ -115,11 +117,11 @@ export class DataSourceImportPluginCode extends PureComponent<
const { code, isCodeValid } = this.state;
return (
<div>
<div className="lowcode-plugin-datasource-import-plugin-code">
<MonacoEditor
theme="vs-dark"
width={800}
height={600}
height={400}
defaultValue={code}
language="json"
onChange={this.handleEditorChange}
@ -127,10 +129,10 @@ export class DataSourceImportPluginCode extends PureComponent<
formatOnType
formatOnPaste
/>
{!isCodeValid && <p></p>}
<p>
{!isCodeValid && <p className="error-msg"></p>}
<p className="btns">
<Button onClick={onCancel}></Button>
<Button onClick={this.handleComplete}></Button>
<Button type="primary" onClick={this.handleComplete}></Button>
</p>
</div>
);

View File

@ -12,7 +12,86 @@
}
.lowcode-plugin-datasource-pane-list {
.next-virtual-list-wrapper {
min-height: 400px;
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 {
}
}
}

View File

@ -4,6 +4,8 @@ import { DataSourcePane } from './pane';
import { DataSourcePaneImportPlugin, DataSourceType } from './types';
import { DataSourceImportPluginCode } from './import-plugins';
import './index.scss';
export { DataSourceImportPluginCode };
const PLUGIN_NAME = 'dataSourcePane';

View File

@ -64,57 +64,77 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
?.filter((item) => (keyword ? item.id.indexOf(keyword) !== -1 : true))
?.map((item) => (
<li key={item.id}>
<Balloon
trigger={
<div>
<Tag size="small">{item.type}</Tag>
<Tag size="small">{item.isInit ? '自动' : '手动'}</Tag>
<div className="datasource-item">
<div className="datasource-item-title">
<div className="datasource-item-id" title={item.id}>
{item.id}
<Button onClick={this.handleEditDataSource.bind(this, item.id)}></Button>
<Button onClick={this.handleDuplicateDataSource.bind(this, item.id)}></Button>
<Button onClick={this.handleRemoveDataSource.bind(this, item.id)}></Button>
</div>
}
align="r"
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>
{_isBoolean(val) ? 'bool' : _isNumber(val) ? 'number' : _isPlainObject(val) ? 'obj' : 'string'}
</Tag>
{val.toString()}
</div>
)}
/>
</Table>
</Balloon>
<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>
{_isBoolean(val)
? 'bool'
: _isNumber(val)
? 'number'
: _isPlainObject(val)
? 'obj'
: 'string'}
</Tag>
{val.toString()}
</div>
)}
/>
</Table>
</Balloon>
<Button size="small" onClick={this.handleEditDataSource.bind(this, item.id)}>
</Button>
<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>
)) || []
);
@ -136,7 +156,9 @@ export default class DataSourceList extends PureComponent<DataSourceListProps, D
}))}
onFilterChange={this.handleSearchFilterChange}
/>
<VirtualList>{this.deriveListDataSource()}</VirtualList>
<div className="datasource-list">
<VirtualList>{this.deriveListDataSource()}</VirtualList>
</div>
</div>
);
}

View File

@ -3,7 +3,7 @@
*/
import React, { PureComponent } from 'react';
import { DataSource, DataSourceConfig } from '@ali/lowcode-types';
import { Tab, Button, MenuButton, Message } from '@alifd/next';
import { Tab, Button, MenuButton, Message, Dialog } from '@alifd/next';
import _cloneDeep from 'lodash/cloneDeep';
import _uniqueId from 'lodash/uniqueId';
import _startsWith from 'lodash/startsWith';
@ -63,14 +63,14 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
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);
// 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[]) => {
@ -83,31 +83,66 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
};
handleImportDataSourceList = (toImport: DataSourceConfig[]) => {
this.closeTab(TAB_ITEM_IMPORT);
this.setState(
({ dataSourceList }) => ({
dataSourceList: dataSourceList.concat(toImport),
}),
() => {
this.handleDataSourceListChange();
},
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) => {
this.closeTab(TAB_ITEM_CREATE);
this.setState(
({ dataSourceList }) => ({
dataSourceList: dataSourceList.concat([
{
...toCreate,
},
]),
}),
() => {
this.handleDataSourceListChange();
},
);
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) => {
@ -130,14 +165,22 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
};
handleRemoveDataSource = (dataSourceId: string) => {
this.setState(
({ dataSourceList }) => ({
dataSourceList: dataSourceList.filter((item) => item.id !== dataSourceId),
}),
() => {
this.handleDataSourceListChange();
},
);
const remove = () => {
this.setState(
({ dataSourceList }) => ({
dataSourceList: dataSourceList.filter((item) => item.id !== dataSourceId),
}),
() => {
this.handleDataSourceListChange();
},
);
};
Dialog.confirm({
content: `确定要删除吗?`,
onOk: () => {
remove();
}
});
};
handleDuplicateDataSource = (dataSourceId: string) => {
@ -233,7 +276,7 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
}));
this.setState({ activeTabKey: TAB_ITEM_IMPORT });
} else {
Message.notice('当前已有一个导入数据源的 TAB 被大家');
Message.notice('当前已有一个导入数据源的 TAB');
}
};
@ -256,21 +299,21 @@ export class DataSourcePane extends PureComponent<DataSourcePaneProps, DataSourc
// @todo onSelect 不行?
return [
_isArray(dataSourceTypes) && dataSourceTypes.length > 1 ? (
<MenuButton size="small" label="新建" onItemClick={this.openCreateDataSourceTab}>
<MenuButton label="新建" onItemClick={this.openCreateDataSourceTab}>
{dataSourceTypes.map((type) => (
<MenuButtonItem key={type.type}>{type.type}</MenuButtonItem>
))}
</MenuButton>
) : _isArray(dataSourceTypes) && dataSourceTypes.length > 1 ? (
<Button onClick={this.openImportDataSourceTab.bind(this, dataSourceTypes[0])}></Button>
) : _isArray(dataSourceTypes) && dataSourceTypes.length === 1 ? (
<Button onClick={this.openCreateDataSourceTab.bind(this, dataSourceTypes[0].type)}></Button>
) : null,
_isArray(importPlugins) && importPlugins.length > 1 ? (
<MenuButton size="small" label="导入" onItemClick={this.openImportDataSourceTab}>
<MenuButton label="导入" onItemClick={this.openImportDataSourceTab}>
{importPlugins.map((plugin) => (
<MenuButtonItem key={plugin.name}>{plugin.name}</MenuButtonItem>
))}
</MenuButton>
) : _isArray(importPlugins) && importPlugins.length > 1 ? (
) : _isArray(importPlugins) && importPlugins.length === 1 ? (
<Button onClick={this.openImportDataSourceTab.bind(this, importPlugins[0].name)}></Button>
) : null,
];

View File

@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.json",
"lib": ["es6", "dom"],
"lib": ["esnext", "dom"],
"compilerOptions": {
"outDir": "lib"
},