Commit c0b35d84 authored by wenmo's avatar wenmo

数据流

parent 42a72dcc
...@@ -20,7 +20,7 @@ const Settings: LayoutSettings & { ...@@ -20,7 +20,7 @@ const Settings: LayoutSettings & {
locale: true locale: true
}, },
headerHeight: 48, headerHeight: 48,
//splitMenus: true splitMenus: true
}; };
export default Settings; export default Settings;
...@@ -27,6 +27,12 @@ export default [ ...@@ -27,6 +27,12 @@ export default [
icon: 'consoleSql', icon: 'consoleSql',
component: './Studio', component: './Studio',
}, },
{
path: '/flinksqlstudio',
name: 'flinsqlstudio',
icon: 'consoleSql',
component: './FlinkSqlStudio',
},
{ {
path: '/task', path: '/task',
name: 'task', name: 'task',
......
import * as monaco from "monaco-editor";
const Completion =[
/** * 内置函数 */
{
label: 'SUM(number)',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'SUM(${1:})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
detail: '返回指定参数的求和'
}
];
export default Completion;
export type BaseDataSourceField ={
fields:[{
label?:string,
displayName?:string,
aliasName?:string,
kind?:any,
insertText?:string,
insertTextRules?:any,
detail?:string,
}]
}
export type BaseDataSourceHeader ={
fields:[{
label?:string,
displayName?:string,
aliasName?:string,
kind?:any,
insertText?:string,
insertTextRules?:any,
detail?:string,
}]
}
export type CompletionItem ={
label?:string,
kind?:any,
insertText?:string,
insertTextRules?:any,
detail?:string,
}
export type StudioParam = {
statement:string,
}
import React, {useEffect, useImperativeHandle, useRef} from 'react';
import * as _monaco from "monaco-editor";
// import MonacoEditor from "react-monaco-editor/lib/editor";
import MonacoEditor from "react-monaco-editor";
import {BaseDataSourceField, BaseDataSourceHeader, CompletionItem} from "./data";
import Completion from "./completion";
import {executeSql} from "./service";
import {StateType} from "@/pages/Studio/model";
import {connect} from "umi";
let provider = {
dispose: () => {},
};
interface IRightContent {
value: any;
handleCheck: () => Promise<boolean>;
secondRightData: (BaseDataSourceField|BaseDataSourceHeader)[];
}
const FlinkSqlEditor = (props:any) => {
const {
height = '300px',
width = '100%',
language = 'sql',
onChange=(val: string, event: { changes: { text: any }[] })=>{},
options = {
selectOnLineNumbers: true,
renderSideBySide: false,
},
// sql=props.data.sql,
sql,
dispatch,
} = props
;
const { value, handleCheck, secondRightData = [] }: IRightContent = props;
const editorInstance:any = useRef<any>();
const monacoInstance: any = useRef();
const code: any = useRef(sql ? sql : '');
// const code: any = useRef(value ? value.formulaContent : '');
const cache: any = useRef(code.current);
const [refresh, setRefresh] = React.useState<boolean>(false);
useEffect(
() => () => {
provider.dispose();
},
[]
);
useImperativeHandle(editorInstance, () => ({
handleSetEditorVal,
getEditorData: () => cache.current,
}));
const submit = async () => {
await executeSql({statement:cache.current});
};
const handleSetEditorVal = (value: string): void => {
if (!value) return;
// 为所选取的值赋值到编辑器中
if (editorInstance.current && value) {
const selection = editorInstance?.current?.getSelection?.();
const range = new _monaco.Range(
selection.startLineNumber,
selection.startColumn,
selection.endLineNumber,
selection.endColumn
);
const id = { major: 1, minor: 1 };
const op = { identifier: id, range, text: value, forceMoveMarkers: true };
editorInstance.current.executeEdits('', [op]);
editorInstance.current.focus();
}
};
const onChangeHandle = (val: string, event: { changes: { text: any }[] }) => {
onChange(val,event);
/*const curWord = event.changes[0].text;
if (curWord === ';') {
cache.current = val +'\r\n';
setRefresh(!refresh); // 刷新页面
return;
}
cache.current = val;*/
dispatch({
type: "Studio/saveSql",
payload: val,
});
};
interface ISuggestions {
label: string;
kind: string;
insertText: string;
detail?: string;
}
const editorDidMountHandle = (editor: any, monaco: any) => {
monacoInstance.current = monaco;
editorInstance.current = editor;
const newSecondRightFields: BaseDataSourceHeader[] = [];
(secondRightData as BaseDataSourceHeader[]).forEach((record) => {
if (record.fields && Array.isArray(record.fields)) {
record.fields.forEach((item: any) => {
newSecondRightFields.push(item);
});
}
});
code.current = newSecondRightFields; // 数组长度永远为1
// 提示项设值
provider = monaco.languages.registerCompletionItemProvider('sql', {
provideCompletionItems() {
const suggestions: ISuggestions[] = [];
if (code && code.current) {
code.current.forEach((record:any) => {
suggestions.push({
// label未写错 中间加空格为了隐藏大写字段名称 大写字段名称用于规避自定义提示不匹配小写的bug
label:
record.label ||
`${record.displayName} (${
record.aliasName
}) ${''}(${record.aliasName.toUpperCase()})`, // 显示名称
kind: record.kind || monaco.languages.CompletionItemKind.Field, // 这里Function也可以是别的值,主要用来显示不同的图标
insertText: record.insertText || record.aliasName, // 实际粘贴上的值
detail: record.detail || `(property) ${record.aliasName}: String`,
});
});
}
Completion.forEach((item:CompletionItem) => {
suggestions.push(item);
});
return {
suggestions,
};
},
quickSuggestions: true,
triggerCharacters: ['$', '.', '='],
});
editor.focus();
};
return (
<React.Fragment>
<MonacoEditor
width={width}
height={height}
language={language}
//value={cache.current}
options={options}
onChange={onChangeHandle}
theme="vs-dark"
editorDidMount={editorDidMountHandle}
/>
</React.Fragment>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
sql: Studio.sql,
}))(FlinkSqlEditor);
import request from 'umi-request';
import {StudioParam} from "@/components/FlinkSqlEditor/data";
export async function executeSql(params: StudioParam) {
return request('/api/studio/executeSql', {
method: 'POST',
data: {
...params,
},
});
}
@import '~antd/lib/style/themes/default.less';
.container{
background: #fafafa;
border: 1px solid #e6f7ff;
}
.ant-divider-horizontal-0 {
margin: 0!important;
}
.dw-path{
margin-left: 10px;
line-height: 32px;
}
import React from "react";
import styles from "./index.less";
import { Menu, Dropdown, Typography, Row, Col } from "antd";
import {CaretRightOutlined, DownOutlined, PlayCircleOutlined } from "@ant-design/icons";
import Space from "antd/es/space";
import Divider from "antd/es/divider";
import Button from "antd/es/button/button";
import Breadcrumb from "antd/es/breadcrumb/Breadcrumb";
import {StateType} from "@/pages/Studio/model";
import {connect} from "umi";
const { SubMenu } = Menu;
//<Button shape="circle" icon={<CaretRightOutlined />} />
const menu = (
<Menu>
<Menu.Item>1st menu item</Menu.Item>
<Menu.Item>2nd menu item</Menu.Item>
<SubMenu title="sub menu">
<Menu.Item>3rd menu item</Menu.Item>
<Menu.Item>4th menu item</Menu.Item>
</SubMenu>
<SubMenu title="disabled sub menu" disabled>
<Menu.Item>5d menu item</Menu.Item>
<Menu.Item>6th menu item</Menu.Item>
</SubMenu>
</Menu>
);
const StudioMenu = (props:any) => {
const {sql} = props;
console.log(props);
const executeSql = () =>{
console.log('获取'+sql);
};
const runMenu = (
<Menu>
<Menu.Item onClick={executeSql}>执行</Menu.Item>
</Menu>
);
return (
<Row className={styles.container}>
<Col span={24}>
<div>
<Space>
<Dropdown overlay={menu}>
<Button type="text" onClick={e => e.preventDefault()}>
文件
</Button>
</Dropdown>
<Dropdown overlay={menu}>
<Button type="text" onClick={e => e.preventDefault()}>
编辑
</Button>
</Dropdown>
<Dropdown overlay={runMenu}>
<Button type="text" onClick={e => e.preventDefault()}>
执行
</Button>
</Dropdown>
<Dropdown overlay={menu}>
<Button type="text" onClick={e => e.preventDefault()}>
帮助
</Button>
</Dropdown>
</Space>
</div>
</Col>
<Divider className={styles["ant-divider-horizontal-0"]}/>
<Col span={24}>
<Breadcrumb className={styles["dw-path"]}>
<Breadcrumb.Item>数据仓库</Breadcrumb.Item>
<Breadcrumb.Item>维度</Breadcrumb.Item>
<Breadcrumb.Item>用户信息</Breadcrumb.Item>
</Breadcrumb>
</Col>
</Row>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
sql: Studio.sql,
}))(StudioMenu);
import { Tabs } from 'antd';
import React from 'react';
import StudioEdit from "../StudioEdit";
const { TabPane } = Tabs;
const initialPanes = [
{ title: '未命名', key: '0' ,value:'select * from ',closable: false,},
];
class EditorTabs extends React.Component {
newTabIndex = 1;
state = {
activeKey: initialPanes[0].key,
panes: initialPanes,
};
onChange = (activeKey: any) => {
this.setState({ activeKey });
};
onEdit = (targetKey: any, action: any) => {
this[action](targetKey);
};
updateValue = (targetKey: any, val: string)=>{
const { panes, activeKey } = this.state;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
pane.value = val;
return;
}
});
/*debugger;
this.setState({
panes:panes,
activeKey: activeKey,
});*/
};
add = () => {
const { panes } = this.state;
const activeKey = this.newTabIndex++;
const newPanes = [...panes];
newPanes.push({ title: `未命名${activeKey}`,value:'', key: `${activeKey}` });
this.setState({
panes: newPanes,
activeKey: activeKey,
});
};
remove = (targetKey:any) => {
const { panes, activeKey } = this.state;
let newActiveKey = activeKey;
let lastIndex = 1;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter(pane => pane.key !== targetKey);
if (newPanes.length && newActiveKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
this.setState({
panes: newPanes,
activeKey: newActiveKey,
});
};
render() {
const { panes, activeKey } = this.state;
return (
<Tabs
type="editable-card"
size="small"
onChange={this.onChange}
activeKey={activeKey}
onEdit={this.onEdit}
>
{panes.map(pane => (
<TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
<StudioEdit value={{formulaContent:pane.value}} onChange={
(val: string)=>{
this.updateValue(pane.key,val);
}} />
</TabPane>
))}
</Tabs>
);
}
}
export default EditorTabs;
@import '~antd/es/style/themes/default.less';
import React from "react";
import {connect} from "umi";
import {StateType} from "@/pages/Demo/FormStepForm/model";
import {DownOutlined, FrownFilled, FrownOutlined, MehOutlined, SmileOutlined} from "@ant-design/icons";
import { Tree, Input } from 'antd';
const { Search } = Input;
type StudioTreeProps = {
};
const treeData = [
{
title: 'parent 1',
key: '0-0',
icon: <SmileOutlined />,
children: [
{
title: 'leaf',
key: '0-0-0',
icon: <MehOutlined />,
},
{
title: 'leaf',
key: '0-0-1',
icon: ({ selected }) => (selected ? <FrownFilled /> : <FrownOutlined />),
},
],
},
];
const StudioTree: React.FC<StudioTreeProps> = (props) => {
// state = {
// expandedKeys: [],
// searchValue: '',
// autoExpandParent: true,
// };
const onExpand =()=>{
// setState({
// expandedKeys,
// autoExpandParent: false,
// })
};
const onChange =()=>{
};
return (
<div>
<Search style={{ marginBottom: 8 }} placeholder="Search" onChange={onChange} />
<Tree
onExpand={onExpand}
// expandedKeys={expandedKeys}
// autoExpandParent={autoExpandParent}
showIcon
showLine
defaultExpandAll
defaultSelectedKeys={['0-0-0']}
switcherIcon={<DownOutlined />}
treeData={treeData}
/>
</div>
);
};
export default connect(({ studio }: { studio: StateType }) => ({
}))(StudioTree);
@import '~antd/es/style/themes/default.less';
.main {
//width: 100%;
//background: @component-background;
}
.card {
margin-bottom: 24px;
:global {
.ant-legacy-form-item .ant-legacy-form-item-control-wrapper {
width: 100%;
}
}
}
.card{
background-color: #ffffff;
}
import React, {useEffect, useState} from "react";
import {connect} from "umi";
import PageContainer from "@ant-design/pro-layout/es/components/PageContainer";
import styles from './index.less';
import StudioMenu from "./StudioMenu";
import {Row,Col,Card} from "antd";
import StudioTree from "./StudioTree";
import StudioTabs from "./StudioTabs";
import {StateType} from "@/pages/Studio/model";
type StudioProps = {
sql: StateType['sql'];
};
const Studio: React.FC<StudioProps> = ({ sql }) => {
const [console, setConsole] = useState<boolean>(false);
const [sqls, setSqls] = useState<String>();
useEffect(() => {
setSqls(sql);
}, [sql]);
return (
<PageContainer
title={false}
content={<StudioMenu />}
className={styles.main}
>
<Card bordered={false} className={styles.card}>
<Row>
<Col span={4}>
<StudioTree />
</Col>
<Col span={20}>
<StudioTabs />
</Col>
</Row>
</Card>
</PageContainer>
)
};
/*function mapStateToProps(state) {
// 这个state是所有model层的state,这里只用到其中一个,所以state.testPage把命名空间为testPage这个model层的state数据取出来
// es6语法解构赋值
debugger;
const { data } = state.Studio;
// 这里return出去的数据,会变成此组件的props,在组件可以通过props.num取到。props变化了,会重新触发render方法,界面也就更新了。
return {
data,
};
}*/
// export default connect(mapStateToProps)(Studio);
export default connect(({ Studio }: { Studio: StateType }) => ({
sql: Studio.sql,
}))(Studio);
// export default Studio;
import {Effect, Reducer} from "umi"; import {Effect, Reducer} from "umi";
import {fakeSubmitForm} from "@/pages/Demo/FormStepForm/service";
export type StateType = { export type StateType = {
current?: string; current?: string;
data?: { sql?: string;
sql: string;
};
}; };
export type ModelType = { export type ModelType = {
...@@ -17,3 +16,39 @@ export type ModelType = { ...@@ -17,3 +16,39 @@ export type ModelType = {
saveSql: Reducer<StateType>; saveSql: Reducer<StateType>;
}; };
}; };
const Model: ModelType = {
namespace: 'Studio',
state: {
current: 'info',
sql: '',
},
effects: {
*executeSql({ payload }, { call, put }) {
yield call(fakeSubmitForm, payload);
yield put({
type: 'saveStepFormData',
payload,
});
yield put({
type: 'saveCurrentStep',
payload: 'result',
});
},
},
reducers: {
saveSql(state, { payload }) {
return {
...state,
sql: {
...payload,
},
};
},
},
};
export default Model;
...@@ -52,6 +52,7 @@ export default { ...@@ -52,6 +52,7 @@ export default {
'menu.demo': 'Demo 开发模板', 'menu.demo': 'Demo 开发模板',
'menu.cluster': '集群中心', 'menu.cluster': '集群中心',
'menu.studio': 'FlinkSql IDE', 'menu.studio': 'FlinkSql IDE',
'menu.flinsqlstudio': 'FlinkSql Studio',
'menu.task': '作业中心', 'menu.task': '作业中心',
'menu.dai': 'Dai 人工智能', 'menu.dai': 'Dai 人工智能',
'menu.dev': 'Dev 开发者工具', 'menu.dev': 'Dev 开发者工具',
......
import Studio from "@/components/Studio";
export default () => {
return <Studio></Studio>
}
import {Effect, Reducer} from "umi";
import {fakeSubmitForm} from "@/pages/Demo/FormStepForm/service";
export type StateType = {
current?: string;
sql?: string;
};
export type ModelType = {
namespace: string;
state: StateType;
effects: {
executeSql: Effect;
};
reducers: {
saveSql: Reducer<StateType>;
};
};
const Model: ModelType = {
namespace: 'Studio',
state: {
current: 'info',
sql: '',
},
effects: {
*executeSql({ payload }, { call, put }) {
yield call(fakeSubmitForm, payload);
yield put({
type: 'saveStepFormData',
payload,
});
yield put({
type: 'saveCurrentStep',
payload: 'result',
});
},
},
reducers: {
saveSql(state, { payload }) {
return {
...state,
sql: payload,
};
},
},
};
export default Model;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment