Commit 9f3e81e2 authored by godkaikai's avatar godkaikai

新增图表

parent 35d04ca9
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
"dependencies": { "dependencies": {
"@ant-design/charts": "^1.2.10", "@ant-design/charts": "^1.2.10",
"@ant-design/icons": "^4.5.0", "@ant-design/icons": "^4.5.0",
"@ant-design/plots": "^1.0.7",
"@ant-design/pro-descriptions": "^1.6.8", "@ant-design/pro-descriptions": "^1.6.8",
"@ant-design/pro-form": "^1.18.3", "@ant-design/pro-form": "^1.18.3",
"@ant-design/pro-layout": "^6.18.0", "@ant-design/pro-layout": "^6.18.0",
......
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
import {Button, Tag,Row, Col,Form,Select, Empty} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import styles from "./index.less";
import {FireOutlined, SearchOutlined,RedoOutlined} from '@ant-design/icons';
import {useState} from "react";
import React from "react";
const {Option} = Select;
export type LineChartConfig = {
xField: string,
yField: string,
seriesField?: string,
xAxis?: {
type?: string,
},
};
export type LineChartProps = {
onChange: (values: Partial<LineChartConfig>) => void;
data: [];
column: [];
};
const LineChartSetting: React.FC<LineChartProps> = (props) => {
const {data,column,onChange: handleChange,dispatch} = props;
const onValuesChange = (change: any, all: any) => {
handleChange(all);
};
const getColumnOptions = () => {
const itemList = [];
for (const item of column) {
itemList.push(<Option key={item} value={item} label={item}>
{item}
</Option>)
}
return itemList;
};
return (
<>
<Form
className={styles.form_setting}
onValuesChange={onValuesChange}
>
<Row>
<Col span={12}>
<Form.Item
label="x 轴" className={styles.form_item} name="xField"
>
{column&&column.length > 0 ? (
<Select defaultValue={column[0]} value={column[0]}>
{getColumnOptions()}
</Select>):(<Select >
{column&&getColumnOptions()}
</Select>)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
label="y 轴" className={styles.form_item} name="yField"
>
{column&&column.length > 0 ? (
<Select defaultValue={column[0]} value={column[0]}>
{getColumnOptions()}
</Select>):(<Select >
{column&&getColumnOptions()}
</Select>)}
</Form.Item>
</Col>
</Row>
<Row>
<Col span={12}>
<Form.Item
label="分组字段" className={styles.form_item} name="seriesField"
>
{column&&column.length > 0 ? (
<Select defaultValue={column[0]} value={column[0]}>
{getColumnOptions()}
</Select>):(<Select >
{column&&getColumnOptions()}
</Select>)}
</Form.Item>
</Col>
</Row>
</Form>
</>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
result: Studio.result,
}))(LineChartSetting);
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
import {Button, Tag,Row, Col,Form,Select, Empty} from "antd";
import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi";
import styles from "./index.less";
import {FireOutlined, SearchOutlined,RedoOutlined} from '@ant-design/icons';
import {CHART, isSql} from "@/components/Studio/conf";
import { Line } from '@ant-design/plots';
import {useEffect, useState} from "react";
import LineChartSetting from "./LineChartSetting";
import {showJobData} from "@/components/Studio/StudioEvent/DQL";
const {Option} = Select;
const Chart = (props:any) => {
const {current,result,dispatch} = props;
const [config, setConfig] = useState({});
const [data, setData] = useState([]);
const [column, setColumn] = useState([]);
const [type, setType] = useState<string>(CHART.LINE);
useEffect(() => {
toBuild();
}, [result,current.console.result]);
const toBuild = () => {
if(isSql(current.task.dialect)){
setData(current.console.result.result.rowData);
setColumn(current.console.result.result.columns);
}else{
setData(result.rowData);
setColumn(result.columns);
}
};
const toRebuild = () => {
if(!isSql(current.task.diagnosticCodesToIgnore)){
showJobData(current.console.result.jobId,dispatch);
}
};
const onValuesChange = (change: any, all: any) => {
if(change.type){
setType(change.type);
}
};
const renderChartSetting = () => {
switch (type){
case CHART.LINE:
return <LineChartSetting data={data} column={column} onChange={(value) => {
setConfig(value);
}} />;
default:
return <LineChartSetting />;
}
};
const renderChartContent = () => {
if(column.length==0){
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
}
switch (type){
case CHART.LINE:
return <Line data={data} {...config} />;
default:
return <Line data={data} {...config} />;
}
};
return (
<div style={{width: '100%'}}>
<Row>
<Col span={16} style={{padding:'20px'}}>
{renderChartContent()}
</Col>
<Col span={8}>
<Form
className={styles.form_setting}
onValuesChange={onValuesChange}
>
<Row>
<Col span={12}>
<Form.Item
label="图形类型" className={styles.form_item} name="type"
>
<Select defaultValue={CHART.LINE} value={CHART.LINE}>
<Option value={CHART.LINE}>折线图</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Button type="primary" onClick={toRebuild} icon={<RedoOutlined />}>
重新渲染
</Button>
</Col>
</Row>
</Form>
{renderChartSetting()}
</Col>
</Row>
</div>
);
};
export default connect(({ Studio }: { Studio: StateType }) => ({
current: Studio.current,
result: Studio.result,
}))(Chart);
import {Tabs, Empty} from "antd"; import {Tabs, Empty} from "antd";
import { import {
CodeOutlined, TableOutlined, RadarChartOutlined, CalendarOutlined, FileSearchOutlined, DesktopOutlined CodeOutlined, TableOutlined, RadarChartOutlined, CalendarOutlined, FileSearchOutlined, DesktopOutlined
, FunctionOutlined, ApartmentOutlined , FunctionOutlined, ApartmentOutlined,BarChartOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import {StateType} from "@/pages/FlinkSqlStudio/model"; import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi"; import {connect} from "umi";
...@@ -13,6 +13,7 @@ import StudioFX from "./StudioFX"; ...@@ -13,6 +13,7 @@ import StudioFX from "./StudioFX";
import StudioCA from "./StudioCA"; import StudioCA from "./StudioCA";
import StudioProcess from "./StudioProcess"; import StudioProcess from "./StudioProcess";
import {Scrollbars} from 'react-custom-scrollbars'; import {Scrollbars} from 'react-custom-scrollbars';
import Chart from "@/components/Chart";
const {TabPane} = Tabs; const {TabPane} = Tabs;
...@@ -50,6 +51,19 @@ const StudioConsole = (props: any) => { ...@@ -50,6 +51,19 @@ const StudioConsole = (props: any) => {
<StudioTable/> <StudioTable/>
</Scrollbars> </Scrollbars>
</TabPane> </TabPane>
<TabPane
tab={
<span>
<BarChartOutlined />
统计
</span>
}
key="StudioChart"
>
<Scrollbars style={{height: consoleHeight}}>
<Chart/>
</Scrollbars>
</TabPane>
<TabPane <TabPane
tab={ tab={
<span> <span>
......
...@@ -11,14 +11,14 @@ import Button from "antd/es/button/button"; ...@@ -11,14 +11,14 @@ import Button from "antd/es/button/button";
import Breadcrumb from "antd/es/breadcrumb/Breadcrumb"; import Breadcrumb from "antd/es/breadcrumb/Breadcrumb";
import {StateType} from "@/pages/FlinkSqlStudio/model"; import {StateType} from "@/pages/FlinkSqlStudio/model";
import {connect} from "umi"; import {connect} from "umi";
import {handleAddOrUpdate, postDataArray} from "@/components/Common/crud"; import { postDataArray} from "@/components/Common/crud";
import {executeSql, explainSql, getJobPlan} from "@/pages/FlinkSqlStudio/service"; import {executeSql, getJobPlan} from "@/pages/FlinkSqlStudio/service";
import StudioHelp from "./StudioHelp"; import StudioHelp from "./StudioHelp";
import StudioGraph from "./StudioGraph"; import StudioGraph from "./StudioGraph";
import {showCluster, showTables, saveTask} from "@/components/Studio/StudioEvent/DDL"; import {showCluster, showTables, saveTask} from "@/components/Studio/StudioEvent/DDL";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import StudioExplain from "../StudioConsole/StudioExplain"; import StudioExplain from "../StudioConsole/StudioExplain";
import {DIALECT, isSql} from "@/components/Studio/conf"; import {DIALECT, isOnline, isSql} from "@/components/Studio/conf";
import { import {
ModalForm, ModalForm,
} from '@ant-design/pro-form'; } from '@ant-design/pro-form';
...@@ -37,10 +37,13 @@ const StudioMenu = (props: any) => { ...@@ -37,10 +37,13 @@ const StudioMenu = (props: any) => {
const [modalVisible, handleModalVisible] = useState<boolean>(false); const [modalVisible, handleModalVisible] = useState<boolean>(false);
const [exportModalVisible, handleExportModalVisible] = useState<boolean>(false); const [exportModalVisible, handleExportModalVisible] = useState<boolean>(false);
const [graphModalVisible, handleGraphModalVisible] = useState<boolean>(false); const [graphModalVisible, handleGraphModalVisible] = useState<boolean>(false);
const [explainData, setExplainData] = useState([]);
const [graphData, setGraphData] = useState(); const [graphData, setGraphData] = useState();
const execute = () => { const execute = () => {
if(!isSql(current.task.dialect)&&!isOnline(current.task.type)){
message.warn(`该任务执行模式为【${current.task.type}】,不支持 SQL 查询,请手动保存后使用右侧按钮——作业提交`);
return;
}
let selectsql = null; let selectsql = null;
if (current.monaco.current) { if (current.monaco.current) {
let selection = current.monaco.current.editor.getSelection(); let selection = current.monaco.current.editor.getSelection();
...@@ -75,7 +78,7 @@ const StudioMenu = (props: any) => { ...@@ -75,7 +78,7 @@ const StudioMenu = (props: any) => {
if (res.datas.success) { if (res.datas.success) {
message.success('执行成功'); message.success('执行成功');
} else { } else {
message.success('执行失败'); message.error('执行失败');
} }
let newTabs = tabs; let newTabs = tabs;
for (let i = 0; i < newTabs.panes.length; i++) { for (let i = 0; i < newTabs.panes.length; i++) {
...@@ -230,8 +233,8 @@ const StudioMenu = (props: any) => { ...@@ -230,8 +233,8 @@ const StudioMenu = (props: any) => {
const runMenu = ( const runMenu = (
<Menu> <Menu>
<Menu.Item onClick={execute}>同步执行</Menu.Item> <Menu.Item onClick={execute}>SQL 查询</Menu.Item>
<Menu.Item onClick={submit}>异步提交</Menu.Item> <Menu.Item onClick={submit}>提交作业</Menu.Item>
</Menu> </Menu>
); );
...@@ -348,7 +351,7 @@ const StudioMenu = (props: any) => { ...@@ -348,7 +351,7 @@ const StudioMenu = (props: any) => {
/> />
</Tooltip>)} </Tooltip>)}
{(!current.task.dialect||current.task.dialect === DIALECT.FLINKSQL||isSql( current.task.dialect )) &&( {(!current.task.dialect||current.task.dialect === DIALECT.FLINKSQL||isSql( current.task.dialect )) &&(
<Tooltip title="执行当前的 FlinkSql"> <Tooltip title="执行当前的 SQL">
<Button <Button
type="text" type="text"
icon={<PlayCircleTwoTone/>} icon={<PlayCircleTwoTone/>}
...@@ -357,7 +360,7 @@ const StudioMenu = (props: any) => { ...@@ -357,7 +360,7 @@ const StudioMenu = (props: any) => {
/> />
</Tooltip>)} </Tooltip>)}
{(!current.task.dialect||current.task.dialect === DIALECT.FLINKSQL||isSql( current.task.dialect )) &&(<> {(!current.task.dialect||current.task.dialect === DIALECT.FLINKSQL||isSql( current.task.dialect )) &&(<>
<Tooltip title="提交当前的作业到集群"> <Tooltip title="提交当前的作业到集群,提交前请手动保存">
<Button <Button
type="text" type="text"
icon={<RocketTwoTone/>} icon={<RocketTwoTone/>}
......
...@@ -20,8 +20,12 @@ export const DIALECT = { ...@@ -20,8 +20,12 @@ export const DIALECT = {
JAVA:'Java', JAVA:'Java',
}; };
export const isSql = (type: string)=>{ export const CHART = {
switch (type){ LINE:'Line',
};
export const isSql = (dialect: string)=>{
switch (dialect){
case DIALECT.SQL: case DIALECT.SQL:
case DIALECT.MYSQL: case DIALECT.MYSQL:
case DIALECT.ORACLE: case DIALECT.ORACLE:
...@@ -32,4 +36,16 @@ export const isSql = (type: string)=>{ ...@@ -32,4 +36,16 @@ export const isSql = (type: string)=>{
default: default:
return false; return false;
} }
};
export const isOnline = (type: string)=>{
switch (type){
case RUN_MODE.LOCAL:
case RUN_MODE.STANDALONE:
case RUN_MODE.YARN_SESSION:
case RUN_MODE.KUBERNETES_SESSION:
return true;
default:
return false;
}
} }
import type {Effect, Reducer} from "umi";
import {
handleAddOrUpdate
} from "@/components/Common/crud";
import type {SqlMetaData} from "@/components/Studio/StudioEvent/data";
export type ClusterType = {
id: number,
name: string,
alias: string,
type: string,
hosts: string,
jobManagerHost: string,
status: number,
note: string,
enabled: boolean,
createTime: Date,
updateTime: Date,
}
export type ClusterConfigurationType = {
id: number,
name: string,
alias: string,
type: string,
config: any,
available: boolean,
note: string,
enabled: boolean,
createTime: Date,
updateTime: Date,
}
export type DataBaseType = {
id: number,
name: string,
alias: string,
groupName: string,
type: string,
url: string,
username: string,
password: string,
note: string,
dbVersion: string,
status: boolean,
healthTime: Date,
heartbeatTime: Date,
enabled: boolean,
createTime: Date,
updateTime: Date,
};
export type EnvType = {
id?: number,
name?: string,
alias?: string,
fragment?: boolean,
};
export type TaskType = {
id?: number,
catalogueId?: number,
name?: string,
alias?: string,
dialect?: string,
type?: string,
checkPoint?: number,
savePointStrategy?: number,
savePointPath?: string,
parallelism?: number,
fragment?: boolean,
statementSet?: boolean,
config?: [],
clusterId?: any,
clusterName?: string,
clusterConfigurationId?: number,
clusterConfigurationName?: string,
databaseId?: number,
databaseName?: string,
jarId?: number,
envId?: number,
note?: string,
enabled?: boolean,
createTime?: Date,
updateTime?: Date,
statement?: string,
session: string;
maxRowNum: number;
jobName: string;
useResult: boolean;
useChangeLog: boolean;
useAutoCancel: boolean;
useSession: boolean;
useRemote: boolean;
};
export type ConsoleType = {
result: {};
}
export type TabsItemType = {
title: string;
key: number,
value: string;
closable: boolean;
path: string[];
task?: TaskType;
console: ConsoleType;
monaco?: any;
isModified: boolean;
sqlMetaData?: SqlMetaData;
}
export type TabsType = {
activeKey: number;
panes?: TabsItemType[];
}
export type ConnectorType = {
tablename: string;
}
export type SessionType = {
session?: string;
sessionConfig?: {
type?: string;
useRemote?: boolean;
clusterId?: number;
clusterName?: string;
address?: string;
}
createUser?: string;
createTime?: string;
connectors: ConnectorType[];
}
export type StateType = {
toolHeight?: number;
toolRightWidth?: number;
toolLeftWidth?: number;
cluster?: ClusterType[];
sessionCluster?: ClusterType[];
clusterConfiguration?: ClusterConfigurationType[];
database?: DataBaseType[];
env?: EnvType[];
currentSession?: SessionType;
current?: TabsItemType;
sql?: string;
monaco?: any;
currentPath?: string[];
tabs?: TabsType;
session?: SessionType[];
result?: {};
rightClickMenu?: boolean;
refs?: {
history: any;
};
};
export type ModelType = {
namespace: string;
state: StateType;
effects: {
saveTask: Effect;
};
reducers: {
saveToolHeight: Reducer<StateType>;
saveToolRightWidth: Reducer<StateType>;
saveToolLeftWidth: Reducer<StateType>;
saveSql: Reducer<StateType>;
saveCurrentPath: Reducer<StateType>;
saveMonaco: Reducer<StateType>;
saveSqlMetaData: Reducer<StateType>;
saveTabs: Reducer<StateType>;
changeActiveKey: Reducer<StateType>;
saveTaskData: Reducer<StateType>;
saveSession: Reducer<StateType>;
showRightClickMenu: Reducer<StateType>;
refreshCurrentSession: Reducer<StateType>;
quitCurrentSession: Reducer<StateType>;
saveResult: Reducer<StateType>;
saveCluster: Reducer<StateType>;
saveSessionCluster: Reducer<StateType>;
saveClusterConfiguration: Reducer<StateType>;
saveDataBase: Reducer<StateType>;
saveEnv: Reducer<StateType>;
};
};
const Model: ModelType = {
namespace: 'Studio',
state: {
toolHeight: 400,
toolRightWidth: 300,
toolLeftWidth: 300,
cluster: [],
sessionCluster: [],
clusterConfiguration: [],
database: [],
env: [],
currentSession: {
connectors: [],
},
current: {
title: '草稿',
key: 0,
value: '',
closable: false,
path: ['草稿'],
isModified: false,
task: {
jobName: '草稿',
type: 'local',
checkPoint: 0,
savePointStrategy: 0,
savePointPath: '',
parallelism: 1,
fragment: true,
statementSet: false,
clusterId: 0,
clusterName: "本地环境",
clusterConfigurationId: undefined,
clusterConfigurationName: undefined,
databaseId: undefined,
databaseName: undefined,
jarId: undefined,
envId: undefined,
maxRowNum: 100,
config: [],
session: '',
alias: '草稿',
dialect: 'FlinkSql',
useResult: true,
useChangeLog: false,
useAutoCancel: false,
useSession: false,
useRemote: false,
},
console: {
result: {},
},
monaco: {},
sqlMetaData: undefined,
},
sql: '',
monaco: {},
currentPath: ['草稿'],
tabs: {
activeKey: 0,
panes: [{
title: '草稿',
key: 0,
value: '',
closable: false,
isModified: false,
path: ['草稿'],
task: {
jobName: '草稿',
type: 'local',
checkPoint: 0,
savePointStrategy: 0,
savePointPath: '',
parallelism: 1,
fragment: true,
statementSet: false,
clusterId: 0,
clusterName: "本地环境",
clusterConfigurationId: undefined,
clusterConfigurationName: undefined,
databaseId: undefined,
databaseName: undefined,
jarId: undefined,
envId: undefined,
session: '',
config: [],
maxRowNum: 100,
alias: '草稿',
dialect: 'FlinkSql',
useResult: true,
useChangeLog: false,
useAutoCancel: false,
useSession: false,
useRemote: false,
},
console: {
result: {},
},
monaco: {},
sqlMetaData: undefined,
}],
},
session: [],
result: {},
rightClickMenu: false,
refs: {
history: {},
}
},
effects: {
* saveTask({payload}, {call, put}) {
const para = payload;
para.configJson = JSON.stringify(payload.config);
yield call(handleAddOrUpdate, 'api/task', para);
yield put({
type: 'saveTaskData',
payload,
});
},
},
reducers: {
saveToolHeight(state, {payload}) {
return {
...state,
toolHeight: payload,
};
}, saveToolRightWidth(state, {payload}) {
return {
...state,
toolRightWidth: payload,
};
}, saveToolLeftWidth(state, {payload}) {
return {
...state,
toolLeftWidth: payload,
};
},
saveSql(state, {payload}) {
const {tabs} = state;
const newCurrent = state.current;
newCurrent.value = payload;
for (let i = 0; i < tabs.panes.length; i++) {
if (tabs.panes[i].key == tabs.activeKey) {
tabs.panes[i].value = payload;
tabs.panes[i].task && (tabs.panes[i].task.statement = payload);
}
}
return {
...state,
current: {
...newCurrent
},
tabs: {
...tabs
},
};
},
saveCurrentPath(state, {payload}) {
return {
...state,
currentPath: payload,
};
},
saveMonaco(state, {payload}) {
return {
...state,
monaco: {
...payload
},
};
},
saveSqlMetaData(state, {payload}) {
const newCurrent = state.current;
const newTabs = state.tabs;
if (newCurrent.key == payload.activeKey) {
newCurrent.sqlMetaData = payload.sqlMetaData;
newCurrent.isModified = payload.isModified;
}
for (let i = 0; i < newTabs.panes.length; i++) {
if (newTabs.panes[i].key == payload.activeKey) {
newTabs.panes[i].sqlMetaData = payload.sqlMetaData;
newTabs.panes[i].isModified = payload.isModified;
break;
}
}
return {
...state,
current: newCurrent,
tabs: newTabs,
};
},
saveTabs(state, {payload}) {
let newCurrent = state.current;
for (let i = 0; i < payload.panes.length; i++) {
if (payload.panes[i].key == payload.activeKey) {
newCurrent = payload.panes[i];
}
}
return {
...state,
current: {
...newCurrent,
isModified: false,
},
tabs: {
...payload,
},
};
},
deleteTabByKey(state, {payload}) {
const newTabs = state.tabs;
for (let i = 0; i < newTabs.panes.length; i++) {
if (newTabs.panes[i].key == payload) {
newTabs.panes.splice(i, 1);
break;
}
}
const newCurrent = newTabs.panes[newTabs.panes.length - 1];
if (newTabs.activeKey == payload) {
newTabs.activeKey = newCurrent.key;
}
return {
...state,
current: {
...newCurrent,
},
tabs: {
...newTabs,
},
};
},
closeTabs(state, {payload}) {
const {deleteType, current} = payload
const newTabs = state.tabs;
const firstKey = newTabs.panes[0].key
let newCurrent = newTabs.panes[0]
if (deleteType === 'CLOSE_OTHER') {
const keys = [firstKey, current.key];
newCurrent = {...current}
newTabs.activeKey = current.key
newTabs.panes = newTabs.panes.filter(item => keys.includes(item.key));
} else {
newTabs.panes = [];
newTabs.activeKey = firstKey
}
return {
...state,
current: {
...newCurrent
},
tabs: {
...newTabs,
}
};
},
changeActiveKey(state, {payload}) {
const {tabs} = state;
tabs.activeKey = payload;
let newCurrent = state.current;
for (let i = 0; i < tabs.panes.length; i++) {
if (tabs.panes[i].key == tabs.activeKey) {
newCurrent = tabs.panes[i];
}
}
return {
...state,
current: {
...newCurrent,
},
tabs: {
...tabs,
},
currentPath: newCurrent.path,
};
},
saveTaskData(state, {payload}) {
const newTabs = state.tabs;
for (let i = 0; i < newTabs.panes.length; i++) {
if (newTabs.panes[i].key == newTabs.activeKey) {
newTabs.panes[i].task = payload;
}
}
return {
...state,
tabs: {
...newTabs,
},
};
},
saveSession(state, {payload}) {
return {
...state,
session: [...payload],
};
},
showRightClickMenu(state, {payload}) {
return {
...state,
rightClickMenu: payload,
};
},
refreshCurrentSession(state, {payload}) {
return {
...state,
currentSession: {
...state?.currentSession,
...payload
}
};
},
quitCurrentSession(state) {
return {
...state,
currentSession: {
connectors: [],
}
};
},
saveResult(state, {payload}) {
return {
...state,
result: {
...payload
},
};
},
saveCluster(state, {payload}) {
return {
...state,
cluster: payload,
};
}, saveSessionCluster(state, {payload}) {
return {
...state,
sessionCluster: payload,
};
}, saveClusterConfiguration(state, {payload}) {
return {
...state,
clusterConfiguration: payload,
};
}, saveDataBase(state, {payload}) {
return {
...state,
database: payload,
};
}, saveEnv(state, {payload}) {
return {
...state,
env: payload,
};
},
},
};
export default Model;
...@@ -172,6 +172,7 @@ export type ModelType = { ...@@ -172,6 +172,7 @@ export type ModelType = {
saveMonaco: Reducer<StateType>; saveMonaco: Reducer<StateType>;
saveSqlMetaData: Reducer<StateType>; saveSqlMetaData: Reducer<StateType>;
saveTabs: Reducer<StateType>; saveTabs: Reducer<StateType>;
closeTabs: Reducer<StateType>;
changeActiveKey: Reducer<StateType>; changeActiveKey: Reducer<StateType>;
saveTaskData: Reducer<StateType>; saveTaskData: Reducer<StateType>;
saveSession: Reducer<StateType>; saveSession: Reducer<StateType>;
...@@ -420,6 +421,31 @@ const Model: ModelType = { ...@@ -420,6 +421,31 @@ const Model: ModelType = {
}, },
}; };
}, },
closeTabs(state, {payload}) {
const {deleteType, current} = payload;
const newTabs = state.tabs;
const firstKey = newTabs.panes[0].key;
let newCurrent = newTabs.panes[0];
if (deleteType === 'CLOSE_OTHER') {
const keys = [firstKey, current.key];
newCurrent = {...current};
newTabs.activeKey = current.key;
newTabs.panes = newTabs.panes.filter(item => keys.includes(item.key));
} else {
newTabs.panes = [];
newTabs.activeKey = firstKey
}
return {
...state,
current: {
...newCurrent
},
tabs: {
...newTabs,
}
};
},
changeActiveKey(state, {payload}) { changeActiveKey(state, {payload}) {
const {tabs} = state; const {tabs} = state;
tabs.activeKey = payload; tabs.activeKey = payload;
......
...@@ -550,6 +550,12 @@ export default (): React.ReactNode => { ...@@ -550,6 +550,12 @@ export default (): React.ReactNode => {
<li> <li>
<Link>新增 UDF Java方言的Local模式的在线编写、调试、动态加载</Link> <Link>新增 UDF Java方言的Local模式的在线编写、调试、动态加载</Link>
</li> </li>
<li>
<Link>新增 编辑器选项卡右键关闭其他和关闭所有</Link>
</li>
<li>
<Link>新增 统计选项卡的图标支持</Link>
</li>
</ul> </ul>
</Paragraph> </Paragraph>
</Timeline.Item> </Timeline.Item>
......
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