| @@ -18,7 +18,7 @@ const Settings: ProLayoutProps & { | |||||
| colorWeak: false, | colorWeak: false, | ||||
| title: '复杂智能软件', | title: '复杂智能软件', | ||||
| pwa: true, | pwa: true, | ||||
| logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', | |||||
| logo: '/assets/images/left-top-logo.png', | |||||
| iconfontUrl: '', | iconfontUrl: '', | ||||
| token: { | token: { | ||||
| // 参见ts声明,demo 见文档,通过token 修改样式 | // 参见ts声明,demo 见文档,通过token 修改样式 | ||||
| @@ -31,7 +31,38 @@ body { | |||||
| -webkit-font-smoothing: antialiased; | -webkit-font-smoothing: antialiased; | ||||
| -moz-osx-font-smoothing: grayscale; | -moz-osx-font-smoothing: grayscale; | ||||
| } | } | ||||
| .ant-pro-layout .ant-pro-layout-content{ | |||||
| padding: 10px; | |||||
| } | |||||
| .ant-pro-layout .ant-pro-layout-bg-list{ | |||||
| background:#f9fafb; | |||||
| } | |||||
| .ant-table-wrapper .ant-table-thead >tr>th{ | |||||
| background-color: #fff; | |||||
| } | |||||
| .ant-table-wrapper .ant-table-thead >tr>td{ | |||||
| background-color: #fff; | |||||
| } | |||||
| .ant-menu-light .ant-menu-item-selected{ | |||||
| background:rgba(197, 232, 255, 0.8)!important; | |||||
| } | |||||
| .ant-pro-base-menu-inline{ | |||||
| // height: 87vh; | |||||
| background:#f2f5f7; | |||||
| border-radius:0px 20px 20px 0px; | |||||
| } | |||||
| .ant-pro-layout .ant-pro-layout-content{ | |||||
| background-color: #fff; | |||||
| } | |||||
| .ant-pro-global-header-logo img{ | |||||
| height: 21px; | |||||
| } | |||||
| .ant-pro-layout .ant-layout-sider.ant-pro-sider{ | |||||
| height: 87vh; | |||||
| } | |||||
| .ant-pro-layout .ant-pro-layout-container{ | |||||
| height: 98vh; | |||||
| } | |||||
| ul, | ul, | ||||
| ol { | ol { | ||||
| list-style: none; | list-style: none; | ||||
| @@ -53,3 +84,5 @@ ol { | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -51,7 +51,7 @@ const ExperimentText = React.FC = () => { | |||||
| return { | return { | ||||
| display: 'flex', | display: 'flex', | ||||
| backgroundColor:'#fff', | backgroundColor:'#fff', | ||||
| height:'81vh' | |||||
| height:'98vh' | |||||
| }; | }; | ||||
| }); | }); | ||||
| const graphStyle = useEmotionCss(() => { | const graphStyle = useEmotionCss(() => { | ||||
| @@ -158,98 +158,78 @@ const ExperimentText = React.FC = () => { | |||||
| },[]) | },[]) | ||||
| const initGraph=()=>{ | const initGraph=()=>{ | ||||
| G6.registerNode( | G6.registerNode( | ||||
| 'rect-node', | |||||
| { | |||||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||||
| getAnchorPoints(cfg) { | |||||
| return ( | |||||
| cfg.anchorPoints || [ | |||||
| // 上下各3,左右各1 | |||||
| [0.1, 0.05], | |||||
| [0.5, 0.05], | |||||
| [0.9, 0.05], | |||||
| [0, 0.5], | |||||
| [1, 0.5], | |||||
| [0.1, 1], | |||||
| [0.5, 1], | |||||
| [0.9, 1], | |||||
| // 四边中间 | |||||
| // [0.5, 0.05], | |||||
| // [0, 0.5], | |||||
| // [1, 0.5], | |||||
| // [0.5, 1], | |||||
| // 四个角落 | |||||
| // [0.05, 0.05], | |||||
| // [0.9, 0.05], | |||||
| // [0.05, 1], | |||||
| // [0.9, 1], | |||||
| ] | |||||
| ); | |||||
| }, | |||||
| afterDraw(cfg, group) { | |||||
| // console.log(group, cfg, 12312); | |||||
| const image = group.addShape('image', { | |||||
| 'rect-node', | |||||
| { | |||||
| // draw anchor-point circles according to the anchorPoints in afterDraw | |||||
| getAnchorPoints(cfg) { | |||||
| return ( | |||||
| cfg.anchorPoints || [ | |||||
| // 上下各3,左右各1 | |||||
| [0.5, 0], | |||||
| [0.5, 1], | |||||
| ] | |||||
| ); | |||||
| }, | |||||
| afterDraw(cfg, group) { | |||||
| // console.log(group, cfg, 12312); | |||||
| const image = group.addShape('image', { | |||||
| attrs: { | |||||
| x: -45, | |||||
| y: -10, | |||||
| width: 20, | |||||
| height: 20, | |||||
| img: cfg.img, | |||||
| cursor: 'pointer', | |||||
| }, | |||||
| draggable: true, | |||||
| }); | |||||
| // if (cfg.label) { | |||||
| // group.addShape('text', { | |||||
| // attrs: { | |||||
| // x: 0, | |||||
| // y: cfg.height / 2 - 5, | |||||
| // textAlign: 'center', | |||||
| // textBaseline: 'middle', | |||||
| // text: cfg.label, | |||||
| // fill: '#fff', | |||||
| // }, | |||||
| // draggable: true, | |||||
| // }); | |||||
| // } | |||||
| const bbox = group.getBBox(); | |||||
| const anchorPoints = this.getAnchorPoints(cfg); | |||||
| // console.log(anchorPoints); | |||||
| anchorPoints.forEach((anchorPos, i) => { | |||||
| group.addShape('circle', { | |||||
| attrs: { | attrs: { | ||||
| x: -25, | |||||
| y: -13, | |||||
| width: 23, | |||||
| height: 21, | |||||
| img: cfg.img, | |||||
| cursor: 'pointer', | |||||
| r: 3, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | |||||
| y: bbox.y + bbox.height * anchorPos[1], | |||||
| fill: '#fff', | |||||
| stroke: '#a4a4a5', | |||||
| }, | }, | ||||
| draggable: true, | |||||
| }); | |||||
| // if (cfg.label) { | |||||
| // group.addShape('text', { | |||||
| // attrs: { | |||||
| // x: 0, | |||||
| // y: cfg.height / 2 - 5, | |||||
| // textAlign: 'center', | |||||
| // textBaseline: 'middle', | |||||
| // text: cfg.label, | |||||
| // fill: '#fff', | |||||
| // }, | |||||
| // draggable: true, | |||||
| // }); | |||||
| // } | |||||
| const bbox = group.getBBox(); | |||||
| const anchorPoints = this.getAnchorPoints(cfg); | |||||
| // console.log(anchorPoints); | |||||
| anchorPoints.forEach((anchorPos, i) => { | |||||
| group.addShape('circle', { | |||||
| attrs: { | |||||
| r: 5, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | |||||
| y: bbox.y + bbox.height * anchorPos[1], | |||||
| fill: '#000', | |||||
| stroke: '#000', | |||||
| }, | |||||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||||
| links: 0, // cache the number of edges connected to this shape | |||||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||||
| draggable: true, // allow to catch the drag events on this shape | |||||
| }); | |||||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | |||||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | |||||
| links: 0, // cache the number of edges connected to this shape | |||||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | |||||
| }); | }); | ||||
| return image; | |||||
| }, | |||||
| // response the state changes and show/hide the link-point circles | |||||
| setState(name, value, item) { | |||||
| // 默认显示全部锚点,防止过宽导致锚点无法被选中 | |||||
| // if (name === 'showAnchors') { | |||||
| }); | |||||
| return image; | |||||
| }, | |||||
| // response the state changes and show/hide the link-point circles | |||||
| setState(name, value, item) { | |||||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | ||||
| anchorPoints.forEach(point => { | anchorPoints.forEach(point => { | ||||
| // if (value) point.show(); | |||||
| // else point.hide(); | |||||
| point.show(); | |||||
| }); | |||||
| // } | |||||
| }, | |||||
| if (value || point.get('links') > 0) point.show() | |||||
| else point.hide() | |||||
| }) | |||||
| // } | |||||
| }, | }, | ||||
| 'rect' | |||||
| ); | |||||
| }, | |||||
| 'rect' | |||||
| ); | |||||
| console.log(graphRef,'graphRef'); | console.log(graphRef,'graphRef'); | ||||
| graph = new G6.Graph({ | graph = new G6.Graph({ | ||||
| container: graphRef.current, | container: graphRef.current, | ||||
| @@ -258,13 +238,27 @@ const ExperimentText = React.FC = () => { | |||||
| height: graphRef.current.clientHeight||760, | height: graphRef.current.clientHeight||760, | ||||
| animate: false, | animate: false, | ||||
| groupByTypes: false, | groupByTypes: false, | ||||
| plugins: [], | |||||
| fitView:true, | |||||
| enabledStack: true, | enabledStack: true, | ||||
| modes: { | modes: { | ||||
| default: [ | default: [ | ||||
| // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles | |||||
| { | |||||
| type: 'drag-node', | |||||
| shouldBegin: e => { | |||||
| if (e.target.get('name') === 'anchor-point') return false; | |||||
| return true; | |||||
| }, | |||||
| // shouldEnd: e => { | |||||
| // console.log(e); | |||||
| // return false; | |||||
| // }, | |||||
| }, | |||||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | |||||
| 'drag-canvas', | 'drag-canvas', | ||||
| 'zoom-canvas', | 'zoom-canvas', | ||||
| // 'brush-select', | |||||
| 'drag-combo', | |||||
| ], | ], | ||||
| altSelect: [ | altSelect: [ | ||||
| { | { | ||||
| @@ -277,49 +271,53 @@ const ExperimentText = React.FC = () => { | |||||
| defaultNode: { | defaultNode: { | ||||
| type: 'rect-node', | type: 'rect-node', | ||||
| size: 70, | |||||
| size: [110,36], | |||||
| labelCfg: { | labelCfg: { | ||||
| style: { | style: { | ||||
| fill: '#000', | fill: '#000', | ||||
| fontSize: 12, | |||||
| fontSize: 10, | |||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| x: 0, | |||||
| x: -20, | |||||
| y: 0, | y: 0, | ||||
| textAlign: 'left', | textAlign: 'left', | ||||
| textBaseline: 'middle', | textBaseline: 'middle', | ||||
| }, | }, | ||||
| }, | }, | ||||
| style: { | style: { | ||||
| fill: 'transparent', | |||||
| stroke: 'transparent', | |||||
| fill: '#fff', | |||||
| stroke: '#fff', | |||||
| radius:10, | |||||
| lineWidth:0.5 | |||||
| }, | |||||
| }, | |||||
| nodeStateStyles: { | |||||
| nodeSelected: { | |||||
| fill: 'red', | |||||
| shadowColor: 'red', | |||||
| stroke: 'red', | |||||
| 'text-shape': { | |||||
| fill: 'red', | |||||
| stroke: 'red', | |||||
| }, | |||||
| }, | }, | ||||
| }, | }, | ||||
| // nodeStateStyles: { | |||||
| // nodeSelected: { | |||||
| // fill: 'red', | |||||
| // shadowColor: 'red', | |||||
| // stroke: 'red', | |||||
| // 'text-shape': { | |||||
| // fill: 'red', | |||||
| // stroke: 'red', | |||||
| // }, | |||||
| // }, | |||||
| // }, | |||||
| defaultEdge: { | defaultEdge: { | ||||
| // type: 'quadratic', | // type: 'quadratic', | ||||
| type: 'polyline', | |||||
| type: 'cubic-vertical', | |||||
| style: { | style: { | ||||
| endArrow: { | |||||
| path: G6.Arrow.triangle(), | |||||
| endArrow: { // 设置终点箭头 | |||||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||||
| d: 4.5, | |||||
| fill:'#a2a6b5' | |||||
| }, | }, | ||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| endArrow: true, | |||||
| lineWidth: 1, | lineWidth: 1, | ||||
| opacity: 1, | opacity: 1, | ||||
| stroke: '#a2a6b5', | stroke: '#a2a6b5', | ||||
| radius: 10, | |||||
| radius: 1, | |||||
| }, | }, | ||||
| nodeStateStyle: { | nodeStateStyle: { | ||||
| hover: { | hover: { | ||||
| @@ -346,7 +344,7 @@ const ExperimentText = React.FC = () => { | |||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| }, | }, | ||||
| }, | }, | ||||
| linkCenter: true, | |||||
| // linkCenter: true, | |||||
| fitView: true, | fitView: true, | ||||
| fitViewPadding: [60, 60, 60, 80], | fitViewPadding: [60, 60, 60, 80], | ||||
| }); | }); | ||||
| @@ -1,6 +1,6 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | import React ,{ useState,useEffect,useRef }from 'react'; | ||||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | import { Space, Table, Tag,Button,Modal, Form, Input ,message, Select,} from 'antd'; | ||||
| import { PlusOutlined, EditOutlined ,PlayCircleOutlined,DeleteOutlined,FieldTimeOutlined} from '@ant-design/icons'; | |||||
| import { PlusOutlined,PlusCircleOutlined, EditOutlined ,PlayCircleOutlined,DeleteOutlined,FieldTimeOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,} from '@/services/pipeline/index.js' | import {getWorkflow,} from '@/services/pipeline/index.js' | ||||
| import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId,deleteExperimentById,deleteQueryByExperimentInsId,putQueryByExperimentInsId} from '@/services/experiment/index.js' | import {getExperiment,runExperiments,getExperimentById,postExperiment,putExperiment,getQueryByExperimentId,deleteExperimentById,deleteQueryByExperimentInsId,putQueryByExperimentInsId} from '@/services/experiment/index.js' | ||||
| import Styles from './index.less' | import Styles from './index.less' | ||||
| @@ -49,6 +49,7 @@ const Experiment = React.FC = () => { | |||||
| size:10000, | size:10000, | ||||
| name:null | name:null | ||||
| }); | }); | ||||
| const [disableFlag,setDisableFlag]=useState(false) | |||||
| const timers=(time)=>{ | const timers=(time)=>{ | ||||
| let timer=new Date(time) | let timer=new Date(time) | ||||
| let hours = timer.getHours(); //转换成时 | let hours = timer.getHours(); //转换成时 | ||||
| @@ -96,6 +97,7 @@ const Experiment = React.FC = () => { | |||||
| } | } | ||||
| const showModal = () => { | const showModal = () => { | ||||
| setDialogTitle('新建实验') | setDialogTitle('新建实验') | ||||
| setDisableFlag(false) | |||||
| console.log(workflowList); | console.log(workflowList); | ||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| @@ -103,6 +105,7 @@ const Experiment = React.FC = () => { | |||||
| getExperimentById(id).then(ret=>{ | getExperimentById(id).then(ret=>{ | ||||
| if(ret.code==200){ | if(ret.code==200){ | ||||
| form.setFieldsValue({...ret.data}) | form.setFieldsValue({...ret.data}) | ||||
| setDisableFlag(true) | |||||
| setFormId(ret.data.id) | setFormId(ret.data.id) | ||||
| setDialogTitle('编辑实验') | setDialogTitle('编辑实验') | ||||
| getWorkflowList() | getWorkflowList() | ||||
| @@ -262,6 +265,7 @@ const Experiment = React.FC = () => { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| key: 'action', | key: 'action', | ||||
| width:300, | |||||
| render: (_, record) => ( | render: (_, record) => ( | ||||
| <Space size="small"> | <Space size="small"> | ||||
| <Button | <Button | ||||
| @@ -291,6 +295,7 @@ const Experiment = React.FC = () => { | |||||
| size="small" | size="small" | ||||
| danger | danger | ||||
| key="batchRemove" | key="batchRemove" | ||||
| style={{color:'#f98e1b'}} | |||||
| icon = {< DeleteOutlined />} | icon = {< DeleteOutlined />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| Modal.confirm({ | Modal.confirm({ | ||||
| @@ -298,6 +303,7 @@ const Experiment = React.FC = () => { | |||||
| content: '确定删除该条实验吗?', | content: '确定删除该条实验吗?', | ||||
| okText: '确认', | okText: '确认', | ||||
| cancelText: '取消', | cancelText: '取消', | ||||
| onOk: () => { | onOk: () => { | ||||
| console.log(record); | console.log(record); | ||||
| deleteExperimentById(record.id).then(ret=>{ | deleteExperimentById(record.id).then(ret=>{ | ||||
| @@ -326,10 +332,15 @@ const Experiment = React.FC = () => { | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| return (<div> | return (<div> | ||||
| <div > | |||||
| {/* <div > | |||||
| <Button type="primary" onClick={showModal} icon = {< PlusOutlined />}> | <Button type="primary" onClick={showModal} icon = {< PlusOutlined />}> | ||||
| 新建实验 | 新建实验 | ||||
| </Button> | </Button> | ||||
| </div> */} | |||||
| <div className={Styles.pipelineTopBox}> | |||||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||||
| 新建实验 | |||||
| </Button> | |||||
| </div> | </div> | ||||
| <Table columns={columns} dataSource={experimentList} pagination={paginationProps} expandable={{ | <Table columns={columns} dataSource={experimentList} pagination={paginationProps} expandable={{ | ||||
| expandedRowRender: (record) => ( | expandedRowRender: (record) => ( | ||||
| @@ -340,7 +351,7 @@ const Experiment = React.FC = () => { | |||||
| <div style={{width:'200px'}}>状态</div> | <div style={{width:'200px'}}>状态</div> | ||||
| <div style={{width:'300px'}}>运行时长</div> | <div style={{width:'300px'}}>运行时长</div> | ||||
| <div style={{width:'300px'}}>开始时间</div> | <div style={{width:'300px'}}>开始时间</div> | ||||
| <div style={{width:'300px'}}>操作</div> | |||||
| <div style={{width:'200px'}}>操作</div> | |||||
| </div>:''} | </div>:''} | ||||
| {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | {experimentInList&&experimentInList.length>0?experimentInList.map((item,index)=>( | ||||
| @@ -349,7 +360,7 @@ const Experiment = React.FC = () => { | |||||
| <div className={Styles.statusBox} style={{width:'200px'}}><img style={{width:'17px',marginRight:'7px'}} src={statusImgObj[item.status]}/> <span style={{color:statusColorObj[item.status]}} className={Styles.statusIcon}>{statusObj[item.status]}</span></div> | <div className={Styles.statusBox} style={{width:'200px'}}><img style={{width:'17px',marginRight:'7px'}} src={statusImgObj[item.status]}/> <span style={{color:statusColorObj[item.status]}} className={Styles.statusIcon}>{statusObj[item.status]}</span></div> | ||||
| <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.create_time).getTime()):timers(new Date().getTime()-new Date(item.create_time).getTime())}</div> | <div style={{width:'300px'}}>{item.finish_time?timers(new Date(item.finish_time).getTime()-new Date(item.create_time).getTime()):timers(new Date().getTime()-new Date(item.create_time).getTime())}</div> | ||||
| <div style={{width:'300px'}}>{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | <div style={{width:'300px'}}>{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}</div> | ||||
| <div style={{width:'300px'}}> | |||||
| <div style={{width:'200px'}}> | |||||
| <Button | <Button | ||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| @@ -375,6 +386,7 @@ const Experiment = React.FC = () => { | |||||
| size="small" | size="small" | ||||
| danger | danger | ||||
| key="batchRemove" | key="batchRemove" | ||||
| style={{color:'#f98e1b'}} | |||||
| disabled={item.status=='Running'||item.status=='Pending'} | disabled={item.status=='Running'||item.status=='Pending'} | ||||
| icon = {< DeleteOutlined />} | icon = {< DeleteOutlined />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| @@ -409,7 +421,9 @@ const Experiment = React.FC = () => { | |||||
| expandedRowKeys:[expandedRowKeys], | expandedRowKeys:[expandedRowKeys], | ||||
| rowExpandable: (record) =>true, | rowExpandable: (record) =>true, | ||||
| }}/> | }}/> | ||||
| <Modal title={dialogTitle} open={isModalOpen} okButtonProps={{ | |||||
| <Modal className={Styles.modal} title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||||
| </div>} open={isModalOpen} okButtonProps={{ | |||||
| htmlType: 'submit', | htmlType: 'submit', | ||||
| form: 'form', | form: 'form', | ||||
| }} onCancel={handleCancel}> | }} onCancel={handleCancel}> | ||||
| @@ -417,15 +431,6 @@ const Experiment = React.FC = () => { | |||||
| name="form" | name="form" | ||||
| form={form} | form={form} | ||||
| layout="vertical" | layout="vertical" | ||||
| labelCol={{ | |||||
| span: 8, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| initialValues={{ | initialValues={{ | ||||
| remember: true, | remember: true, | ||||
| }} | }} | ||||
| @@ -460,6 +465,7 @@ const Experiment = React.FC = () => { | |||||
| <Form.Item | <Form.Item | ||||
| label="选择流水线" | label="选择流水线" | ||||
| name="workflow_id" | name="workflow_id" | ||||
| rules={[ | rules={[ | ||||
| // { | // { | ||||
| // required: true, | // required: true, | ||||
| @@ -467,8 +473,8 @@ const Experiment = React.FC = () => { | |||||
| // }, | // }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Select> | |||||
| {workflowList&&workflowList.length>0?workflowList.map(item=>{return <Select.Option value={item.id}>{item.name}</Select.Option>}):''} | |||||
| <Select disabled={disableFlag}> | |||||
| {workflowList&&workflowList.length>0?workflowList.map(item=>{return <Select.Option value={item.id}>{item.name}</Select.Option>}):''} | |||||
| {/* <Select.Option value="demo">Demo</Select.Option> */} | {/* <Select.Option value="demo">Demo</Select.Option> */} | ||||
| </Select> | </Select> | ||||
| </Form.Item> | </Form.Item> | ||||
| @@ -7,22 +7,34 @@ | |||||
| height: 49px; | height: 49px; | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| } | |||||
| .pipelineTopBox{ | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: flex-end; | |||||
| padding-right: 30px; | |||||
| width: 100%; | |||||
| height: 49px; | |||||
| background-size: 100% 100%; | |||||
| background-image: url(/assets/images/pipeline-back.png); | |||||
| margin-bottom: 10px; | |||||
| } | |||||
| .plusButton{ | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border:1px solid; | |||||
| border-color:rgba(22, 100, 255, 0.11); | |||||
| border-radius:4px; | |||||
| color:#1d1d20; | |||||
| font-size:14px; | |||||
| font-family: 'Alibaba'; | |||||
| } | |||||
| .plusButton:hover{ | |||||
| background:rgba(22, 100, 255, 0.06)!important; | |||||
| border:1px solid!important; | |||||
| border-color:rgba(22, 100, 255, 0.11)!important; | |||||
| color:#1d1d20!important; | |||||
| } | } | ||||
| // .plusButton{ | |||||
| // background:rgba(22, 100, 255, 0.06); | |||||
| // border:1px solid; | |||||
| // border-color:rgba(22, 100, 255, 0.11); | |||||
| // border-radius:4px; | |||||
| // color:#1d1d20; | |||||
| // font-size:15px; | |||||
| // font-family: 'Alibaba'; | |||||
| // } | |||||
| // .plusButton:hover{ | |||||
| // background:rgba(22, 100, 255, 0.06); | |||||
| // border:1px solid; | |||||
| // border-color:rgba(22, 100, 255, 0.11); | |||||
| // border-radius:4px; | |||||
| // } | |||||
| .tableExpandBox{ | .tableExpandBox{ | ||||
| width: 100%; | width: 100%; | ||||
| display: flex; | display: flex; | ||||
| @@ -44,4 +56,45 @@ | |||||
| } | } | ||||
| .statusBox:hover .statusIcon{ | .statusBox:hover .statusIcon{ | ||||
| visibility: visible; | visibility: visible; | ||||
| } | |||||
| .modal { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| background:linear-gradient(180deg,#cfdfff 0%,#d4e2ff 9.77%,#ffffff 40%,#ffffff 100%); | |||||
| border-radius:21px; | |||||
| padding: 20px 67px; | |||||
| width: 825px; | |||||
| } | |||||
| .ant-modal-header{ | |||||
| background-color: transparent; | |||||
| margin: 20px 0; | |||||
| } | |||||
| .ant-input{ | |||||
| border-color:#e6e6e6; | |||||
| height: 40px; | |||||
| } | |||||
| .ant-select-single{ | |||||
| height: 40px; | |||||
| } | |||||
| .ant-form-item .ant-form-item-label >label{ | |||||
| color:rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| .ant-modal-footer{ | |||||
| margin: 40px 0 30px 0; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| } | |||||
| .ant-btn{ | |||||
| width:123px; | |||||
| height:40px; | |||||
| font-size:18px; | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border-radius:10px; | |||||
| } | |||||
| .ant-btn-primary{ | |||||
| background:#1664ff; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -9,6 +9,7 @@ | |||||
| width: 100%; | width: 100%; | ||||
| height:43px; | height:43px; | ||||
| background:#f8fbff; | background:#f8fbff; | ||||
| color:#1d1d20; | color:#1d1d20; | ||||
| font-size:15px; | font-size:15px; | ||||
| font-family: 'Alibaba'; | font-family: 'Alibaba'; | ||||
| @@ -22,6 +23,7 @@ | |||||
| .buttonList{ | .buttonList{ | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| justify-content: end; | |||||
| padding: 0 30px; | padding: 0 30px; | ||||
| width: 100%; | width: 100%; | ||||
| height:45px; | height:45px; | ||||
| @@ -17,11 +17,12 @@ const editPipeline = React.FC = () => { | |||||
| let contextMenu={} | let contextMenu={} | ||||
| const locationParams =useParams () //新版本获取路由参数接口 | const locationParams =useParams () //新版本获取路由参数接口 | ||||
| let graph=null | let graph=null | ||||
| let sourceAnchorIdx, targetAnchorIdx; | |||||
| const pipelineContainer = useEmotionCss(() => { | const pipelineContainer = useEmotionCss(() => { | ||||
| return { | return { | ||||
| display: 'flex', | display: 'flex', | ||||
| backgroundColor:'#fff', | backgroundColor:'#fff', | ||||
| height:'81vh' | |||||
| height:'98vh' | |||||
| }; | }; | ||||
| }); | }); | ||||
| const rightmenu= useEmotionCss(() => { | const rightmenu= useEmotionCss(() => { | ||||
| @@ -46,7 +47,8 @@ const editPipeline = React.FC = () => { | |||||
| const graphStyle = useEmotionCss(() => { | const graphStyle = useEmotionCss(() => { | ||||
| return { | return { | ||||
| width:'100%', | width:'100%', | ||||
| backgroundColor:'#f9fafb', | |||||
| backgroundSize: '100% 100%', | |||||
| backgroundImage: 'url(/assets/images/pipeline-canvas-back.png)', | |||||
| flex:1 | flex:1 | ||||
| }; | }; | ||||
| }); | }); | ||||
| @@ -81,7 +83,7 @@ const editPipeline = React.FC = () => { | |||||
| data.nodes[index] = val; | data.nodes[index] = val; | ||||
| graph.changeData(data) | graph.changeData(data) | ||||
| } | } | ||||
| const savePipeline=()=>{ | |||||
| const savePipeline=(val)=>{ | |||||
| const data = graph.save(); | const data = graph.save(); | ||||
| console.log(data); | console.log(data); | ||||
| let params={ | let params={ | ||||
| @@ -90,14 +92,22 @@ const editPipeline = React.FC = () => { | |||||
| } | } | ||||
| saveWorkflow(params).then(ret=>{ | saveWorkflow(params).then(ret=>{ | ||||
| console.log(ret); | console.log(ret); | ||||
| message.success('保存成功') | |||||
| if(ret.code==200){ | |||||
| message.success('保存成功') | |||||
| setTimeout(()=>{ | setTimeout(()=>{ | ||||
| navgite({pathname:`/pipeline`,}); | |||||
| if(val){ | |||||
| navgite({pathname:`/pipeline`,}); | |||||
| } | |||||
| },500) | },500) | ||||
| } | |||||
| else{ | |||||
| message.error('保存失败') | |||||
| } | |||||
| }) | }) | ||||
| console.log(params); | console.log(params); | ||||
| } | } | ||||
| const handlerClick=(e)=>{ | const handlerClick=(e)=>{ | ||||
| e.stopPropagation() | |||||
| console.log(propsRef,graph); | console.log(propsRef,graph); | ||||
| // let cache = []; | // let cache = []; | ||||
| // let json_str = JSON.stringify(graph, function(key, value) { | // let json_str = JSON.stringify(graph, function(key, value) { | ||||
| @@ -124,6 +134,106 @@ const editPipeline = React.FC = () => { | |||||
| },500) | },500) | ||||
| } | } | ||||
| } | } | ||||
| const processParallelEdgesOnAnchorPoint = ( | |||||
| edges, | |||||
| offsetDiff = 15, | |||||
| multiEdgeType = 'cubic-vertical', | |||||
| singleEdgeType = undefined, | |||||
| loopEdgeType = undefined | |||||
| ) => { | |||||
| const len = edges.length; | |||||
| const cod = offsetDiff * 2; | |||||
| const loopPosition = [ | |||||
| 'top', | |||||
| 'top-right', | |||||
| 'right', | |||||
| 'bottom-right', | |||||
| 'bottom', | |||||
| 'bottom-left', | |||||
| 'left', | |||||
| 'top-left', | |||||
| ]; | |||||
| const edgeMap = {}; | |||||
| const tags = []; | |||||
| const reverses = {}; | |||||
| for (let i = 0; i < len; i++) { | |||||
| const edge = edges[i]; | |||||
| const { source, target, sourceAnchor, targetAnchor } = edge; | |||||
| const sourceTarget = `${source}|${sourceAnchor}-${target}|${targetAnchor}`; | |||||
| if (tags[i]) continue; | |||||
| if (!edgeMap[sourceTarget]) { | |||||
| edgeMap[sourceTarget] = []; | |||||
| } | |||||
| tags[i] = true; | |||||
| edgeMap[sourceTarget].push(edge); | |||||
| for (let j = 0; j < len; j++) { | |||||
| if (i === j) continue; | |||||
| const sedge = edges[j]; | |||||
| const { source: src, target: dst, sourceAnchor: srcAnchor, targetAnchor: dstAnchor } = sedge; | |||||
| // 两个节点之间共同的边 | |||||
| // 第一条的source = 第二条的target | |||||
| // 第一条的target = 第二条的source | |||||
| if (!tags[j]) { | |||||
| if (source === dst && sourceAnchor === dstAnchor | |||||
| && target === src && targetAnchor === srcAnchor) { | |||||
| edgeMap[sourceTarget].push(sedge); | |||||
| tags[j] = true; | |||||
| reverses[`${src}|${srcAnchor}|${dst}|${dstAnchor}|${edgeMap[sourceTarget].length - 1}`] = true; | |||||
| } else if (source === src && sourceAnchor === srcAnchor | |||||
| && target === dst && targetAnchor === dstAnchor) { | |||||
| edgeMap[sourceTarget].push(sedge); | |||||
| tags[j] = true; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| for (const key in edgeMap) { | |||||
| const arcEdges = edgeMap[key]; | |||||
| const { length } = arcEdges; | |||||
| for (let k = 0; k < length; k++) { | |||||
| const current = arcEdges[k]; | |||||
| if (current.source === current.target) { | |||||
| if (loopEdgeType) current.type = loopEdgeType; | |||||
| // 超过8条自环边,则需要重新处理 | |||||
| current.loopCfg = { | |||||
| position: loopPosition[k % 8], | |||||
| dist: Math.floor(k / 8) * 20 + 50, | |||||
| }; | |||||
| continue; | |||||
| } | |||||
| if (length === 1 && singleEdgeType && (current.source !== current.target || current.sourceAnchor !== current.targetAnchor)) { | |||||
| current.type = singleEdgeType; | |||||
| continue; | |||||
| } | |||||
| current.type = multiEdgeType; | |||||
| const sign = | |||||
| (k % 2 === 0 ? 1 : -1) * (reverses[`${current.source}|${current.sourceAnchor}|${current.target}|${current.targetAnchor}|${k}`] ? -1 : 1); | |||||
| if (length % 2 === 1) { | |||||
| current.curveOffset = sign * Math.ceil(k / 2) * cod; | |||||
| } else { | |||||
| current.curveOffset = sign * (Math.floor(k / 2) * cod + offsetDiff); | |||||
| } | |||||
| } | |||||
| } | |||||
| return edges; | |||||
| }; | |||||
| const cloneElement=(item)=>{ | |||||
| console.log(item); | |||||
| let data=graph.save() | |||||
| const nodeId = s8(); | |||||
| console.log(item.getModel()); | |||||
| data.nodes.push({ | |||||
| ...item.getModel(), | |||||
| label: item.getModel().label+'-copy', | |||||
| x: item.getModel().x + 150, | |||||
| y: item.getModel().y, | |||||
| id: item.getModel().component_name+'-'+nodeId, | |||||
| }); | |||||
| graph.changeData(data) | |||||
| } | |||||
| const getFirstWorkflow=(val)=>{ | const getFirstWorkflow=(val)=>{ | ||||
| getWorkflowById(val).then(ret=>{ | getWorkflowById(val).then(ret=>{ | ||||
| console.log(ret); | console.log(ret); | ||||
| @@ -161,22 +271,17 @@ const editPipeline = React.FC = () => { | |||||
| color: #333333; | color: #333333; | ||||
| overflow-y: auto;"> | overflow-y: auto;"> | ||||
| <li style="padding: 10px 20px;cursor: pointer;" code="clone">复制</li> | |||||
| <li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li> | <li style="padding: 10px 20px;cursor: pointer;" code="delete">删除</li> | ||||
| </ul>`; | </ul>`; | ||||
| }, | }, | ||||
| handleMenuClick: (target, item) => { | handleMenuClick: (target, item) => { | ||||
| switch (target.getAttribute('code')) { | switch (target.getAttribute('code')) { | ||||
| // <li style="padding: 10px 20px;cursor: pointer;" code="undo">撤回</li> | |||||
| // <li style="padding: 10px 20px;cursor: pointer;" code="redo">恢复</li> | |||||
| // case 'undo': | |||||
| // this.$emit('handleMenuCall', { code: 'undo' }); | |||||
| // break; | |||||
| // case 'redo': | |||||
| // this.$emit('handleMenuCall', { code: 'redo' }); | |||||
| // break; | |||||
| case 'delete': | case 'delete': | ||||
| graph.removeItem(item); | graph.removeItem(item); | ||||
| break; | |||||
| case 'clone': | |||||
| cloneElement(item) | |||||
| break; | break; | ||||
| default: | default: | ||||
| break; | break; | ||||
| @@ -189,7 +294,7 @@ const editPipeline = React.FC = () => { | |||||
| offsetY: 0, | offsetY: 0, | ||||
| // the types of items that allow the menu show up | // the types of items that allow the menu show up | ||||
| // 在哪些类型的元素上响应 | // 在哪些类型的元素上响应 | ||||
| itemTypes: ['node', 'edge', 'canvas']}) | |||||
| itemTypes: ['node', 'edge']}) | |||||
| initGraph() | initGraph() | ||||
| }; | }; | ||||
| @@ -197,7 +302,17 @@ const editPipeline = React.FC = () => { | |||||
| getFirstWorkflow(locationParams.id) | getFirstWorkflow(locationParams.id) | ||||
| initMenu() | initMenu() | ||||
| return ()=>{ | |||||
| graph.off('node:mouseenter', e => { | |||||
| graph.setItemState(e.item, 'showAnchors', true); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| }); | |||||
| graph.off('node:mouseleave', e => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', false); | |||||
| graph.setItemState(e.item, 'nodeSelected', false); | |||||
| }); | |||||
| graph.off('dblclick', handlerClick); | |||||
| } | |||||
| console.log(contextMenu); | console.log(contextMenu); | ||||
| },[]) | },[]) | ||||
| const initGraph=()=>{ | const initGraph=()=>{ | ||||
| @@ -209,25 +324,8 @@ const editPipeline = React.FC = () => { | |||||
| return ( | return ( | ||||
| cfg.anchorPoints || [ | cfg.anchorPoints || [ | ||||
| // 上下各3,左右各1 | // 上下各3,左右各1 | ||||
| [0.1, 0.05], | |||||
| [0.5, 0.05], | |||||
| [0.9, 0.05], | |||||
| [0, 0.5], | |||||
| [1, 0.5], | |||||
| [0.1, 1], | |||||
| [0.5, 0], | |||||
| [0.5, 1], | [0.5, 1], | ||||
| [0.9, 1], | |||||
| // 四边中间 | |||||
| // [0.5, 0.05], | |||||
| // [0, 0.5], | |||||
| // [1, 0.5], | |||||
| // [0.5, 1], | |||||
| // 四个角落 | |||||
| // [0.05, 0.05], | |||||
| // [0.9, 0.05], | |||||
| // [0.05, 1], | |||||
| // [0.9, 1], | |||||
| ] | ] | ||||
| ); | ); | ||||
| }, | }, | ||||
| @@ -235,10 +333,10 @@ const editPipeline = React.FC = () => { | |||||
| // console.log(group, cfg, 12312); | // console.log(group, cfg, 12312); | ||||
| const image = group.addShape('image', { | const image = group.addShape('image', { | ||||
| attrs: { | attrs: { | ||||
| x: -25, | |||||
| y: -13, | |||||
| width: 21, | |||||
| height: 21, | |||||
| x: -45, | |||||
| y: -10, | |||||
| width: 20, | |||||
| height: 20, | |||||
| img: cfg.img, | img: cfg.img, | ||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| }, | }, | ||||
| @@ -263,17 +361,16 @@ const editPipeline = React.FC = () => { | |||||
| anchorPoints.forEach((anchorPos, i) => { | anchorPoints.forEach((anchorPos, i) => { | ||||
| group.addShape('circle', { | group.addShape('circle', { | ||||
| attrs: { | attrs: { | ||||
| r: 5, | |||||
| r: 3, | |||||
| x: bbox.x + bbox.width * anchorPos[0], | x: bbox.x + bbox.width * anchorPos[0], | ||||
| y: bbox.y + bbox.height * anchorPos[1], | y: bbox.y + bbox.height * anchorPos[1], | ||||
| fill: '#000', | |||||
| stroke: '#000', | |||||
| fill: '#fff', | |||||
| stroke: '#a4a4a5', | |||||
| }, | }, | ||||
| name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point') | ||||
| anchorPointIdx: i, // flag the idx of the anchor-point circle | anchorPointIdx: i, // flag the idx of the anchor-point circle | ||||
| links: 0, // cache the number of edges connected to this shape | links: 0, // cache the number of edges connected to this shape | ||||
| visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state | ||||
| draggable: true, // allow to catch the drag events on this shape | |||||
| }); | }); | ||||
| }); | }); | ||||
| return image; | return image; | ||||
| @@ -281,14 +378,11 @@ const editPipeline = React.FC = () => { | |||||
| // response the state changes and show/hide the link-point circles | // response the state changes and show/hide the link-point circles | ||||
| setState(name, value, item) { | setState(name, value, item) { | ||||
| // 默认显示全部锚点,防止过宽导致锚点无法被选中 | |||||
| // if (name === 'showAnchors') { | |||||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach(point => { | |||||
| // if (value) point.show(); | |||||
| // else point.hide(); | |||||
| point.show(); | |||||
| }); | |||||
| const anchorPoints = item.getContainer().findAll(ele => ele.get('name') === 'anchor-point'); | |||||
| anchorPoints.forEach(point => { | |||||
| if (value || point.get('links') > 0) point.show() | |||||
| else point.hide() | |||||
| }) | |||||
| // } | // } | ||||
| }, | }, | ||||
| }, | }, | ||||
| @@ -299,7 +393,7 @@ const editPipeline = React.FC = () => { | |||||
| container: graphRef.current, | container: graphRef.current, | ||||
| grid: true, | grid: true, | ||||
| width: graphRef.current.clientWidth ||500, | width: graphRef.current.clientWidth ||500, | ||||
| height: graphRef.current.clientHeight||760, | |||||
| height: graphRef.current.clientHeight||'100%', | |||||
| animate: false, | animate: false, | ||||
| groupByTypes: false, | groupByTypes: false, | ||||
| fitView:true, | fitView:true, | ||||
| @@ -321,9 +415,28 @@ const editPipeline = React.FC = () => { | |||||
| }, | }, | ||||
| // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles | ||||
| { | { | ||||
| type: 'create-edge', | |||||
| key: 'shift', // undefined by default, options: 'shift', 'control', 'ctrl', 'meta', 'alt' | |||||
| type: 'create-edge', | |||||
| // trigger: 'drag', | |||||
| shouldBegin: e => { | |||||
| // avoid beginning at other shapes on the node | |||||
| if (e.target && e.target.get('name') !== 'anchor-point') return false; | |||||
| sourceAnchorIdx = e.target.get('anchorPointIdx'); | |||||
| e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle | |||||
| return true; | |||||
| }, | }, | ||||
| shouldEnd: e => { | |||||
| // avoid ending at other shapes on the node | |||||
| if (e.target && e.target.get('name') !== 'anchor-point') return false; | |||||
| if (e.target) { | |||||
| targetAnchorIdx = e.target.get('anchorPointIdx'); | |||||
| e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle | |||||
| return true; | |||||
| } | |||||
| targetAnchorIdx = undefined; | |||||
| return true; | |||||
| }, | |||||
| }, | |||||
| 'drag-canvas', | 'drag-canvas', | ||||
| 'zoom-canvas', | 'zoom-canvas', | ||||
| // 'brush-select', | // 'brush-select', | ||||
| @@ -340,49 +453,54 @@ const editPipeline = React.FC = () => { | |||||
| defaultNode: { | defaultNode: { | ||||
| type: 'rect-node', | type: 'rect-node', | ||||
| size: 70, | |||||
| size: [110,36], | |||||
| labelCfg: { | labelCfg: { | ||||
| style: { | style: { | ||||
| fill: '#000', | fill: '#000', | ||||
| fontSize: 12, | |||||
| cursor: 'pointer', | |||||
| x: 0, | |||||
| fontSize: 10, | |||||
| boxShadow:'0px 0px 12px rgba(75, 84, 137, 0.05)', | |||||
| x: -20, | |||||
| y: 0, | y: 0, | ||||
| textAlign: 'left', | textAlign: 'left', | ||||
| textBaseline: 'middle', | textBaseline: 'middle', | ||||
| }, | }, | ||||
| }, | }, | ||||
| style: { | style: { | ||||
| fill: 'transparent', | |||||
| stroke: 'transparent', | |||||
| fill: '#fff', | |||||
| stroke: '#fff', | |||||
| cursor: 'pointer', | |||||
| radius:10, | |||||
| lineWidth:0.5 | |||||
| }, | |||||
| }, | |||||
| nodeStateStyles: { | |||||
| nodeSelected: { | |||||
| fill: 'red', | |||||
| shadowColor: 'red', | |||||
| stroke: 'red', | |||||
| 'text-shape': { | |||||
| fill: 'red', | |||||
| stroke: 'red', | |||||
| }, | |||||
| }, | }, | ||||
| }, | }, | ||||
| // nodeStateStyles: { | |||||
| // nodeSelected: { | |||||
| // fill: 'red', | |||||
| // shadowColor: 'red', | |||||
| // stroke: 'red', | |||||
| // 'text-shape': { | |||||
| // fill: 'red', | |||||
| // stroke: 'red', | |||||
| // }, | |||||
| // }, | |||||
| // }, | |||||
| defaultEdge: { | defaultEdge: { | ||||
| // type: 'quadratic', | // type: 'quadratic', | ||||
| type: 'polyline', | |||||
| type: 'cubic-vertical', | |||||
| style: { | style: { | ||||
| endArrow: { | |||||
| path: G6.Arrow.triangle(), | |||||
| endArrow: { // 设置终点箭头 | |||||
| path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) | |||||
| d: 4.5, | |||||
| fill:'#CDD0DC' | |||||
| }, | }, | ||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| endArrow: true, | |||||
| lineWidth: 1, | lineWidth: 1, | ||||
| opacity: 1, | opacity: 1, | ||||
| stroke: '#a2a6b5', | |||||
| radius: 10, | |||||
| stroke: '#CDD0DC', | |||||
| radius: 1, | |||||
| }, | }, | ||||
| nodeStateStyle: { | nodeStateStyle: { | ||||
| hover: { | hover: { | ||||
| @@ -409,14 +527,29 @@ const editPipeline = React.FC = () => { | |||||
| cursor: 'pointer', | cursor: 'pointer', | ||||
| }, | }, | ||||
| }, | }, | ||||
| linkCenter: true, | |||||
| // linkCenter: true, | |||||
| fitView: true, | fitView: true, | ||||
| fitViewPadding: [60, 60, 60, 80], | fitViewPadding: [60, 60, 60, 80], | ||||
| }); | }); | ||||
| graph.on('dblclick', handlerClick); | |||||
| graph.on('dblclick', (e)=>{ | |||||
| console.log(e.item); | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| handlerClick(e) | |||||
| }); | |||||
| graph.on('click', (e)=>{ | |||||
| console.log(e.item); | |||||
| }); | |||||
| graph.on('aftercreateedge', (e) => { | graph.on('aftercreateedge', (e) => { | ||||
| // update the sourceAnchor and targetAnchor for the newly added edge | |||||
| graph.updateItem(e.edge, { | |||||
| sourceAnchor: sourceAnchorIdx, | |||||
| targetAnchor: targetAnchorIdx | |||||
| }) | |||||
| // update the curveOffset for parallel edges | |||||
| const edges = graph.save().edges; | const edges = graph.save().edges; | ||||
| G6.Util.processParallelEdges(edges); | |||||
| processParallelEdgesOnAnchorPoint(edges); | |||||
| graph.getEdges().forEach((edge, i) => { | graph.getEdges().forEach((edge, i) => { | ||||
| graph.updateItem(edge, { | graph.updateItem(edge, { | ||||
| curveOffset: edges[i].curveOffset, | curveOffset: edges[i].curveOffset, | ||||
| @@ -424,6 +557,50 @@ const editPipeline = React.FC = () => { | |||||
| }); | }); | ||||
| }); | }); | ||||
| }); | }); | ||||
| graph.on('node:mouseenter', e => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', true); | |||||
| graph.setItemState(e.item, 'nodeSelected', true); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: '#1664ff', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('node:mouseleave', e => { | |||||
| // this.graph.setItemState(e.item, 'showAnchors', false); | |||||
| graph.setItemState(e.item, 'nodeSelected', false); | |||||
| graph.updateItem(e.item, { | |||||
| // 节点的样式 | |||||
| style: { | |||||
| stroke: 'transparent', | |||||
| }, | |||||
| }); | |||||
| }); | |||||
| graph.on('afterremoveitem', e => { | |||||
| if (e.item && e.item.source && e.item.target) { | |||||
| const sourceNode = graph.findById(e.item.source); | |||||
| const targetNode = graph.findById(e.item.target); | |||||
| const { sourceAnchor, targetAnchor } = e.item; | |||||
| if (sourceNode && !isNaN(sourceAnchor)) { | |||||
| const sourceAnchorShape = sourceNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor)); | |||||
| sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1); | |||||
| } | |||||
| if (targetNode && !isNaN(targetAnchor)) { | |||||
| const targetAnchorShape = targetNode.getContainer().find(ele => (ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor)); | |||||
| targetAnchorShape.set('links', targetAnchorShape.get('links') - 1); | |||||
| } | |||||
| } | |||||
| }) | |||||
| // after clicking on the first node, the edge is created, update the sourceAnchor | |||||
| graph.on('afteradditem', e => { | |||||
| if (e.item && e.item.getType() === 'edge') { | |||||
| graph.updateItem(e.item, { | |||||
| sourceAnchor: sourceAnchorIdx | |||||
| }); | |||||
| } | |||||
| }) | |||||
| window.onresize = () => { | window.onresize = () => { | ||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) return; | if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) return; | ||||
| @@ -434,9 +611,8 @@ const editPipeline = React.FC = () => { | |||||
| <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | ||||
| <div className={Styles.centerContainer}> | <div className={Styles.centerContainer}> | ||||
| <div className={Styles.buttonList}> | <div className={Styles.buttonList}> | ||||
| {/* <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>撤回</Button> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>恢复</Button> */} | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={savePipeline}>保存</Button> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} style={{marginRight:'20px'}} onClick={()=>{savePipeline(false)}}>保存</Button> | |||||
| <Button type="primary" shape="round" icon={<SaveOutlined />} onClick={()=>{savePipeline(true)}}>保存并返回</Button> | |||||
| </div> | </div> | ||||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | ||||
| </div> | </div> | ||||
| @@ -34,15 +34,17 @@ const modelMenus = ({onParDragEnd}) => { | |||||
| onParDragEnd({...data,x:e.clientX,y:e.clientY,label:data.component_label,img:`/assets/images/${data.icon_path}.png`}) | onParDragEnd({...data,x:e.clientX,y:e.clientY,label:data.component_label,img:`/assets/images/${data.icon_path}.png`}) | ||||
| } | } | ||||
| const { Panel } = Collapse; | const { Panel } = Collapse; | ||||
| return (<div style={{width:'300px',height:'100%'}}> | |||||
| return (<div style={{width:'250px',height:'100%'}} className={Styles.collapse}> | |||||
| <Collapse | <Collapse | ||||
| collapsible="header" | collapsible="header" | ||||
| defaultActiveKey={['1']} | defaultActiveKey={['1']} | ||||
| expandIconPosition="end" | |||||
| > | > | ||||
| {modelMenusList && modelMenusList.length > 0 | {modelMenusList && modelMenusList.length > 0 | ||||
| ? modelMenusList.map(item => ( | ? modelMenusList.map(item => ( | ||||
| <Panel | <Panel | ||||
| header={<div>{item.name}</div>} | header={<div>{item.name}</div>} | ||||
| key={item.key} | key={item.key} | ||||
| > | > | ||||
| @@ -9,4 +9,35 @@ | |||||
| font-size:14px; | font-size:14px; | ||||
| height:40px; | height:40px; | ||||
| cursor: pointer; | cursor: pointer; | ||||
| border-radius:4px; | |||||
| padding: 0 16px; | |||||
| } | |||||
| .collapseItem:hover{ | |||||
| background:rgba(22, 100, 255, 0.08); | |||||
| } | |||||
| .collapse{ | |||||
| :global { | |||||
| .ant-collapse{ | |||||
| border-color: transparent!important; | |||||
| background-color: #fff; | |||||
| } | |||||
| .ant-collapse>.ant-collapse-item >.ant-collapse-header{ | |||||
| background-color: #fff; | |||||
| border-color: transparent; | |||||
| margin-bottom: 5px; | |||||
| } | |||||
| .ant-collapse>.ant-collapse-item{ | |||||
| border-radius: 0px; | |||||
| border-color:rgba(20, 49, 179, 0.12); | |||||
| margin: 0 10px; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content{ | |||||
| padding-bottom: 15px; | |||||
| border-top: 1px solid transparent; | |||||
| } | |||||
| .ant-collapse .ant-collapse-content>.ant-collapse-content-box{ | |||||
| padding: 0; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| import React ,{ useState,useEffect,useRef }from 'react'; | import React ,{ useState,useEffect,useRef }from 'react'; | ||||
| import { Space, Table, Tag,Button,Modal, Form, Input ,message} from 'antd'; | import { Space, Table, Tag,Button,Modal, Form, Input ,message} from 'antd'; | ||||
| import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||||
| import { PlusOutlined,PlusCircleOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined ,CopyOutlined} from '@ant-design/icons'; | |||||
| import {getWorkflow,addWorkflow,removeWorkflow,cloneWorkflow,getWorkflowById,editWorkflow} from '@/services/pipeline/index.js' | import {getWorkflow,addWorkflow,removeWorkflow,cloneWorkflow,getWorkflowById,editWorkflow} from '@/services/pipeline/index.js' | ||||
| import Styles from './index.less' | import Styles from './index.less' | ||||
| import momnet from 'moment' | import momnet from 'moment' | ||||
| @@ -36,7 +36,7 @@ const Pipeline = React.FC = () => { | |||||
| } | } | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields() | form.resetFields() | ||||
| setDialogTitle('编辑流水线') | |||||
| setDialogTitle('新建流水线') | |||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| const handleOk = () => { | const handleOk = () => { | ||||
| @@ -56,7 +56,10 @@ const Pipeline = React.FC = () => { | |||||
| } | } | ||||
| else{ | else{ | ||||
| addWorkflow(values).then(ret=>{ | addWorkflow(values).then(ret=>{ | ||||
| navgite({pathname:`/pipeline/pytorchtext/${ret.id}/${ret.name}`,}); | |||||
| console.log(ret); | |||||
| if(ret.code==200){ | |||||
| navgite({pathname:`/pipeline/pytorchtext/${ret.data.id}/${ret.data.name}`,}); | |||||
| } | |||||
| } | } | ||||
| ) | ) | ||||
| } | } | ||||
| @@ -107,7 +110,7 @@ const Pipeline = React.FC = () => { | |||||
| title: '序号', | title: '序号', | ||||
| dataIndex: 'index', | dataIndex: 'index', | ||||
| key: 'index', | key: 'index', | ||||
| width: 60, | |||||
| width: 80, | |||||
| render(text, record, index) { | render(text, record, index) { | ||||
| return ( | return ( | ||||
| <span>{(pageOption.current.page - 1) * 10 + index + 1}</span> | <span>{(pageOption.current.page - 1) * 10 + index + 1}</span> | ||||
| @@ -141,6 +144,7 @@ const Pipeline = React.FC = () => { | |||||
| { | { | ||||
| title: '操作', | title: '操作', | ||||
| key: 'action', | key: 'action', | ||||
| render: (_, record) => ( | render: (_, record) => ( | ||||
| <Space size="small"> | <Space size="small"> | ||||
| <Button | <Button | ||||
| @@ -192,6 +196,7 @@ const Pipeline = React.FC = () => { | |||||
| type="link" | type="link" | ||||
| size="small" | size="small" | ||||
| danger | danger | ||||
| style={{color:'#f98e1b'}} | |||||
| key="batchRemove" | key="batchRemove" | ||||
| icon = {< DeleteOutlined />} | icon = {< DeleteOutlined />} | ||||
| onClick={async () => { | onClick={async () => { | ||||
| @@ -229,12 +234,14 @@ const Pipeline = React.FC = () => { | |||||
| ]; | ]; | ||||
| return (<div> | return (<div> | ||||
| <div className={Styles.pipelineTopBox}> | <div className={Styles.pipelineTopBox}> | ||||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {< PlusOutlined />}> | |||||
| <Button type="primary" className={Styles.plusButton} onClick={showModal} icon = {<PlusCircleOutlined style={{color:'#1664ff'}} />}> | |||||
| 新建流水线 | 新建流水线 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <Table columns={columns} dataSource={pipeList} pagination={paginationProps}/> | <Table columns={columns} dataSource={pipeList} pagination={paginationProps}/> | ||||
| <Modal title={dialogTitle} open={isModalOpen} okButtonProps={{ | |||||
| <Modal title={<div style={{display:'flex',alignItems:'center',fontWeight:500}}> | |||||
| <img style={{width:'20px',marginRight:'10px'}} src={`/assets/images/pipeline-edit-icon.png`} alt="" />{dialogTitle} | |||||
| </div>} open={isModalOpen} className={Styles.modal} okButtonProps={{ | |||||
| htmlType: 'submit', | htmlType: 'submit', | ||||
| form: 'form', | form: 'form', | ||||
| }} onCancel={handleCancel}> | }} onCancel={handleCancel}> | ||||
| @@ -242,15 +249,6 @@ const Pipeline = React.FC = () => { | |||||
| name="form" | name="form" | ||||
| form={form} | form={form} | ||||
| layout="vertical" | layout="vertical" | ||||
| labelCol={{ | |||||
| span: 8, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 16, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| initialValues={{ | initialValues={{ | ||||
| remember: true, | remember: true, | ||||
| }} | }} | ||||
| @@ -282,18 +280,6 @@ const Pipeline = React.FC = () => { | |||||
| > | > | ||||
| <Input /> | <Input /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | |||||
| label="备注" | |||||
| name="remake" | |||||
| rules={[ | |||||
| // { | |||||
| // required: true, | |||||
| // message: 'Please input your username!', | |||||
| // }, | |||||
| ]} | |||||
| > | |||||
| <TextArea /> | |||||
| </Form.Item> | |||||
| </Form> | </Form> | ||||
| </Modal> | </Modal> | ||||
| </div>)}; | </div>)}; | ||||
| @@ -7,19 +7,58 @@ | |||||
| height: 49px; | height: 49px; | ||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| margin-bottom: 10px; | |||||
| } | |||||
| .plusButton{ | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border:1px solid; | |||||
| border-color:rgba(22, 100, 255, 0.11); | |||||
| border-radius:4px; | |||||
| color:#1d1d20; | |||||
| font-size:14px; | |||||
| font-family: 'Alibaba'; | |||||
| } | |||||
| .plusButton:hover{ | |||||
| background:rgba(22, 100, 255, 0.06)!important; | |||||
| border:1px solid!important; | |||||
| border-color:rgba(22, 100, 255, 0.11)!important; | |||||
| color:#1d1d20!important; | |||||
| } | |||||
| .modal { | |||||
| :global { | |||||
| .ant-modal-content { | |||||
| background:linear-gradient(180deg,#cfdfff 0%,#d4e2ff 9.77%,#ffffff 40%,#ffffff 100%); | |||||
| border-radius:21px; | |||||
| padding: 20px 67px; | |||||
| width: 825px; | |||||
| } | |||||
| .ant-modal-header{ | |||||
| background-color: transparent; | |||||
| margin: 20px 0; | |||||
| } | |||||
| .ant-input{ | |||||
| border-color:#e6e6e6; | |||||
| height: 40px; | |||||
| } | |||||
| .ant-form-item .ant-form-item-label >label{ | |||||
| color:rgba(29, 29, 32, 0.8); | |||||
| } | |||||
| .ant-modal-footer{ | |||||
| margin: 40px 0 30px 0; | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| } | |||||
| .ant-btn{ | |||||
| width:123px; | |||||
| height:40px; | |||||
| font-size:18px; | |||||
| background:rgba(22, 100, 255, 0.06); | |||||
| border-radius:10px; | |||||
| } | |||||
| .ant-btn-primary{ | |||||
| background:#1664ff; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| // .plusButton{ | |||||
| // background:rgba(22, 100, 255, 0.06); | |||||
| // border:1px solid; | |||||
| // border-color:rgba(22, 100, 255, 0.11); | |||||
| // border-radius:4px; | |||||
| // color:#1d1d20; | |||||
| // font-size:15px; | |||||
| // font-family: 'Alibaba'; | |||||
| // } | |||||
| // .plusButton:hover{ | |||||
| // background:rgba(22, 100, 255, 0.06); | |||||
| // border:1px solid; | |||||
| // border-color:rgba(22, 100, 255, 0.11); | |||||
| // border-radius:4px; | |||||
| // } | |||||