diff --git a/frontend/desktop/src/config/i18n/cn.js b/frontend/desktop/src/config/i18n/cn.js index 8927296d98..4b6e116144 100644 --- a/frontend/desktop/src/config/i18n/cn.js +++ b/frontend/desktop/src/config/i18n/cn.js @@ -1766,7 +1766,7 @@ const cn = { '跳过节点将忽略失败继续往后执行': '跳过节点将忽略失败继续往后执行', '确定重试子流程【n】 ?': '确定重试子流程【{n}】 ?', '确定重试节点【n】 ?': '确定重试节点【{n}】 ?', - '子任务中重试无法修改参数,如需修改请在根任务中操作': '子任务中重试无法修改参数,如需修改请在根任务中操作' + '非根节点仅支持以原参数进行重试': '非根节点仅支持以原参数进行重试' } export default cn diff --git a/frontend/desktop/src/config/i18n/en.js b/frontend/desktop/src/config/i18n/en.js index 5589bcd3d5..5c907c1b7e 100644 --- a/frontend/desktop/src/config/i18n/en.js +++ b/frontend/desktop/src/config/i18n/en.js @@ -1800,7 +1800,7 @@ const en = { '跳过节点将忽略失败继续往后执行': 'Skipping node will ignore the failure and continue executing the next step.', '确定重试子流程【n】 ?': 'Retry this subflow [ {n} ] ?', '确定重试节点【n】 ?': 'Retry this node [ {n} ] ?', - '子任务中重试无法修改参数,如需修改请在根任务中操作': 'Retrying in a sub-task does not allow modifying parameters. If modification is needed, please operate in the root task.' + '非根节点仅支持以原参数进行重试': 'Non-root nodes only support retrying with the original parameters.' } export default en diff --git a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue index 7bcca77fdf..123485ea48 100644 --- a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue +++ b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo.vue @@ -54,12 +54,11 @@ v-bk-tooltips.top="$t('放大')" @click="onZoomIn"> - - + +
+ +
{ - this.setSubprocessCanvasZoom() + this.initCanvasZoom() }) } }, @@ -701,13 +700,13 @@ * 兼容旧版本子流程节点输入数据 * 获取子流程输入参数 (subflow_detail_var 标识当前为子流程节点详情) */ + constants = { subflow_detail_var: true, ...inputsInfo } inputsInfo = Object.values(this.pipelineData.constants).reduce((acc, cur) => { if (cur.show_type === 'show') { acc[cur.key] = cur.value } return acc }, {}) - constants = { subflow_detail_var: true, ...inputsInfo } } for (const key in inputsInfo) { renderData[key] = inputsInfo[key] @@ -997,7 +996,9 @@ const time = name === 'history' ? 300 : 0 setTimeout(() => { const scrollBoxDom = document.querySelector('.scroll-box') - scrollBoxDom.scrollTo({ top: scrollBoxDom.scrollHeight, behavior: 'smooth' }) + const subProcessCanvasDom = document.querySelector('.sub-process') + const { height = 0 } = subProcessCanvasDom.getBoundingClientRect() + scrollBoxDom.scrollTo({ top: height, behavior: 'smooth' }) }, time) } }, @@ -1021,7 +1022,7 @@ if (node.isSubProcess || updateCanvas) { this.canvasRandomKey = new Date().getTime() this.$nextTick(() => { - this.setSubprocessCanvasZoom() + this.initCanvasZoom() }) } this.$emit('onClickTreeNode', node) @@ -1040,23 +1041,11 @@ } }) }, - setSubprocessCanvasZoom () { - const flowDom = this.$el.querySelector('.sub-flow') - if (!flowDom) return - const { height = 0, width = 0 } = flowDom.getBoundingClientRect() - let jsFlowInstance = this.$refs.subProcessCanvas - jsFlowInstance = jsFlowInstance.$refs.jsFlow - jsFlowInstance && jsFlowInstance.zoomOut(0.75, width / 2, height / 2) - const { start_event, flows } = this.subProcessPipeline - const firstNodeId = flows[start_event.outgoing].target - const firstNodeDom = document.querySelector(`#${firstNodeId}`) - const { y } = firstNodeDom.getBoundingClientRect() - jsFlowInstance.setCanvasPosition(0, 180 - y) - }, // 移动画布,将节点放到画布中央 moveNodeToView (id) { // 判断dom是否存在当前视图中 const nodeEl = document.querySelector(`#${id} .canvas-node-item`) + if (!nodeEl) return const isInViewPort = this.judgeInViewPort(nodeEl) // 如果不存在需要将节点挪到画布中间 if (!isInViewPort) { @@ -1079,6 +1068,34 @@ const { top, left } = element.getBoundingClientRect() return top > canvasTop && top < canvasTop + height && left > canvasLeft && left < canvasLeft + width }, + // 画布初始化时缩放比偏移 + initCanvasZoom () { + // 获取画布上下左右最大坐标 + const xList = this.canvasData.locations.map(node => node.x) + const yList = this.canvasData.locations.map(node => node.y) + const minX = Math.min(...xList) + const maxX = Math.max(...xList) + const minY = Math.min(...yList) + const maxY = Math.max(...yList) + const maxXNodeId = this.canvasData.locations.find(node => node.x === maxX).id + const maxYNodeId = this.canvasData.locations.find(node => node.y === maxY).id + const { width } = this.$el.querySelector(`#${maxXNodeId}`).getBoundingClientRect() + const { height } = this.$el.querySelector(`#${maxYNodeId}`).getBoundingClientRect() + const netHeight = maxY - minY + height + 60 + const netWidth = maxX - minX + width + 80 + const subprocessDom = this.$el.querySelector('.sub-process') + const { height: canvasHeight, width: canvasWidth } = subprocessDom.getBoundingClientRect() + // 最大比例0.75 + let ratio = Math.min(canvasHeight / netHeight, canvasWidth / netWidth) + ratio = ratio > 0.75 ? 0.75 : ratio + let jsFlowInstance = this.$refs.subProcessCanvas + jsFlowInstance = jsFlowInstance.$refs.jsFlow + jsFlowInstance && jsFlowInstance.zoomOut(ratio, 0, 0) + // 设置偏移量 + const offsetX = canvasWidth / 2 - (minX - 30 + netWidth / 2) * ratio + const offsetY = canvasHeight / 2 - (minY + netHeight / 2) * ratio + jsFlowInstance.setCanvasPosition(offsetX, offsetY, true) + }, toggleNodeActive (id, isActive) { const node = document.getElementById(id) @@ -1265,6 +1282,7 @@ const pipelineTree = JSON.parse(resp.pipeline_tree) const parentInfo = { parentId: nodeInfo.parentId ? nodeInfo.parentId + '-' + nodeInfo.id : nodeInfo.id, + independentId: nodeInfo.id, parentLevel: nodeInfo.nodeLevel, lastLevelStyle: 'margin-left: 0px', taskId @@ -1294,7 +1312,7 @@ this.$set(nodeActivity, 'pipeline', { ...pipelineTree, taskId }) this.canvasRandomKey = new Date().getTime() this.$nextTick(() => { - this.setSubprocessCanvasZoom() + this.initCanvasZoom() }) } } catch (error) { @@ -1394,7 +1412,8 @@ const info = { name: this.executeInfo.name, taskId: this.subProcessTaskId, - isSubProcessNode: this.isSubProcessNode + isSubProcessNode: this.isSubProcessNode, + isSubNode: !!this.nodeDetailConfig.root_node } this.$emit('onRetryClick', this.nodeDetailConfig.node_id, info) }, @@ -1420,6 +1439,44 @@ }, onContinueClick () { this.$emit('onContinueClick', this.nodeDetailConfig.node_id, this.subProcessTaskId) + }, + handleMousedown (event) { + this.updateResizeMaskStyle() + this.updateResizeProxyStyle() + this.canvasExpand = false + document.addEventListener('mousemove', this.handleMouseMove) + document.addEventListener('mouseup', this.handleMouseUp) + }, + handleMouseMove (event) { + const flowDom = this.$el.querySelector('.sub-flow') + const { top: flowTop } = flowDom.getBoundingClientRect() + let top = event.clientY - flowTop + let maxHeight = window.innerHeight - 180 + maxHeight = maxHeight - (this.isShowActionWrap ? 48 : 0) + top = top > maxHeight ? maxHeight : top + top = top < 160 ? 160 : top + const resizeProxy = this.$refs.resizeProxy + resizeProxy.style.top = `${top}px` + }, + updateResizeMaskStyle () { + const resizeMask = this.$refs.resizeMask + resizeMask.style.display = 'block' + resizeMask.style.cursor = 'row-resize' + }, + updateResizeProxyStyle () { + const resizeProxy = this.$refs.resizeProxy + resizeProxy.style.visibility = 'visible' + }, + handleMouseUp () { + const resizeMask = this.$refs.resizeMask + const resizeProxy = this.$refs.resizeProxy + resizeProxy.style.visibility = 'hidden' + resizeMask.style.display = 'none' + const subProcessDom = document.querySelector('.sub-process') + subProcessDom.style.height = resizeProxy.style.top + this.canvasExpand = true + document.removeEventListener('mousemove', this.handleMouseMove) + document.removeEventListener('mouseup', this.handleMouseUp) } } } @@ -1530,7 +1587,7 @@ } .sub-process { flex-shrink: 0; - height: 160px; + height: 320px; margin: 0 25px 8px 15px; position: relative; background: #e1e4e8; @@ -1573,7 +1630,7 @@ } } .flow-option { - width: 96px; + width: 68px; height: 32px; position: absolute; bottom: 16px; @@ -1589,11 +1646,8 @@ border-radius: 2px; i { cursor: pointer; - &:nth-child(2) { - margin: 0 14px; - } &:last-child { - font-size: 14px; + margin-left: 14px; } &:hover { color: #3a84ff; @@ -1601,10 +1655,9 @@ } } &.canvas-expand { - height: calc(100% - 85px); & + div { .log-section { - height: 858px; + height: 768px; } } } @@ -1715,5 +1768,58 @@ } } } + .resize-trigger { + height: 5px; + width: calc(100% + 40px); + position: absolute; + left: -15px; + bottom: -5px; + cursor: row-resize; + z-index: 3; + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 1px; + width: 100%; + } + &::after { + content: ""; + position: absolute; + top: 5px; + right: 50%; + width: 2px; + height: 2px; + color: #979ba5; + transform: translate3d(0,-50%,0); + background: currentColor; + box-shadow: 4px 0 0 0 currentColor,8px 0 0 0 currentColor,-4px 0 0 0 currentColor,-8px 0 0 0 currentColor; + } + &:hover::before { + background-color: #3a84ff; + } + } + .resize-proxy { + visibility: hidden; + position: absolute; + pointer-events: none; + z-index: 9999; + &.top { + top: 320px; + left: -15px; + width: calc(100% + 40px); + border-top: 1px dashed #3a84ff; + } + } + .resize-mask { + display: none; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 9999; + } } diff --git a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/ExecuteInfoForm.vue b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/ExecuteInfoForm.vue index 0b42a18063..80aeb626c0 100644 --- a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/ExecuteInfoForm.vue +++ b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/ExecuteInfoForm.vue @@ -269,7 +269,7 @@ async initData () { try { // 获取对应模板配置 - const tplConfig = this.getNodeSnapshotConfig ? {} : await this.getNodeSnapshotConfig(this.nodeDetailConfig) + const tplConfig = await this.getNodeSnapshotConfig(this.nodeDetailConfig) this.templateConfig = tplConfig.data || { ...this.nodeActivity, isOldData: true } || {} if (this.isSubProcessNode || this.nodeActivity.type === 'SubProcess') { // 子流程任务节点 // tplConfig.data为null为该功能之前的旧数据,没有original_template_id字段的,不调接口 diff --git a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/InputParams.vue b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/InputParams.vue index 7b62bf863d..1df95a09f4 100644 --- a/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/InputParams.vue +++ b/frontend/desktop/src/pages/task/TaskExecute/ExecuteInfo/InputParams.vue @@ -24,7 +24,7 @@ :scheme="renderConfig" :form-option="renderOption" :constants="inputConstants" - v-model="inputRenderDate"> + v-model="inputRenderData"> @@ -32,7 +32,7 @@ + :value="inputRenderData"> @@ -99,7 +99,7 @@ }, renderKey: null, inputConstants: {}, - inputRenderDate: {}, + inputRenderData: {}, isExpand: true } }, @@ -132,7 +132,8 @@ } this.inputConstants = constants }, - deep: true + deep: true, + immediate: true }, renderData: { handler (val) { @@ -143,11 +144,11 @@ Object.keys(this.renderData).forEach(key => { const value = this.renderData[key] if (/^\${[^${}]+}$/.test(value) && key in this.inputConstants) { - this.renderData[key] = this.inputConstants[key] + renderData[key] = this.inputConstants[key] } }) } - this.inputRenderDate = renderData + this.inputRenderData = renderData }, deep: true, immediate: true diff --git a/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue b/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue index 79161f4c2a..2028ce31f2 100644 --- a/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue +++ b/frontend/desktop/src/pages/task/TaskExecute/TaskOperation.vue @@ -1009,14 +1009,15 @@ }, async onRetryClick (id, info) { try { + const { taskId } = info || {} + this.subProcessTaskId = taskId // 独立子任务重试使用二次确认弹框 - if (this.isChildTaskFlow) { + if (this.isChildTaskFlow || (info && info.isSubNode)) { const nodeConfig = this.nodeTreePipelineData.activities[id] - const isSubProcessNode = nodeConfig.component.code === 'subprocess_plugin' - const title = isSubProcessNode - ? this.$t('确定重试子流程【n】 ?', { n: nodeConfig.name }) - : this.$t('确定重试节点【n】 ?', { n: nodeConfig.name }) - const subTitle = this.$t('子任务中重试无法修改参数,如需修改请在根任务中操作') + const name = info ? info.name : nodeConfig.name + const title = info.isSubProcessNode + ? this.$t('确定重试子流程【n】 ?', { n: name }) + : this.$t('确定重试节点【n】 ?', { n: name }) const h = this.$createElement this.$bkInfo({ subHeader: h('div', { class: 'custom-header' }, [ @@ -1026,12 +1027,12 @@ name: 'bk-overflow-tips' }] }, [title]), - isSubProcessNode ? h('div', { + h('div', { class: 'custom-header-sub-title bk-dialog-header-inner', directives: [{ name: 'bk-overflow-tips' }] - }, [subTitle]) : '' + }, [this.$t('非根节点仅支持以原参数进行重试')]) ]), width: 450, extCls: 'dialog-custom-header-title', @@ -1045,32 +1046,18 @@ }) return } - const { taskId } = info || {} - this.subProcessTaskId = taskId const resp = await this.getInstanceRetryParams({ id: taskId || this.instance_id }) if (resp.data.enable) { this.openNodeInfoPanel('retryNode', i18n.t('重试节点')) - // 子流程任务节点,不需要重新获取节点配置 - if (!taskId) { - this.setNodeDetailConfig(id) - } + this.setNodeDetailConfig(id) if (this.nodeDetailConfig.component_code) { await this.loadNodeInfo(id) } } else { - // 子流程任务节点,不需要重新获取节点配置 - if (!taskId) { - this.setNodeDetailConfig(id) - } - let isSubProcessNode = false - if (info) { - isSubProcessNode = info.isSubProcessNode - this.retryNodeName = info.name - } else { - const nodeConfig = this.nodeTreePipelineData.activities[id] - isSubProcessNode = nodeConfig?.component.code === 'subprocess_plugin' - this.retryNodeName = nodeConfig.name - } + this.setNodeDetailConfig(id) + const nodeConfig = this.nodeTreePipelineData.activities[id] + const isSubProcessNode = nodeConfig?.component.code === 'subprocess_plugin' + this.retryNodeName = nodeConfig.name this.openNodeInfoPanel('modifyParams', isSubProcessNode ? i18n.t('重试子流程') : i18n.t('重试节点')) this.retryNodeId = id } @@ -1410,7 +1397,7 @@ const endNode = tools.deepClone(data.end_event) const fstLine = startNode.outgoing const nodeId = data.flows[fstLine].target - const { parentId, parentLevel, lastLevelStyle, taskId } = pipelineInfo + const { parentId, independentId, parentLevel, lastLevelStyle, taskId } = pipelineInfo let marginLeft if (lastLevelStyle) { marginLeft = lastLevelStyle.match(/[0-9]+/g)[0] @@ -1439,7 +1426,7 @@ }) this.getNodeTargetMaps(data) this.getNodeSourceMaps(data) - const nodeInfo = { id: nodeId, parentId, parentLevel, lastLevelStyle, taskId } + const nodeInfo = { id: nodeId, parentId, independentId, parentLevel, lastLevelStyle, taskId } this.retrieveLines(data.id, data, nodeInfo, orderedData) orderedData.push(endEvent) // 过滤root最上层汇聚网关 @@ -1461,6 +1448,7 @@ branchId, nodeLevel = 1, parentId, + independentId, parentLevel, lastLevelStyle, lastId, @@ -1519,6 +1507,11 @@ isLevelUp, taskId } + if (parentId) { + let subprocessStack = independentId ? parentId.split(independentId)[1] : parentId + subprocessStack = subprocessStack?.split('-') || [] + treeItem.subprocessStack = subprocessStack.filter(item => item) + } let marginLeft = 0 if (treeItem.nodeLevel === 1) { marginLeft = 0 @@ -1633,6 +1626,7 @@ if (this.nodeIds[componentData.id]) { delete this.nodeIds[componentData.id] } + parentInfo.independentId = id treeItem.children = this.getOrderedTree(componentData, parentInfo) } treeItem.type = 'SubProcess' @@ -1703,6 +1697,7 @@ if (conditions.length) { conditions.forEach(item => { item.style = `margin-left: ${item.parentId ? 16 : marginLeft + 33}px` + item.subprocessStack = treeItem.subprocessStack treeItem.children.push(item) this.retrieveLines( flowId, @@ -1712,6 +1707,7 @@ branchId: item.id, nodeLevel: item.nodeLevel, parentId, + independentId, gatewayId: id, lastId: item.id }, @@ -2139,7 +2135,7 @@ this.updateTaskStatus(id) }, async onClickTreeNode (node) { - const { id, conditionType, parentId, taskId } = node + const { id, conditionType, parentId, taskId, subprocessStack = [] } = node if (this.nodeDetailConfig.node_id) { this.updateNodeActived(this.nodeDetailConfig.node_id, false) } @@ -2174,17 +2170,11 @@ this.isCondition = true this.conditionData = { ...node } } - let subprocessStack = parentId ? parentId.split('-') : [] - let instance_id = this.instance_id - if (taskId) { - subprocessStack = [] - instance_id = taskId - } this.nodeDetailConfig = { component_code: code, version: version, node_id: nodeId, - instance_id, + instance_id: taskId || this.instance_id, taskId, root_node: parentId, subprocess_stack: JSON.stringify(subprocessStack),