diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 200bbd20..1351cd2c 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -77,7 +77,7 @@ export default [ }, { name: '流水线详情', - path: ':id/:name', + path: ':id', component: './Pipeline/editPipeline/index', }, ], diff --git a/react-ui/src/locales/zh-CN/pages.ts b/react-ui/src/locales/zh-CN/pages.ts index 5e4565d5..ed9afc5e 100644 --- a/react-ui/src/locales/zh-CN/pages.ts +++ b/react-ui/src/locales/zh-CN/pages.ts @@ -1,12 +1,12 @@ export default { 'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范', 'pages.login.accountLogin.tab': '账户密码登录', - 'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/admin123)', + 'pages.login.accountLogin.errorMessage': '错误的用户名和密码', 'pages.login.failure': '登录失败,请重试!', 'pages.login.success': '登录成功!', - 'pages.login.username.placeholder': '用户名: admin', + 'pages.login.username.placeholder': '用户名', 'pages.login.username.required': '用户名是必填项!', - 'pages.login.password.placeholder': '密码: admin123', + 'pages.login.password.placeholder': '密码', 'pages.login.password.required': '密码是必填项!', 'pages.login.phoneLogin.tab': '手机号登录', 'pages.login.phoneLogin.errorMessage': '验证码错误', diff --git a/react-ui/src/locales/zh-TW/pages.ts b/react-ui/src/locales/zh-TW/pages.ts index f9e265b0..b3e37ef5 100644 --- a/react-ui/src/locales/zh-TW/pages.ts +++ b/react-ui/src/locales/zh-TW/pages.ts @@ -1,12 +1,12 @@ export default { 'pages.layouts.userLayout.title': 'Ant Design 是西湖區最具影響力的 Web 設計規範', 'pages.login.accountLogin.tab': '賬戶密碼登錄', - 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼(admin/admin123)', + 'pages.login.accountLogin.errorMessage': '錯誤的用戶名和密碼', 'pages.login.failure': '登錄失敗,請重試!', 'pages.login.success': '登錄成功!', - 'pages.login.username.placeholder': '用戶名: admin', + 'pages.login.username.placeholder': '用戶名', 'pages.login.username.required': '用戶名是必填項!', - 'pages.login.password.placeholder': '密碼: admin123', + 'pages.login.password.placeholder': '密碼', 'pages.login.password.required': '密碼是必填項!', 'pages.login.phoneLogin.tab': '手機號登錄', 'pages.login.phoneLogin.errorMessage': '驗證碼錯誤', diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index aa6349d8..69648b49 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -198,7 +198,7 @@ function Experiment() { }; const routeToEdit = (e, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/template/${record.workflow_id}/${record.workflow_name}` }); + navgite({ pathname: `/pipeline/template/${record.workflow_id}` }); }; // 创建或者编辑实验接口请求 const handleAddExperiment = async (values) => { diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx index 6cbabfe0..991e9855 100644 --- a/react-ui/src/pages/Experiment/training/index.jsx +++ b/react-ui/src/pages/Experiment/training/index.jsx @@ -23,24 +23,12 @@ function ExperimentText() { const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); const graphRef = useRef(); - // const onDragEnd = (val) => { - // console.log(val, 'eee'); - // const _x = val.x; - // const _y = val.y; - // const point = graph.getPointByClient(_x, _y); - // let model = {}; - // // 元模型 - // model = { - // ...val, - // x: point.x, - // y: point.y, - // id: val.component_name + '-' + s8(), - // isCluster: false, - // }; - // graph.addItem('node', model, true); - // }; const getGraphData = (data) => { if (graph) { + // 修改历史数据有蓝色边框的问题 + data.nodes.forEach((item) => { + item.style.stroke = '#fff'; + }); graph.data(data); graph.render(); } else { @@ -92,9 +80,11 @@ function ExperimentText() { getAnchorPoints(cfg) { return ( cfg.anchorPoints || [ - // 上下各3,左右各1 + // 四个,上下左右 [0.5, 0], [0.5, 1], + [0, 0.5], + [1, 0.5], ] ); }, @@ -120,6 +110,7 @@ function ExperimentText() { textAlign: 'left', textBaseline: 'middle', fill: '#000', + cursor: 'pointer', }, name: 'text-shape', draggable: true, @@ -166,8 +157,12 @@ function ExperimentText() { width: graphRef.current.clientWidth || 500, height: graphRef.current.clientHeight || 760, animate: false, - groupByTypes: false, - enabledStack: true, + groupByTypes: true, + enabledStack: false, + fitView: true, + minZoom: 0.5, + maxZoom: 5, + fitViewPadding: 300, modes: { default: [ // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles @@ -181,15 +176,6 @@ function ExperimentText() { // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles 'drag-canvas', 'zoom-canvas', - // 'brush-select', - 'drag-combo', - ], - altSelect: [ - { - type: 'brush-select', - trigger: 'drag', - }, - 'drag-node', ], }, @@ -217,7 +203,7 @@ function ExperimentText() { }, defaultEdge: { // type: 'quadratic', - type: 'cubic-vertical', + // type: 'cubic-vertical', style: { endArrow: { @@ -232,15 +218,8 @@ function ExperimentText() { stroke: '#a2a6b5', radius: 1, }, - nodeStateStyle: { - hover: { - opacity: 1, - stroke: '#8fe8ff', - }, - }, labelCfg: { autoRotate: true, - // refY: 10, style: { fontSize: 10, fill: '#FFF', @@ -257,11 +236,6 @@ function ExperimentText() { cursor: 'pointer', }, }, - // linkCenter: true, - fitView: true, - minZoom: 0.5, - maxZoom: 5, - fitViewPadding: 300, }); graph.on('node:click', (e) => { if (e.target.get('name') !== 'anchor-point' && e.item) { diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index bd8286cf..6a6e74e8 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -12,7 +12,14 @@ import GraphLegend from '../GraphLegend'; import NodeTooltips from '../NodeTooltips'; import styles from './index.less'; import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; -import { NodeType, getGraphData, nodeHeight, nodeWidth, normalizeTreeData } from './utils'; +import { + NodeType, + getGraphData, + nodeFontSize, + nodeHeight, + nodeWidth, + normalizeTreeData, +} from './utils'; type modeModelEvolutionProps = { resourceId: number; @@ -73,7 +80,7 @@ function ModelEvolution({ width: graphRef.current!.clientWidth, height: graphRef.current!.clientHeight, fitView: true, - fitViewPadding: [50, 100, 50, 100], + fitViewPadding: [100, 100, 100, 100], minZoom: 0.5, maxZoom: 5, defaultNode: { @@ -95,7 +102,7 @@ function ModelEvolution({ position: 'center', style: { fill: '#ffffff', - fontSize: 8, + fontSize: nodeFontSize, textAlign: 'center', cursor: 'pointer', }, @@ -107,7 +114,7 @@ function ModelEvolution({ autoRotate: true, }, style: { - stroke: '#a2c1ff', + stroke: '#DEE0E5', lineWidth: 1, }, }, @@ -163,28 +170,32 @@ function ModelEvolution({ graph.on('node:click', (e: G6GraphEvent) => { const nodeItem = e.item; - const model = nodeItem.getModel(); + const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset; const { model_type } = model; const { origin } = location; let url: string = ''; switch (model_type) { case NodeType.children: case NodeType.parent: { - const { current_model_id, version } = model as ModelDepsData; + const { current_model_id, version } = model; url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`; break; } case NodeType.project: { - const { url: projectUrl } = model as ProjectDependency; + const { url: projectUrl } = model; url = projectUrl; break; } case NodeType.trainDataset: case NodeType.testDataset: { - const { dataset_id, dataset_version } = model as TrainDataset; + const { dataset_id, dataset_version } = model; url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`; break; } + case NodeType.current: { + // TODO: 隐藏数据集和项目 + break; + } default: break; } diff --git a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx index 78fdb87a..3c3f4225 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/utils.tsx @@ -6,8 +6,10 @@ import Hierarchy from '@antv/hierarchy'; export const nodeWidth = 110; export const nodeHeight = 50; export const vGap = nodeHeight + 20; -export const hGap = nodeHeight + 20; +export const hGap = nodeWidth; export const ellipseWidth = nodeWidth; +export const labelPadding = 30; +export const nodeFontSize = 8; // 数据集节点 const datasetNodes: NodeConfig[] = []; @@ -38,14 +40,14 @@ export interface TrainDataset extends NodeConfig { dataset_id: number; dataset_name: string; dataset_version: string; - model_type: NodeType; + model_type: NodeType.testDataset | NodeType.trainDataset; } export interface ProjectDependency extends NodeConfig { url: string; name: string; branch: string; - model_type: NodeType; + model_type: NodeType.project; } export type ModalDetail = { @@ -63,7 +65,7 @@ export interface ModelDepsAPIData { current_model_id: number; version: string; exp_ins_id: number; - model_type: NodeType; + model_type: NodeType.children | NodeType.current | NodeType.parent; current_model_name: string; project_dependency: ProjectDependency; test_dataset: TrainDataset[]; @@ -94,9 +96,13 @@ export function normalizeChildren(data: ModelDepsData[]) { // 获取 label export function getLabel(node: ModelDepsData | ModelDepsAPIData) { return ( - fittingString(`${node.model_version_dependcy_vo.name ?? ''}`, nodeWidth - 12, 8) + + fittingString( + `${node.model_version_dependcy_vo.name ?? ''}`, + nodeWidth - labelPadding, + nodeFontSize, + ) + '\n' + - fittingString(`${node.version}`, nodeWidth - 12, 8) + fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize) ); } @@ -231,12 +237,12 @@ const addDatasetDependency = ( node.type = 'ellipse'; node.size = [ellipseWidth, nodeHeight]; node.label = - fittingString(node.dataset_name, ellipseWidth - 12, 8) + + fittingString(node.dataset_name, ellipseWidth - labelPadding, nodeFontSize) + '\n' + - fittingString(node.dataset_version, ellipseWidth - 12, 8); + fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); const half = len / 2 - 0.5; - node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap / 2); + node.x = currentNode.x! - (half - index) * (ellipseWidth + 20); node.y = currentNode.y! - nodeHeight - vGap; nodes.push(node); datasetNodes.push(node); @@ -263,7 +269,7 @@ const addProjectDependency = ( node.id = `$P_${node.url}_${node.branch}`; node.model_type = NodeType.project; node.type = 'rect'; - node.label = fittingString(node.name, nodeWidth - 12, 8); + node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); node.style = getStyle(NodeType.project); node.style.radius = nodeHeight / 2; node.x = currentNode.x; @@ -311,15 +317,13 @@ function adjustDatasetPosition(node: NodeConfig) { }; const overlapRect = isChildrenOverlapDataset(datasetNodes, nodeRect); if (overlapRect) { - console.log(node); - const adjustRect = { x: overlapRect.x - nodeWidth - hGap / 2, y: overlapRect.y, width: overlapRect.width, height: overlapRect.height, }; - const lastNode = datasetNodes[datasetNodes.length - 1] as NodeConfig; + const lastNode = datasetNodes[datasetNodes.length - 1]; const distance = lastNode.x! - adjustRect.x; datasetNodes.forEach((item) => { item.x = item.x! - distance; diff --git a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx index dc926114..a4b3f13b 100644 --- a/react-ui/src/pages/Model/components/NodeTooltips/index.tsx +++ b/react-ui/src/pages/Model/components/NodeTooltips/index.tsx @@ -118,14 +118,18 @@ function ProjectInfo({ data }: { data: ProjectDependency }) { function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { if (!data) return null; - let Component; + let Component = null; const { model_type } = data; if (model_type === NodeType.testDataset || model_type === NodeType.trainDataset) { - Component = ; + Component = ; } else if (model_type === NodeType.project) { - Component = ; - } else { - Component = ; + Component = ; + } else if ( + model_type === NodeType.children || + model_type === NodeType.parent || + model_type === NodeType.current + ) { + Component = ; } return (
{ const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); const { message } = App.useApp(); - let sourceAnchorIdx, targetAnchorIdx; + let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx; + let dragSourceNode; useEffect(() => { initMenu(); @@ -35,9 +36,8 @@ const EditPipeline = () => { }, []); const onDragEnd = (val) => { - const _x = val.x; - const _y = val.y; - const point = graph.getPointByClient(_x, _y); + const { x, y } = val; + const point = graph.getPointByClient(x, y); // 元模型 const model = { ...val, @@ -46,8 +46,8 @@ const EditPipeline = () => { id: val.component_name + '-' + s8(), isCluster: false, }; - console.log('model', model); - graph.addItem('node', model, true); + // console.log('model', model); + graph.addItem('node', model, false); }; const formChange = (val) => { if (graph) { @@ -83,11 +83,11 @@ const EditPipeline = () => { return; } - const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); - if (propsError) { - message.error('基本信息必填项需配置'); - return; - } + // const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); + // if (propsError) { + // message.error('基本信息必填项需配置'); + // return; + // } propsRef.current.propClose(); setTimeout(() => { const data = graph.save(); @@ -110,6 +110,10 @@ const EditPipeline = () => { }; const getGraphData = (data) => { if (graph) { + // 修改历史数据有蓝色边框的问题 + data.nodes.forEach((item) => { + item.style.stroke = '#fff'; + }); graph.data(data); graph.render(); } else { @@ -118,7 +122,6 @@ const EditPipeline = () => { }, 500); } }; - const processParallelEdgesOnAnchorPoint = ( edges, offsetDiff = 15, @@ -230,6 +233,15 @@ const EditPipeline = () => { } return edges; }; + // 判断两个节点之间是否有边 + const hasEdge = (source, target) => { + const neighbors = source.getNeighbors(); + for (const node of neighbors) { + // 新建边的时候,获取的 neighbors 的数据有问题,不全是 INode 类型,可能没有 getID 方法 + if (node.getID?.() === target.getID?.()) return true; + } + return false; + }; const cloneElement = (item) => { console.log(item); let data = graph.save(); @@ -254,15 +266,13 @@ const EditPipeline = () => { } }); }; - const handlerContextMenu = (e) => { - e.stopPropagation(); - // this.menuType = e.item._cfg.type; - }; // 上下文菜单 const initMenu = () => { // const selectedNodes = this.selectedNodes; contextMenu = new G6.Menu({ getContent(evt) { + const type = evt.item.getType(); + const cloneDisplay = type === 'node' ? 'block' : 'none'; return `
    - -
  • 复制
  • +
  • 复制
  • 删除
`; }, @@ -314,11 +323,11 @@ const EditPipeline = () => { getAnchorPoints(cfg) { return ( cfg.anchorPoints || [ - // 四个 + // 四个,上下左右 [0.5, 0], [0.5, 1], - // [0, 0.5], - // [1, 0.5], + [0, 0.5], + [1, 0.5], ] ); }, @@ -344,6 +353,7 @@ const EditPipeline = () => { textAlign: 'left', textBaseline: 'middle', fill: '#000', + cursor: 'pointer', }, name: 'text-shape', draggable: true, @@ -360,6 +370,7 @@ const EditPipeline = () => { fill: '#fff', stroke: '#a4a4a5', cursor: 'crosshair', + lineWidth: 1, }, 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 @@ -380,10 +391,9 @@ const EditPipeline = () => { // if (value || point.get('links') > 0) point.show(); // else point.hide(); // }); - const group = item.getContainer(); const shape = group.get('children')[0]; - const anchorPoints = group.findAll((ele) => ele.get('name') === 'anchor-point'); + const anchorPoints = group.findAll((item) => item.get('name') === 'anchor-point'); if (name === 'hover') { if (value) { shape.attr('stroke', themes['primaryColor']); @@ -396,6 +406,18 @@ const EditPipeline = () => { point.hide(); }); } + } else if (name === 'drag') { + if (sourceAnchorIdx !== null && sourceAnchorIdx !== undefined) { + const anchorPoint = anchorPoints[sourceAnchorIdx]; + anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); + anchorPoint.attr('lineWidth', value ? 2 : 1); + } + } else if (name === 'drop') { + if (dropAnchorIdx !== null && dropAnchorIdx !== undefined) { + const anchorPoint = anchorPoints[dropAnchorIdx]; + anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5'); + anchorPoint.attr('lineWidth', value ? 2 : 1); + } } }, }, @@ -407,10 +429,14 @@ const EditPipeline = () => { width: graphRef.current.clientWidth || 500, height: graphRef.current.clientHeight || '100%', animate: false, - groupByTypes: false, + groupByTypes: true, fitView: true, plugins: [contextMenu], - enabledStack: true, + enabledStack: false, + fitView: true, + minZoom: 0.5, + maxZoom: 5, + fitViewPadding: 300, modes: { default: [ // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles @@ -420,10 +446,6 @@ const EditPipeline = () => { 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 { @@ -434,11 +456,17 @@ const EditPipeline = () => { 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 + dragSourceNode = e.item; return true; }, shouldEnd: (e) => { // avoid ending at other shapes on the node if (e.target && e.target.get('name') !== 'anchor-point') return false; + if (!dragSourceNode || !e.item) return false; + // 不允许连接自己 + if (dragSourceNode.getID() === e.item.getID()) return false; + // 两个节点不允许多条边 + if (hasEdge(dragSourceNode, e.item)) 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 @@ -450,22 +478,11 @@ const EditPipeline = () => { }, 'drag-canvas', 'zoom-canvas', - // 'brush-select', - 'drag-combo', - ], - altSelect: [ - { - type: 'brush-select', - trigger: 'drag', - }, - 'drag-node', ], }, - defaultNode: { type: 'rect-node', size: [110, 36], - labelCfg: { style: { fill: 'transparent', @@ -488,8 +505,7 @@ const EditPipeline = () => { }, }, defaultEdge: { - //type: 'cubic-vertical', - + // type: 'cubic-vertical', style: { endArrow: { // 设置终点箭头 @@ -503,15 +519,8 @@ const EditPipeline = () => { stroke: '#CDD0DC', radius: 1, }, - nodeStateStyle: { - hover: { - opacity: 1, - stroke: '#8fe8ff', - }, - }, labelCfg: { autoRotate: true, - // refY: 10, style: { fontSize: 10, fill: '#FFF', @@ -528,20 +537,16 @@ const EditPipeline = () => { cursor: 'pointer', }, }, - // linkCenter: true, - fitView: true, - minZoom: 0.5, - maxZoom: 5, - fitViewPadding: 300, }); graph.on('node:click', (e) => { e.stopPropagation(); if (e.target.get('name') !== 'anchor-point' && e.item) { - // graph.setItemState(e.item, 'nodeClicked', true); + // 获取所有的上游节点 const parentNodes = findAllParentNodes(graph, e.item); // 如果没有打开过全局参数抽屉,获取不到全局参数 const globalParams = paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; + // 打开节点编辑抽屉 propsRef.current.showDrawer(e, globalParams, parentNodes); } }); @@ -550,23 +555,26 @@ const EditPipeline = () => { graph.updateItem(e.edge, { sourceAnchor: sourceAnchorIdx, targetAnchor: targetAnchorIdx, + type: + targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal', }); // update the curveOffset for parallel edges - const edges = graph.save().edges; - processParallelEdgesOnAnchorPoint(edges); - graph.getEdges().forEach((edge, i) => { - graph.updateItem(edge, { - curveOffset: edges[i].curveOffset, - curvePosition: edges[i].curvePosition, - }); - }); + // const edges = graph.save().edges; + // processParallelEdgesOnAnchorPoint(edges); + // graph.getEdges().forEach((edge, i) => { + // graph.updateItem(edge, { + // curveOffset: edges[i].curveOffset, + // curvePosition: edges[i].curvePosition, + // }); + // }); }); + // 删除边时,修改 anchor-point 的 links 值 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; + const { source, target, sourceAnchor, targetAnchor } = e.item; + const sourceNode = graph.findById(source); + const targetNode = graph.findById(target); if (sourceNode && !isNaN(sourceAnchor)) { const sourceAnchorShape = sourceNode .getContainer() @@ -587,9 +595,10 @@ const EditPipeline = () => { } } }); - // after clicking on the first node, the edge is created, update the sourceAnchor + // after drag on the first node, the edge is created, update the sourceAnchor graph.on('afteradditem', (e) => { - if (e.item && e.item.getType() === 'edge') { + const sourceAnchor = e.item.getModel().sourceAnchor; + if (e.item && e.item.getType() === 'edge' && !sourceAnchor) { graph.updateItem(e.item, { sourceAnchor: sourceAnchorIdx, }); @@ -601,17 +610,34 @@ const EditPipeline = () => { graph.on('node:mouseleave', (e) => { graph.setItemState(e.item, 'hover', false); }); - graph.on('node:dragenter', (e) => { + graph.on('node:dragstart', (e) => { graph.setItemState(e.item, 'hover', true); + graph.setItemState(e.item, 'drag', true); }); - graph.on('node:dragleave', (e) => { + graph.on('node:dragend', (e) => { graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drag', false); }); - graph.on('node:dragstart', (e) => { + graph.on('node:dragenter', (e) => { + if (e.item?.getID() === dragSourceNode?.getID()) return; graph.setItemState(e.item, 'hover', true); + if (e.target.get('name') === 'anchor-point') { + dropAnchorIdx = e.target.get('anchorPointIdx'); + graph.setItemState(e.item, 'drop', true); + } else { + graph.setItemState(e.item, 'drop', false); + } }); - graph.on('node:drag', (e) => { - graph.setItemState(e.item, 'hover', true); + graph.on('node:dragleave', (e) => { + if (e.item?.getID() === dragSourceNode?.getID()) return; + graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drop', false); + dropAnchorIdx = undefined; + }); + graph.on('node:drop', (e) => { + graph.setItemState(e.item, 'hover', false); + graph.setItemState(e.item, 'drop', false); + dropAnchorIdx = undefined; }); window.onresize = () => { if (!graph || graph.get('destroyed')) return; diff --git a/react-ui/src/pages/Pipeline/index.jsx b/react-ui/src/pages/Pipeline/index.jsx index cf5fb571..12dbaae8 100644 --- a/react-ui/src/pages/Pipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/index.jsx @@ -43,32 +43,30 @@ const Pipeline = () => { }; const routeToEdit = (e, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/template/${record.id}/${record.name}` }); + navgite({ pathname: `/pipeline/template/${record.id}` }); }; const showModal = () => { form.resetFields(); + setFormId(null); setDialogTitle('新建流水线'); setIsModalOpen(true); }; - const handleOk = () => { - console.log(1111); - setIsModalOpen(false); - }; const handleCancel = () => { setIsModalOpen(false); }; const onFinish = (values) => { if (formId) { editWorkflow({ ...values, id: formId }).then((ret) => { + setIsModalOpen(false); message.success('编辑成功'); getList(); - setIsModalOpen(false); }); } else { addWorkflow(values).then((ret) => { - console.log(ret); + setIsModalOpen(false); + message.success('新建成功'); if (ret.code === 200) { - navgite({ pathname: `/pipeline/template/${ret.data.id}/${ret.data.name}` }); + navgite({ pathname: `/pipeline/template/${ret.data.id}` }); } }); } diff --git a/react-ui/src/pages/User/Login/index.tsx b/react-ui/src/pages/User/Login/index.tsx index f531cbad..220a2b17 100644 --- a/react-ui/src/pages/User/Login/index.tsx +++ b/react-ui/src/pages/User/Login/index.tsx @@ -344,7 +344,7 @@ const Login: React.FC = () => { }} placeholder={intl.formatMessage({ id: 'pages.login.password.placeholder', - defaultMessage: '密码: admin123', + defaultMessage: '请输入密码', })} rules={[ { @@ -374,7 +374,7 @@ const Login: React.FC = () => { required: true, message: ( ), diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index 8a976abc..4934396c 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -9,12 +9,19 @@ import { clearSessionToken, getAccessToken } from './access'; import { setRemoteMenu } from './services/session'; import { gotoLoginPage } from './utils/ui'; +// [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. +const popupError = (error: string) => { + // 直接调用 message.error 有时候不弹出来 + setTimeout(() => { + message.error(error); + }, 100); +}; + /** * Umi Max 网络请求配置 * @doc https://umijs.org/docs/max/request#配置 */ export const requestConfig: RequestConfig = { - errorConfig: {}, requestInterceptors: [ (url: string, options: AxiosRequestConfig) => { const headers = options.headers ?? {}; @@ -30,26 +37,32 @@ export const requestConfig: RequestConfig = { }, ], responseInterceptors: [ - (response: AxiosResponse) => { - const { status, data } = response || {}; - if (status >= 200 && status < 300) { - if (data && (data instanceof Blob || data.code === 200)) { - return response; - } else if (data && data.code === 401) { - clearSessionToken(); - setRemoteMenu(null); - gotoLoginPage(false); - message.error('请重新登录'); - return Promise.reject(response); + [ + (response: AxiosResponse) => { + const { status, data } = response || {}; + console.log(message, data); + if (status >= 200 && status < 300) { + if (data && (data instanceof Blob || data.code === 200)) { + return response; + } else if (data && data.code === 401) { + clearSessionToken(); + setRemoteMenu(null); + gotoLoginPage(false); + popupError('请重新登录'); + return Promise.reject(response); + } else { + popupError(data?.msg ?? '请求失败'); + return Promise.reject(response); + } } else { - console.log(message, data); - message.error(data?.msg ?? '请求失败'); + popupError('请求失败'); return Promise.reject(response); } - } else { - message.error('请求失败'); - return Promise.reject(response); - } - }, + }, + (error: Error) => { + popupError(error.message ?? '请求失败'); + return Promise.reject(error); + }, + ], ], }; diff --git a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java index 91ed6847..4680285e 100644 --- a/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java +++ b/ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/scheduling/ExperimentInstanceStatusTask.java @@ -34,7 +34,7 @@ public class ExperimentInstanceStatusTask { private ModelDependencyDao modelDependencyDao; private List experimentIds = new ArrayList<>(); - @Scheduled(cron = "0/30 * * * * ?") // 每30S执行一次 + @Scheduled(cron = "0/14 * * * * ?") // 每30S执行一次 public void executeExperimentInsStatus() throws IOException { // 首先查到所有非终止态的实验实例 List experimentInsList = experimentInsService.queryByExperimentIsNotTerminated(); @@ -49,7 +49,7 @@ public class ExperimentInstanceStatusTask { }catch (Exception e){ experimentIns.setStatus("Failed"); } - if (!StringUtils.equals(oldStatus,experimentIns.getStatus())){ +// if (!StringUtils.equals(oldStatus,experimentIns.getStatus())){ experimentIns.setUpdateTime(new Date()); // 线程安全的添加操作 synchronized (experimentIds) { @@ -57,7 +57,7 @@ public class ExperimentInstanceStatusTask { } updateList.add(experimentIns); - } +// } // experimentInsDao.update(experimentIns); } @@ -105,7 +105,7 @@ public class ExperimentInstanceStatusTask { } } - @Scheduled(cron = "0/30 * * * * ?") // / 每30S执行一次 + @Scheduled(cron = "0/17 * * * * ?") // / 每30S执行一次 public void executeExperimentStatus() throws IOException { if (experimentIds.size()==0){ return;