11 changed files with 321 additions and 207 deletions
			
			
		@ -1,44 +1,55 @@ | 
				
			|||
 | 
				
			|||
.container { | 
				
			|||
  display: flex; | 
				
			|||
    flex-direction: row; | 
				
			|||
    justify-content: space-between; | 
				
			|||
  flex-direction: column; | 
				
			|||
  flex: 1; | 
				
			|||
  height: 100%; | 
				
			|||
 | 
				
			|||
    .left{ | 
				
			|||
      padding:10px; | 
				
			|||
  .pane { | 
				
			|||
    display: flex; | 
				
			|||
    flex: 1; | 
				
			|||
      border:1px solid #eee; | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
      .editor{ | 
				
			|||
        width:100%; | 
				
			|||
        height:600px; | 
				
			|||
.card { | 
				
			|||
  display: flex; | 
				
			|||
  flex-direction: column; | 
				
			|||
  flex: 1; | 
				
			|||
  background-color: #ffffff; | 
				
			|||
 | 
				
			|||
  .cardTitleText { | 
				
			|||
    margin: 10px 24px 0; | 
				
			|||
    font-size: 16px; | 
				
			|||
    font-weight: 500; | 
				
			|||
    color: #000000; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
      .visualInput{ | 
				
			|||
        width:100%; | 
				
			|||
  .cardBody { | 
				
			|||
    padding: 10px 24px; | 
				
			|||
    height: 100%; | 
				
			|||
    overflow: scroll; | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
    .right{ | 
				
			|||
:global { | 
				
			|||
  .ant-tabs { | 
				
			|||
    height: 100%; | 
				
			|||
      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; | 
				
			|||
  .ant-tabs-content-holder { | 
				
			|||
    height: 100%; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
      .rightBottom{ | 
				
			|||
        height:300px; | 
				
			|||
        flex:1; | 
				
			|||
        border:1px solid #eee; | 
				
			|||
  .ant-tabs-content { | 
				
			|||
    height: 100%; | 
				
			|||
    overflow: scroll; | 
				
			|||
  } | 
				
			|||
 | 
				
			|||
  .sc-bdVaJa.cjjWdp { | 
				
			|||
    opacity: 1; | 
				
			|||
    background-color: #efefef; | 
				
			|||
 | 
				
			|||
    &:hover { | 
				
			|||
      border-color: transparent; | 
				
			|||
    } | 
				
			|||
  } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
@ -1,135 +1,91 @@ | 
				
			|||
import React from 'react'; | 
				
			|||
import JsonView from 'react-json-view'; | 
				
			|||
import { Tabs, Form, Input, Button, Space } from 'antd'; | 
				
			|||
const { TabPane } = Tabs; | 
				
			|||
import CodeEditor from '../codeEditor'; | 
				
			|||
import Console from '../console'; | 
				
			|||
import { inputJson, outputJson } from './json' | 
				
			|||
import React, { | 
				
			|||
  useState, | 
				
			|||
  useCallback, | 
				
			|||
} from 'react'; | 
				
			|||
 | 
				
			|||
import styles from './index.module.less'; | 
				
			|||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | 
				
			|||
interface MyFormItemProps { | 
				
			|||
  type: string | 
				
			|||
} | 
				
			|||
 | 
				
			|||
const MyFormItem: React.FC<MyFormItemProps> = (props) => { | 
				
			|||
  return ( | 
				
			|||
    <> | 
				
			|||
      <h3>{props.type}</h3> | 
				
			|||
      <Form.List name={props.type}> | 
				
			|||
        {(fields, { add, remove }) => ( | 
				
			|||
          <> | 
				
			|||
            {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 type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>新增</Button> | 
				
			|||
            </Form.Item> | 
				
			|||
          </> | 
				
			|||
        )} | 
				
			|||
      </Form.List> | 
				
			|||
    </> | 
				
			|||
  ) | 
				
			|||
} | 
				
			|||
import Console from '../console'; | 
				
			|||
import InputPanel from './inputPanel'; | 
				
			|||
import JsonView from 'react-json-view'; | 
				
			|||
 | 
				
			|||
interface VisualInputProps { | 
				
			|||
  onChange: (value: object) => void | 
				
			|||
} | 
				
			|||
// FIXME: https://github.com/tomkp/react-split-pane/issues/541
 | 
				
			|||
// @ts-ignore
 | 
				
			|||
import SplitPane from 'react-split-pane/lib/SplitPane'; | 
				
			|||
// @ts-ignore
 | 
				
			|||
import Pane from 'react-split-pane/lib/Pane'; | 
				
			|||
 | 
				
			|||
const defaultInput = { | 
				
			|||
  pipe: {}, | 
				
			|||
  context: {}, | 
				
			|||
  payload: {}, | 
				
			|||
  config: {} | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
const VisualInput: React.FC<VisualInputProps> = (props) => { | 
				
			|||
  const [form] = Form.useForm() | 
				
			|||
  const PAYLOAD = 'payload', PIPE = 'pipe', CONTEXT = 'context', CONFIG = 'config' | 
				
			|||
  const onChange = () => { | 
				
			|||
    props.onChange(form.getFieldsValue()) | 
				
			|||
interface ICardProps { | 
				
			|||
  title: string; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
const Card: React.FC<ICardProps> = (props) => { | 
				
			|||
  const {title} = props; | 
				
			|||
 | 
				
			|||
  return ( | 
				
			|||
    <div className={styles.visualInput}> | 
				
			|||
      <Form form={form} name="form" autoComplete="off" onValuesChange={onChange}> | 
				
			|||
        <MyFormItem type={PAYLOAD} /> | 
				
			|||
        <MyFormItem type={PIPE} /> | 
				
			|||
        <MyFormItem type={CONTEXT} /> | 
				
			|||
        <MyFormItem type={CONFIG} /> | 
				
			|||
      </Form> | 
				
			|||
    <div className={styles.card}> | 
				
			|||
      <div className={styles.cardTitleText}>{title}</div> | 
				
			|||
      <div className={styles.cardBody}> | 
				
			|||
        {props.children} | 
				
			|||
      </div> | 
				
			|||
  ) | 
				
			|||
} | 
				
			|||
interface CodeRunProps { | 
				
			|||
  onChange: (value: object) => void | 
				
			|||
} | 
				
			|||
    </div> | 
				
			|||
  ); | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
const CodeRun: React.FC<CodeRunProps> = (props) => { | 
				
			|||
  const onFormChange = (value: any) => { | 
				
			|||
    props.onChange(value) | 
				
			|||
interface ICodeRunProps { | 
				
			|||
} | 
				
			|||
  const onJsonChange = (ev: any, newCode: any): void => { | 
				
			|||
    const codeStr = newCode.slice(15) | 
				
			|||
    const formatCodeStr = codeStr.replace(/ |\/\/.*/g, '').replace(/\s/g, '').replace(/(\w+):/g, '"$1":').replace(/'/g, '"') | 
				
			|||
    try { | 
				
			|||
      props.onChange(JSON.parse(formatCodeStr)) | 
				
			|||
    } catch (err) { | 
				
			|||
      props.onChange({}) | 
				
			|||
    } | 
				
			|||
  }; | 
				
			|||
 | 
				
			|||
const CodeRun: React.FC<ICodeRunProps> = (props) => { | 
				
			|||
   | 
				
			|||
  const [input, setInput] = useState(defaultInput); | 
				
			|||
  const [output, setOutput] = useState({}); | 
				
			|||
 | 
				
			|||
  const onChangeInput = useCallback((val: any) => { | 
				
			|||
    setInput(val); | 
				
			|||
  }, []); | 
				
			|||
 | 
				
			|||
  return ( | 
				
			|||
    <div className={styles.container}> | 
				
			|||
      <div className={styles.left}> | 
				
			|||
        <Tabs | 
				
			|||
          type="card" | 
				
			|||
          tabBarGutter={0} | 
				
			|||
          defaultActiveKey={'basic'} | 
				
			|||
          tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }} | 
				
			|||
        > | 
				
			|||
          <TabPane className={styles.tabPane} tab={'可视化'} key={'visualTab'}> | 
				
			|||
            <VisualInput onChange={onFormChange} /> | 
				
			|||
          </TabPane> | 
				
			|||
          <TabPane className={styles.tabPane} tab={'JSON输入'} key={'jsonTab'}> | 
				
			|||
            <CodeEditor | 
				
			|||
              value={inputJson} | 
				
			|||
              className={styles.editor} | 
				
			|||
              onChange={onJsonChange} | 
				
			|||
      <SplitPane split={'horizontal'}> | 
				
			|||
        <Pane initialSize={'380px'} minSize={'43px'}> | 
				
			|||
          <SplitPane split={'vertical'}> | 
				
			|||
            <Pane className={styles.pane} minSize={'360px'} maxSize={'660px'}> | 
				
			|||
              <Card title={'输入'}> | 
				
			|||
                <InputPanel | 
				
			|||
                  data={input} | 
				
			|||
                  onChange={onChangeInput} | 
				
			|||
                /> | 
				
			|||
          </TabPane> | 
				
			|||
        </Tabs> | 
				
			|||
      </div> | 
				
			|||
 | 
				
			|||
      <div className={styles.right}> | 
				
			|||
        <div className={styles.rightTop}> | 
				
			|||
          <p>输出</p> | 
				
			|||
              </Card> | 
				
			|||
            </Pane> | 
				
			|||
            <Pane className={styles.pane}> | 
				
			|||
              <Card title={'输出'}> | 
				
			|||
                <JsonView | 
				
			|||
                  name={null} | 
				
			|||
                  collapsed={false} | 
				
			|||
                  enableClipboard={false} | 
				
			|||
                  displayDataTypes={false} | 
				
			|||
                  displayObjectSize={false} | 
				
			|||
            src={outputJson} | 
				
			|||
                  src={{}} | 
				
			|||
                /> | 
				
			|||
        </div> | 
				
			|||
        <div className={styles.rightBottom}> | 
				
			|||
              </Card> | 
				
			|||
            </Pane> | 
				
			|||
          </SplitPane> | 
				
			|||
        </Pane> | 
				
			|||
        <Pane className={styles.pane} minSize={'150px'}> | 
				
			|||
          <Card title={'控制台'}> | 
				
			|||
            <Console /> | 
				
			|||
          </Card> | 
				
			|||
        </Pane> | 
				
			|||
      </SplitPane> | 
				
			|||
    </div> | 
				
			|||
      </div> | 
				
			|||
    </div> | 
				
			|||
 | 
				
			|||
  ) | 
				
			|||
  ); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
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