smallstonesk
4 years ago
11 changed files with 321 additions and 207 deletions
@ -1,44 +1,55 @@ |
|||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1; |
||||
|
height: 100%; |
||||
|
|
||||
.container { |
.pane { |
||||
display: flex; |
display: flex; |
||||
flex-direction: row; |
flex: 1; |
||||
justify-content: space-between; |
} |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
flex: 1; |
||||
|
background-color: #ffffff; |
||||
|
|
||||
|
.cardTitleText { |
||||
|
margin: 10px 24px 0; |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
color: #000000; |
||||
|
} |
||||
|
|
||||
|
.cardBody { |
||||
|
padding: 10px 24px; |
||||
|
height: 100%; |
||||
|
overflow: scroll; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:global { |
||||
|
.ant-tabs { |
||||
height: 100%; |
height: 100%; |
||||
|
} |
||||
|
|
||||
.left{ |
.ant-tabs-content-holder { |
||||
padding:10px; |
height: 100%; |
||||
flex:1; |
} |
||||
border:1px solid #eee; |
|
||||
|
|
||||
.editor{ |
.ant-tabs-content { |
||||
width:100%; |
height: 100%; |
||||
height:600px; |
overflow: scroll; |
||||
} |
} |
||||
|
|
||||
.visualInput{ |
.sc-bdVaJa.cjjWdp { |
||||
width:100%; |
opacity: 1; |
||||
} |
background-color: #efefef; |
||||
} |
|
||||
|
|
||||
.right{ |
&:hover { |
||||
height:100%; |
border-color: transparent; |
||||
flex:1; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
justify-content: space-between; |
|
||||
|
|
||||
.rightTop{ |
|
||||
height:50%; |
|
||||
padding:10px; |
|
||||
flex:1; |
|
||||
border:1px solid #eee; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.rightBottom{ |
|
||||
height:300px; |
|
||||
flex:1; |
|
||||
border:1px solid #eee; |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
} |
||||
|
@ -1,135 +1,91 @@ |
|||||
import React from 'react'; |
import React, { |
||||
import JsonView from 'react-json-view'; |
useState, |
||||
import { Tabs, Form, Input, Button, Space } from 'antd'; |
useCallback, |
||||
const { TabPane } = Tabs; |
} from 'react'; |
||||
import CodeEditor from '../codeEditor'; |
|
||||
import Console from '../console'; |
|
||||
import { inputJson, outputJson } from './json' |
|
||||
import styles from './index.module.less'; |
import styles from './index.module.less'; |
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; |
|
||||
interface MyFormItemProps { |
|
||||
type: string |
|
||||
} |
|
||||
|
|
||||
const MyFormItem: React.FC<MyFormItemProps> = (props) => { |
import Console from '../console'; |
||||
return ( |
import InputPanel from './inputPanel'; |
||||
<> |
import JsonView from 'react-json-view'; |
||||
<h3>{props.type}</h3> |
|
||||
<Form.List name={props.type}> |
// FIXME: https://github.com/tomkp/react-split-pane/issues/541
|
||||
{(fields, { add, remove }) => ( |
// @ts-ignore
|
||||
<> |
import SplitPane from 'react-split-pane/lib/SplitPane'; |
||||
{fields.map(field => ( |
// @ts-ignore
|
||||
<Space key={field.key} align="baseline"> |
import Pane from 'react-split-pane/lib/Pane'; |
||||
<Form.Item |
|
||||
{...field} |
const defaultInput = { |
||||
label="key" |
pipe: {}, |
||||
name={[field.name, 'key']} |
context: {}, |
||||
fieldKey={[field.fieldKey, 'key']} |
payload: {}, |
||||
> |
config: {} |
||||
<Input /> |
}; |
||||
</Form.Item> |
|
||||
<Form.Item |
|
||||
{...field} |
|
||||
label="value" |
|
||||
name={[field.name, 'value']} |
|
||||
fieldKey={[field.fieldKey, 'value']} |
|
||||
> |
|
||||
<Input /> |
|
||||
</Form.Item> |
|
||||
<MinusCircleOutlined onClick={() => remove(field.name)} /> |
|
||||
</Space> |
|
||||
))} |
|
||||
<Form.Item> |
|
||||
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>新增</Button> |
|
||||
</Form.Item> |
|
||||
</> |
|
||||
)} |
|
||||
</Form.List> |
|
||||
</> |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
interface VisualInputProps { |
interface ICardProps { |
||||
onChange: (value: object) => void |
title: string; |
||||
} |
} |
||||
|
|
||||
const VisualInput: React.FC<VisualInputProps> = (props) => { |
const Card: React.FC<ICardProps> = (props) => { |
||||
const [form] = Form.useForm() |
const {title} = props; |
||||
const PAYLOAD = 'payload', PIPE = 'pipe', CONTEXT = 'context', CONFIG = 'config' |
|
||||
const onChange = () => { |
|
||||
props.onChange(form.getFieldsValue()) |
|
||||
} |
|
||||
|
|
||||
return ( |
return ( |
||||
<div className={styles.visualInput}> |
<div className={styles.card}> |
||||
<Form form={form} name="form" autoComplete="off" onValuesChange={onChange}> |
<div className={styles.cardTitleText}>{title}</div> |
||||
<MyFormItem type={PAYLOAD} /> |
<div className={styles.cardBody}> |
||||
<MyFormItem type={PIPE} /> |
{props.children} |
||||
<MyFormItem type={CONTEXT} /> |
</div> |
||||
<MyFormItem type={CONFIG} /> |
|
||||
</Form> |
|
||||
</div> |
</div> |
||||
) |
); |
||||
} |
}; |
||||
interface CodeRunProps { |
|
||||
onChange: (value: object) => void |
interface ICodeRunProps { |
||||
} |
} |
||||
|
|
||||
const CodeRun: React.FC<CodeRunProps> = (props) => { |
const CodeRun: React.FC<ICodeRunProps> = (props) => { |
||||
const onFormChange = (value: any) => { |
|
||||
props.onChange(value) |
const [input, setInput] = useState(defaultInput); |
||||
} |
const [output, setOutput] = useState({}); |
||||
const onJsonChange = (ev: any, newCode: any): void => { |
|
||||
const codeStr = newCode.slice(15) |
const onChangeInput = useCallback((val: any) => { |
||||
const formatCodeStr = codeStr.replace(/ |\/\/.*/g, '').replace(/\s/g, '').replace(/(\w+):/g, '"$1":').replace(/'/g, '"') |
setInput(val); |
||||
try { |
}, []); |
||||
props.onChange(JSON.parse(formatCodeStr)) |
|
||||
} catch (err) { |
|
||||
props.onChange({}) |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
return ( |
return ( |
||||
<div className={styles.container}> |
<div className={styles.container}> |
||||
<div className={styles.left}> |
<SplitPane split={'horizontal'}> |
||||
<Tabs |
<Pane initialSize={'380px'} minSize={'43px'}> |
||||
type="card" |
<SplitPane split={'vertical'}> |
||||
tabBarGutter={0} |
<Pane className={styles.pane} minSize={'360px'} maxSize={'660px'}> |
||||
defaultActiveKey={'basic'} |
<Card title={'输入'}> |
||||
tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }} |
<InputPanel |
||||
> |
data={input} |
||||
<TabPane className={styles.tabPane} tab={'可视化'} key={'visualTab'}> |
onChange={onChangeInput} |
||||
<VisualInput onChange={onFormChange} /> |
/> |
||||
</TabPane> |
</Card> |
||||
<TabPane className={styles.tabPane} tab={'JSON输入'} key={'jsonTab'}> |
</Pane> |
||||
<CodeEditor |
<Pane className={styles.pane}> |
||||
value={inputJson} |
<Card title={'输出'}> |
||||
className={styles.editor} |
<JsonView |
||||
onChange={onJsonChange} |
name={null} |
||||
/> |
collapsed={false} |
||||
</TabPane> |
enableClipboard={false} |
||||
</Tabs> |
displayDataTypes={false} |
||||
</div> |
displayObjectSize={false} |
||||
|
src={{}} |
||||
<div className={styles.right}> |
/> |
||||
<div className={styles.rightTop}> |
</Card> |
||||
<p>输出</p> |
</Pane> |
||||
<JsonView |
</SplitPane> |
||||
name={null} |
</Pane> |
||||
collapsed={false} |
<Pane className={styles.pane} minSize={'150px'}> |
||||
enableClipboard={false} |
<Card title={'控制台'}> |
||||
displayDataTypes={false} |
<Console /> |
||||
displayObjectSize={false} |
</Card> |
||||
src={outputJson} |
</Pane> |
||||
/> |
</SplitPane> |
||||
</div> |
|
||||
<div className={styles.rightBottom}> |
|
||||
<Console /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
</div> |
||||
|
); |
||||
) |
|
||||
} |
} |
||||
|
|
||||
export default CodeRun; |
export default CodeRun; |
||||
|
@ -0,0 +1,24 @@ |
|||||
|
.itemHeader { |
||||
|
margin-bottom: 8px; |
||||
|
|
||||
|
.itemTitleText { |
||||
|
font-size: 15px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.itemDescText { |
||||
|
margin-left: 5px; |
||||
|
font-size: 12px; |
||||
|
color: #999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:global { |
||||
|
.ant-tabs-nav { |
||||
|
margin-bottom: 5px !important; |
||||
|
} |
||||
|
|
||||
|
.ant-form-item { |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
} |
@ -0,0 +1,166 @@ |
|||||
|
import React, { |
||||
|
useRef, |
||||
|
useEffect, |
||||
|
} from 'react'; |
||||
|
|
||||
|
import styles from './index.module.less'; |
||||
|
|
||||
|
import CodeEditor from '../../codeEditor'; |
||||
|
import { Tabs, Form, Input, Button, Space } from 'antd'; |
||||
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; |
||||
|
|
||||
|
const inputItems = [ |
||||
|
{ type: 'pipe', desc: '上一节点返回值' }, |
||||
|
{ type: 'context', desc: '当前逻辑流程公用数据' }, |
||||
|
{ type: 'payload', desc: '当前逻辑流程通用数据' }, |
||||
|
{ type: 'config', desc: '当前节点投放配置' }, |
||||
|
]; |
||||
|
|
||||
|
interface IVisualFormItemProps { |
||||
|
type: string; |
||||
|
desc: string; |
||||
|
} |
||||
|
|
||||
|
const VisualFormItem: React.FC<IVisualFormItemProps> = (props) => { |
||||
|
const { type, desc } = props; |
||||
|
|
||||
|
return ( |
||||
|
<React.Fragment> |
||||
|
<div className={styles.itemHeader}> |
||||
|
<span className={styles.itemTitleText}>{type}</span> |
||||
|
<span className={styles.itemDescText}>{desc}</span> |
||||
|
</div> |
||||
|
<Form.List name={type}> |
||||
|
{(fields, { add, remove }) => ( |
||||
|
<React.Fragment> |
||||
|
{fields.map(field => ( |
||||
|
<Space key={field.key} align={'baseline'}> |
||||
|
<Form.Item |
||||
|
{...field} |
||||
|
label={'key'} |
||||
|
name={[field.name, 'key']} |
||||
|
fieldKey={[field.fieldKey, 'key']} |
||||
|
> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
<Form.Item |
||||
|
{...field} |
||||
|
label={'value'} |
||||
|
name={[field.name, 'value']} |
||||
|
fieldKey={[field.fieldKey, 'value']} |
||||
|
> |
||||
|
<Input /> |
||||
|
</Form.Item> |
||||
|
<MinusCircleOutlined onClick={() => remove(field.name)} /> |
||||
|
</Space> |
||||
|
))} |
||||
|
<Form.Item> |
||||
|
<Button |
||||
|
block={true} |
||||
|
type={'dashed'} |
||||
|
icon={<PlusOutlined />} |
||||
|
onClick={() => add()} |
||||
|
> |
||||
|
新增 |
||||
|
</Button> |
||||
|
</Form.Item> |
||||
|
</React.Fragment> |
||||
|
)} |
||||
|
</Form.List> |
||||
|
</React.Fragment> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
interface IVisualPanelProps { |
||||
|
data: any; |
||||
|
onChange: (value: object) => void |
||||
|
} |
||||
|
|
||||
|
const VisualPanel: React.FC<IVisualPanelProps> = (props) => { |
||||
|
const { data, onChange } = props; |
||||
|
const [form] = Form.useForm(); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
const filedsValue: { [key: string]: any } = {}; |
||||
|
for (const { type } of inputItems) { |
||||
|
const mockValue = data[type] || {}; |
||||
|
filedsValue[type] = Object.keys(mockValue).map(key => ({ key, value: mockValue[key] })); |
||||
|
} |
||||
|
form.setFieldsValue(filedsValue); |
||||
|
}, [data]); |
||||
|
|
||||
|
const onFormChange = () => { |
||||
|
const input: { [key: string]: any } = {}; |
||||
|
const filedsValue = form.getFieldsValue(); |
||||
|
for (const { type } of inputItems) { |
||||
|
const mockValue = filedsValue[type] || []; |
||||
|
input[type] = mockValue.reduce((prev: any, cur: { key: string, value: any }) => { |
||||
|
const { key, value } = cur; |
||||
|
prev[key] = value; |
||||
|
return prev; |
||||
|
}, {}); |
||||
|
} |
||||
|
onChange(input); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.visualInput}> |
||||
|
<Form |
||||
|
form={form} |
||||
|
autoComplete={'on'} |
||||
|
onChange={onFormChange} |
||||
|
> |
||||
|
{inputItems.map(({ type, desc }, index) => ( |
||||
|
<VisualFormItem |
||||
|
key={index} |
||||
|
type={type} |
||||
|
desc={desc} |
||||
|
/> |
||||
|
))} |
||||
|
</Form> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
interface IInputPanelProps { |
||||
|
data: any; |
||||
|
onChange: (data: any) => void; |
||||
|
} |
||||
|
|
||||
|
const InputPanel: React.FC<IInputPanelProps> = (props) => { |
||||
|
const cache: any = useRef({}); |
||||
|
const { data, onChange } = props; |
||||
|
|
||||
|
const onVisualChange = (newForm: any) => { |
||||
|
clearTimeout(cache.current.timer); |
||||
|
cache.current.timer = setTimeout(() => onChange(newForm), 1000); |
||||
|
}; |
||||
|
const onEditorChange = (ev: any, newCode: string | undefined = '') => { |
||||
|
clearTimeout(cache.current.timer); |
||||
|
cache.current.timer = setTimeout(() => { |
||||
|
try { |
||||
|
onChange(JSON.parse(newCode)); |
||||
|
} catch (err) { |
||||
|
console.log(err); |
||||
|
} |
||||
|
}, 1000); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Tabs type={'card'} defaultActiveKey={'visualTab'}> |
||||
|
<Tabs.TabPane className={styles.tabPane} tab={'可视化模式'} key={'visualTab'}> |
||||
|
<VisualPanel data={data} onChange={onVisualChange} /> |
||||
|
</Tabs.TabPane> |
||||
|
<Tabs.TabPane className={styles.tabPane} tab={'源码模式'} key={'jsonTab'}> |
||||
|
<CodeEditor |
||||
|
height={'100%'} |
||||
|
language={'json'} |
||||
|
value={JSON.stringify(data, null, 2)} |
||||
|
onChange={onEditorChange} |
||||
|
/> |
||||
|
</Tabs.TabPane> |
||||
|
</Tabs> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default InputPanel; |
@ -1,39 +0,0 @@ |
|||||
const inputJson = `export default {
|
|
||||
// 上游数据
|
|
||||
pipe: { |
|
||||
success: false, |
|
||||
message: '未登录' |
|
||||
}, |
|
||||
// 公用数据
|
|
||||
context: { |
|
||||
data: { |
|
||||
isLogin: false |
|
||||
} |
|
||||
}, |
|
||||
// 传入载荷
|
|
||||
payload: { |
|
||||
name:'xxx' |
|
||||
}, |
|
||||
// 节点配置项
|
|
||||
config: { |
|
||||
hasLog: true |
|
||||
} |
|
||||
}`;
|
|
||||
|
|
||||
const outputJson = { |
|
||||
pipe: { |
|
||||
success: false, |
|
||||
message: '未登录' |
|
||||
}, |
|
||||
context: { |
|
||||
data: { |
|
||||
isLogin: false, |
|
||||
name: 'xxx' |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export { |
|
||||
inputJson, |
|
||||
outputJson |
|
||||
} |
|
Loading…
Reference in new issue