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" :key="index"
v-prevent-re-click v-prevent-re-click
type="text" type="text"
:style="`width: ${item.text && item.text.length >= 4 ? '74px': '26px' :style="`color: ${item.color ? item.color : statusColorMap.running};display: ${item.condition ===
};color: ${item.color ? item.color : statusColorMap.running};display: ${item.condition ===
'all' ? 'all' ?
'inline-block': 'inline-block':
(item.condition === status ? 'inline-block': 'none')};overflow: hidden;text-overflow: ellipsis;`" (item.condition === status ? 'inline-block': 'none')};overflow: hidden;text-overflow: ellipsis;`"
@@ -101,14 +100,18 @@ export default {
*/ */
startTimeMillis: { startTimeMillis: {
type: Number, type: Number,
required: true required: false,
default: function() {
return new Date().getTime()
}
}, },
/** /**
* 持续时间。单位:秒 * 持续时间。单位:秒
*/ */
durationMillis: { durationMillis: {
type: Number, type: Number,
required: true required: false,
default: 0
}, },
/** /**
* 状态 * 状态
@@ -200,8 +203,8 @@ export default {
taskWindowStyle() { taskWindowStyle() {
const colorHex = this.getColorHexByStatus(this.status) const colorHex = this.getColorHexByStatus(this.status)
return { return {
width: '240px', width: '260px',
maxWidth: '240px', maxWidth: '260px',
height: '100px', height: '100px',
maxHeight: '100px', maxHeight: '100px',
border: `1px solid ${colorHex}`, border: `1px solid ${colorHex}`,
@@ -54,35 +54,14 @@
/> />
</div> </div>
</CutePage> </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> <CutePage>
<el-divider content-position="left">节点参数设置</el-divider> <el-divider content-position="left">节点参数设置</el-divider>
<el-form label-width="140px"> <el-form label-width="140px">
<el-form-item label="节点名称"> <el-form-item label="节点名称">
<el-input v-model="name" /> <el-input v-model="name" placeholder="请输入节点中文名称。例如:初始化" />
</el-form-item> </el-form-item>
<el-form-item label="节点编码"> <el-form-item label="节点编码">
<el-input v-model="code" /> <el-input v-model="code" placeholder="请输入节点英文名称。例如:initial" />
</el-form-item> </el-form-item>
<el-form-item label="是否支持点击"> <el-form-item label="是否支持点击">
<el-switch v-model="click" /> <el-switch v-model="click" />
@@ -109,7 +88,22 @@
<el-table :data="buttonList"> <el-table :data="buttonList">
<el-table-column prop="text" label="按钮名称" width="180"> <el-table-column prop="text" label="按钮名称" width="180">
<template v-slot="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column prop="type" label="按钮类型" width="160"> <el-table-column prop="type" label="按钮类型" width="160">
@@ -126,7 +120,7 @@
</el-table-column> </el-table-column>
<el-table-column label="执行动作"> <el-table-column label="执行动作">
<template v-slot="scope"> <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"> <el-table-column prop="blank" label="是否新开窗口" width="100">
<template v-slot="innerScope"> <template v-slot="innerScope">
<el-switch v-model="innerScope.row.blank" /> <el-switch v-model="innerScope.row.blank" />
@@ -134,11 +128,11 @@
</el-table-column> </el-table-column>
<el-table-column prop="linkUrl" label="打开链接"> <el-table-column prop="linkUrl" label="打开链接">
<template v-slot="innerScope"> <template v-slot="innerScope">
<el-input v-model="innerScope.row.linkUrl" /> <el-input v-model="innerScope.row.linkUrl" placeholder="请输入or黏贴第三方链接" />
</template> </template>
</el-table-column> </el-table-column>
</el-table> </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"> <el-table-column prop="method" label="请求类型" width="160">
<template v-slot="innerScope"> <template v-slot="innerScope">
<el-select <el-select
@@ -153,7 +147,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="requestUrl" label="请求地址"> <el-table-column prop="requestUrl" label="请求地址">
<template v-slot="innerScope"> <template v-slot="innerScope">
<el-input v-model="innerScope.row.requestUrl" /> <el-input v-model="innerScope.row.requestUrl" placeholder="请输入or黏贴请求地址。这里应为系统内部地址,具体参考 src/api 文件夹" />
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -166,30 +160,33 @@
<el-table :data="parameterList"> <el-table :data="parameterList">
<el-table-column prop="name" label="参数名称"> <el-table-column prop="name" label="参数名称">
<template v-slot="nodeParamScope"> <template v-slot="nodeParamScope">
<el-input v-model="nodeParamScope.row.name" /> <el-input v-model="nodeParamScope.row.name" placeholder="参数中文名" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="code" label="参数编码"> <el-table-column prop="code" label="参数编码">
<template v-slot="nodeParamScope"> <template v-slot="nodeParamScope">
<el-input v-model="nodeParamScope.row.code" /> <el-input v-model="nodeParamScope.row.code" placeholder="参数英文名" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="dataSource" label="参数可选项"> <el-table-column prop="dataSource" label="参数可选项">
<template v-slot="nodeParamScope"> <template v-slot="nodeParamScope">
<el-select <el-input
v-model="nodeParamScope.row.value" v-for="(item, index) in nodeParamScope.row.dataSource"
placeholder="请输入参数可选项" :key="index"
v-model="item.value"
style="margin-bottom: 2px"
clearable clearable
filterable placeholder="请输入参数选项"
allow-create @input="(value) => onParameterDataSourceInput(nodeParamScope, index, value)"
> @clear="onParameterDataSourceClear(nodeParamScope, index)"
<el-option />
v-for="(item, index) in nodeParamScope.row.dataSource" <el-button
:key="index" style="width: 100%"
:label="item.label" type="primary"
:value="item.value" plain
/> @click="onParameterDataSourceAppendClick(nodeParamScope)"
</el-select> >添加可选项
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -198,6 +195,37 @@
</el-form> </el-form>
</CutePage> </CutePage>
</el-col> </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> </el-row>
</template> </template>
@@ -211,19 +239,19 @@ export default {
components: { CutePage, PipeTask }, components: { CutePage, PipeTask },
data() { data() {
return { return {
name: '环境准备', name: '',
code: 'release', code: '',
click: false, click: false,
retry: false, retry: false,
detailType: null, detailType: null,
buttonList: [ buttonList: [
{ // {
condition: 'running', // condition: 'running',
method: 'get', // method: 'get',
requestUrl: `api/devops/ci/doCheck?id=#pipelineInstanceNodeId`, // requestUrl: `api/devops/ci/doCheck?id=#pipelineInstanceNodeId`,
text: '确认合并', // text: '确认合并',
type: 'execute' // type: 'execute'
} // }
], ],
hasButtonList: false, hasButtonList: false,
parameters: { parameters: {
@@ -237,22 +265,235 @@ export default {
} }
}, },
parameterList: [ parameterList: [
{ // {
name: 'Jdk版本', // name: 'Jdk版本',
code: 'jdkVersion', // code: 'jdkVersion',
dataSource: [ // dataSource: [
{ label: 'jdk8', value: 'jdk8' }, // { label: 'jdk8', value: 'jdk8' },
{ label: 'jdk11', value: 'jdk11' }, // { label: 'jdk11', value: 'jdk11' },
{ label: 'jdk21', value: 'jdk21' } // { label: 'jdk21', value: 'jdk21' }
] // ]
} // }
] ],
taskConfig: ''
} }
}, },
computed: {}, computed: {
watch: {}, },
mounted() {}, watch: {
methods: {} 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> </script>
@@ -54,8 +54,8 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.pipeline-card { .pipeline-card {
width: 240px; width: 260px;
max-width: 240px; max-width: 260px;
} }
.pipeline-link { .pipeline-link {