feat(devops): 扩展流水线任务构建器功能并优化界面布局

- 将Pipeline和PipeTask组件宽度从240px调整为260px
- 为PipeTask组件的startTimeMillis和durationMillis属性添加默认值
- 在PipeTaskBuilder中移除预设的节点选项表单并改为动态配置
- 为节点按钮添加触发条件选择功能,支持按状态显示不同按钮
- 优化节点参数配置界面,支持动态添加参数选项输入框
- 新增节点配置预览功能,实时显示JSON格式的配置信息
- 为输入框添加占位符提示文本提升用户体验
- 重构数据绑定逻辑,通过watch监听器实现配置同步
This commit is contained in:
2026-03-09 19:38:48 +08:00
parent e81d85a92a
commit 21f29a04fc
3 changed files with 318 additions and 74 deletions
@@ -44,8 +44,7 @@
:key="index"
v-prevent-re-click
type="text"
:style="`width: ${item.text && item.text.length >= 4 ? '74px': '26px'
};color: ${item.color ? item.color : statusColorMap.running};display: ${item.condition ===
:style="`color: ${item.color ? item.color : statusColorMap.running};display: ${item.condition ===
'all' ?
'inline-block':
(item.condition === status ? 'inline-block': 'none')};overflow: hidden;text-overflow: ellipsis;`"
@@ -101,14 +100,18 @@ export default {
*/
startTimeMillis: {
type: Number,
required: true
required: false,
default: function() {
return new Date().getTime()
}
},
/**
* 持续时间。单位:秒
*/
durationMillis: {
type: Number,
required: true
required: false,
default: 0
},
/**
* 状态
@@ -200,8 +203,8 @@ export default {
taskWindowStyle() {
const colorHex = this.getColorHexByStatus(this.status)
return {
width: '240px',
maxWidth: '240px',
width: '260px',
maxWidth: '260px',
height: '100px',
maxHeight: '100px',
border: `1px solid ${colorHex}`,
@@ -54,35 +54,14 @@
/>
</div>
</CutePage>
<CutePage>
<el-divider content-position="left">节点选项(预览)</el-divider>
<el-form ref="form" :model="parameters" label-width="160px">
<el-form-item
v-for="(paramCode, index) in Object.keys(parameters)"
:key="`paramKey_${index}`"
:label="`${parameters[paramCode].name}(${paramCode})`"
>
<el-select
v-model="parameters[paramCode].value"
:placeholder="`请选择${parameters[paramCode].name}`"
clearable
filterable
>
<el-option v-for="(item, index2) in parameters[paramCode].dataSource" :key="`item_${index2}`" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
</CutePage>
</el-col>
<el-col :span="12">
<CutePage>
<el-divider content-position="left">节点参数设置</el-divider>
<el-form label-width="140px">
<el-form-item label="节点名称">
<el-input v-model="name" />
<el-input v-model="name" placeholder="请输入节点中文名称。例如:初始化" />
</el-form-item>
<el-form-item label="节点编码">
<el-input v-model="code" />
<el-input v-model="code" placeholder="请输入节点英文名称。例如:initial" />
</el-form-item>
<el-form-item label="是否支持点击">
<el-switch v-model="click" />
@@ -109,7 +88,22 @@
<el-table :data="buttonList">
<el-table-column prop="text" label="按钮名称" width="180">
<template v-slot="scope">
<el-input v-model="scope.row.text" />
<el-input v-model="scope.row.text" placeholder="请输入按钮名称" />
</template>
</el-table-column>
<el-table-column prop="text" label="触发条件" width="180">
<template v-slot="scope">
<el-select
v-model="scope.row.condition"
placeholder="请选择触发条件"
clearable
>
<el-option label="所有" value="all" />
<el-option label="待执行" value="pending" />
<el-option label="执行中" value="running" />
<el-option label="执行成功" value="success" />
<el-option label="执行失败" value="failed" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="type" label="按钮类型" width="160">
@@ -126,7 +120,7 @@
</el-table-column>
<el-table-column label="执行动作">
<template v-slot="scope">
<el-table v-if="scope.row.type === 'link'" :data="buttonList">
<el-table v-if="scope.row.type === 'link'" :data="[scope.row]">
<el-table-column prop="blank" label="是否新开窗口" width="100">
<template v-slot="innerScope">
<el-switch v-model="innerScope.row.blank" />
@@ -134,11 +128,11 @@
</el-table-column>
<el-table-column prop="linkUrl" label="打开链接">
<template v-slot="innerScope">
<el-input v-model="innerScope.row.linkUrl" />
<el-input v-model="innerScope.row.linkUrl" placeholder="请输入or黏贴第三方链接" />
</template>
</el-table-column>
</el-table>
<el-table v-if="scope.row.type === 'execute'" :data="buttonList">
<el-table v-if="scope.row.type === 'execute'" :data="[scope.row]">
<el-table-column prop="method" label="请求类型" width="160">
<template v-slot="innerScope">
<el-select
@@ -153,7 +147,7 @@
</el-table-column>
<el-table-column prop="requestUrl" label="请求地址">
<template v-slot="innerScope">
<el-input v-model="innerScope.row.requestUrl" />
<el-input v-model="innerScope.row.requestUrl" placeholder="请输入or黏贴请求地址。这里应为系统内部地址,具体参考 src/api 文件夹" />
</template>
</el-table-column>
</el-table>
@@ -166,30 +160,33 @@
<el-table :data="parameterList">
<el-table-column prop="name" label="参数名称">
<template v-slot="nodeParamScope">
<el-input v-model="nodeParamScope.row.name" />
<el-input v-model="nodeParamScope.row.name" placeholder="参数中文名" />
</template>
</el-table-column>
<el-table-column prop="code" label="参数编码">
<template v-slot="nodeParamScope">
<el-input v-model="nodeParamScope.row.code" />
<el-input v-model="nodeParamScope.row.code" placeholder="参数英文名" />
</template>
</el-table-column>
<el-table-column prop="dataSource" label="参数可选项">
<template v-slot="nodeParamScope">
<el-select
v-model="nodeParamScope.row.value"
placeholder="请输入参数可选项"
clearable
filterable
allow-create
>
<el-option
<el-input
v-for="(item, index) in nodeParamScope.row.dataSource"
:key="index"
:label="item.label"
:value="item.value"
v-model="item.value"
style="margin-bottom: 2px"
clearable
placeholder="请输入参数选项"
@input="(value) => onParameterDataSourceInput(nodeParamScope, index, value)"
@clear="onParameterDataSourceClear(nodeParamScope, index)"
/>
</el-select>
<el-button
style="width: 100%"
type="primary"
plain
@click="onParameterDataSourceAppendClick(nodeParamScope)"
>添加可选项
</el-button>
</template>
</el-table-column>
</el-table>
@@ -198,6 +195,37 @@
</el-form>
</CutePage>
</el-col>
<el-col :span="12">
<CutePage>
<el-divider content-position="left">节点选项(预览)</el-divider>
<el-form ref="form" :model="parameters" label-width="160px">
<el-form-item
v-for="(paramCode, index) in Object.keys(parameters)"
:key="`paramKey_${index}`"
:label="`${parameters[paramCode].name}(${paramCode})`"
>
<el-select
v-model="parameters[paramCode].value"
:placeholder="`请选择${parameters[paramCode].name}`"
clearable
filterable
>
<el-option v-for="(item, index2) in parameters[paramCode].dataSource" :key="`item_${index2}`" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
</CutePage>
<CutePage>
<el-divider content-position="left">节点配置(预览)</el-divider>
<div style="padding-left: 15px">
<el-input
v-model="taskConfig"
type="textarea"
:rows="25"
/>
</div>
</CutePage>
</el-col>
</el-row>
</template>
@@ -211,19 +239,19 @@ export default {
components: { CutePage, PipeTask },
data() {
return {
name: '环境准备',
code: 'release',
name: '',
code: '',
click: false,
retry: false,
detailType: null,
buttonList: [
{
condition: 'running',
method: 'get',
requestUrl: `api/devops/ci/doCheck?id=#pipelineInstanceNodeId`,
text: '确认合并',
type: 'execute'
}
// {
// condition: 'running',
// method: 'get',
// requestUrl: `api/devops/ci/doCheck?id=#pipelineInstanceNodeId`,
// text: '确认合并',
// type: 'execute'
// }
],
hasButtonList: false,
parameters: {
@@ -237,22 +265,235 @@ export default {
}
},
parameterList: [
{
name: 'Jdk版本',
code: 'jdkVersion',
dataSource: [
{ label: 'jdk8', value: 'jdk8' },
{ label: 'jdk11', value: 'jdk11' },
{ label: 'jdk21', value: 'jdk21' }
]
}
]
// {
// name: 'Jdk版本',
// code: 'jdkVersion',
// dataSource: [
// { label: 'jdk8', value: 'jdk8' },
// { label: 'jdk11', value: 'jdk11' },
// { label: 'jdk21', value: 'jdk21' }
// ]
// }
],
taskConfig: ''
}
},
computed: {},
watch: {},
mounted() {},
methods: {}
computed: {
},
watch: {
name: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: newVal,
code: this.code,
click: this.click,
retry: this.retry,
detailType: this.detailType,
buttonList: this.buttonList,
parameters: tempParameters
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true
},
code: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: newVal,
click: this.click,
retry: this.retry,
detailType: this.detailType,
buttonList: this.buttonList,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true
},
click: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: this.code,
click: newVal,
retry: this.retry,
detailType: this.detailType,
buttonList: this.buttonList,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true
},
retry: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: this.code,
click: this.click,
retry: newVal,
detailType: this.detailType,
buttonList: this.buttonList,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true
},
detailType: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: this.code,
click: this.click,
retry: this.retry,
detailType: newVal,
buttonList: this.buttonList,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true
},
buttonList: {
handler(newVal) {
const tempParameters = {}
for (const key in this.parameters) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: this.code,
click: this.click,
retry: this.retry,
detailType: this.detailType,
buttonList: newVal,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true,
deep: true
},
parameters: {
handler(newVal) {
const tempParameters = {}
for (const key in newVal) {
tempParameters[key] = ''
}
const tempParameterMap = {}
for (const key in newVal) {
tempParameterMap[key] = newVal[key].dataSource
}
const config = {
name: this.name,
code: this.code,
click: this.click,
retry: this.retry,
detailType: this.detailType,
buttonList: this.buttonList,
parameters: tempParameters,
parameterMap: tempParameterMap
}
this.taskConfig = JSON.stringify(config, null, 2)
},
immediate: true,
deep: true
},
/**
* 用于刷新 parameters
*/
parameterList: {
handler: function(newList, oldList) {
const newParameters = {}
for (const param of newList) {
if (param.code) {
newParameters[param.code] = {
name: param.name,
dataSource: param.dataSource || []
}
}
}
this.parameters = newParameters
},
deep: true,
immediate: true
}
},
mounted() {
},
methods: {
onParameterDataSourceInput(nodeParamScope, index, value) {
const iii = nodeParamScope.row.dataSource[index]
iii.label = value
iii.value = value
nodeParamScope.row.dataSource[index] = iii
},
onParameterDataSourceClear(nodeParamScope, index) {
nodeParamScope.row.dataSource.splice(index, 1)
},
onParameterDataSourceAppendClick(nodeParamScope) {
// m1.这种写法vue2无法响应式的检测到变化
// const ds = nodeParamScope.row.dataSource || []
// ds.push({label: '', value: ''})
// nodeParamScope.row.dataSource = ds
// m2.使用 this.$set(推荐用于 Vue 2)
if (!nodeParamScope.row.dataSource) {
this.$set(nodeParamScope.row, 'dataSource', [])
}
nodeParamScope.row.dataSource.push({ label: '', value: '' })
}
}
}
</script>
@@ -54,8 +54,8 @@ export default {
</script>
<style lang="scss" scoped>
.pipeline-card {
width: 240px;
max-width: 240px;
width: 260px;
max-width: 260px;
}
.pipeline-link {