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),