Files
graduate-design/orion-ops-vue/src/components/app/AppBuildConfigForm.vue
2026-03-06 14:01:32 +08:00

329 lines
9.1 KiB
Vue

<template>
<div id="app-build-conf-container">
<a-spin :spinning="loading">
<!-- 产物路径 -->
<div id="app-bundle-container">
<div id="app-bundle-wrapper">
<span class="label normal-label required-label">构建产物路径</span>
<a-textarea class="bundle-input"
v-model="bundlePath"
:maxLength="1024"
:autoSize="{minRows: 1}"
:placeholder="'基于版本仓库的相对路径 或 绝对路径, 路径不能包含 \\\ 应该用 / 替换'"/>
</div>
</div>
<!-- 构建操作 -->
<div id="app-action-container">
<template v-for="(action, index) in actions">
<div class="app-action-block" :key="index" v-if="action.visible">
<!-- 分隔符 -->
<a-divider class="action-divider">构建操作{{ index + 1 }}</a-divider>
<div class="app-action-wrapper">
<!-- 操作 -->
<div class="app-action">
<div class="action-name-wrapper">
<span class="label normal-label required-label action-label">操作名称{{ index + 1 }}</span>
<a-input class="action-name-input" v-model="action.name" :maxLength="32" placeholder="操作名称"/>
</div>
<!-- 代码块 -->
<div class="action-editor-wrapper" v-if="action.type === BUILD_ACTION_TYPE.COMMAND.value">
<span class="label normal-label required-label action-label">主机命令{{ index + 1 }}</span>
<div class="app-action-editor">
<Editor :config="editorConfig" :value="action.command" @change="(v) => action.command = v"/>
</div>
</div>
<div class="action-type-wrapper" v-else>
<span class="label normal-label action-label">操作类型</span>
<a-button class="action-type-name" ghost disabled>
{{ action.type | formatActionType('label') }}
</a-button>
</div>
</div>
<!-- 操作 -->
<div class="app-action-handler">
<a-button-group v-if="actions.length > 1">
<a-button title="移除" @click="removeAction(index)" icon="minus-circle"/>
<a-button title="上移" v-if="index !== 0" @click="swapAction(index, index - 1)" icon="arrow-up"/>
<a-button title="下移" v-if="index !== actions.length - 1" @click="swapAction(index + 1, index )" icon="arrow-down"/>
</a-button-group>
</div>
</div>
</div>
</template>
</div>
<!-- 底部按钮 -->
<div id="app-action-footer">
<a-button class="app-action-footer-button" type="dashed"
@click="addAction(BUILD_ACTION_TYPE.COMMAND.value)">
添加命令操作 (宿主机执行)
</a-button>
<a-button class="app-action-footer-button" type="dashed"
v-if="visibleAddCheckout"
@click="addAction(BUILD_ACTION_TYPE.CHECKOUT.value)">
添加检出操作 (宿主机执行)
</a-button>
<a-button class="app-action-footer-button" type="primary" @click="save">保存</a-button>
</div>
</a-spin>
</div>
</template>
<script>
import { BUILD_ACTION_TYPE, enumValueOf } from '@/lib/enum'
import Editor from '@/components/editor/Editor'
const editorConfig = {
enableLiveAutocompletion: true,
fontSize: 14
}
export default {
name: 'AppBuildConfigForm',
props: {
appId: Number,
dataLoading: Boolean,
detail: Object
},
components: {
Editor
},
computed: {
visibleAddCheckout() {
return this.repoId &&
this.repoId !== null &&
this.actions.map(s => s.type).filter(t => t === BUILD_ACTION_TYPE.CHECKOUT.value).length < 1
}
},
watch: {
detail(e) {
this.initData(e)
},
dataLoading(e) {
this.loading = e
}
},
data() {
return {
BUILD_ACTION_TYPE,
loading: false,
profileId: null,
repoId: null,
bundlePath: undefined,
actions: [],
editorConfig
}
},
methods: {
initData(detail) {
this.profileId = detail.profileId
this.repoId = detail.repoId
this.bundlePath = detail.env && detail.env.bundlePath
if (detail.buildActions && detail.buildActions.length) {
this.actions = detail.buildActions.map(s => {
return {
visible: true,
name: s.name,
type: s.type,
command: s.command
}
})
} else {
this.actions = []
}
},
addAction(type) {
this.actions.push({
type,
command: '',
name: undefined,
visible: true
})
},
removeAction(index) {
this.actions[index].visible = false
this.$nextTick(() => {
this.actions.splice(index, 1)
})
},
swapAction(index, target) {
const temp = this.actions[target]
this.$set(this.actions, target, this.actions[index])
this.$set(this.actions, index, temp)
},
save() {
if (!this.bundlePath || !this.bundlePath.trim().length) {
this.$message.warning('请输入构建产物路径')
return
}
if (this.bundlePath.includes('\\')) {
this.$message.warning('构建产物路径不能包含 \\ 应该用 / 替换')
return
}
if (!this.actions.length) {
this.$message.warning('请设置构建操作')
return
}
for (let i = 0; i < this.actions.length; i++) {
const action = this.actions[i]
if (!action.name) {
this.$message.warning(`请输入操作名称 [构建操作${i + 1}]`)
return
}
if (BUILD_ACTION_TYPE.COMMAND.value === action.type) {
if (!action.command) {
this.$message.warning(`请输入操作命令 [构建操作${i + 1}]`)
return
} else if (action.command.length > 2048) {
this.$message.warning(`操作命令长度不能大于2048位 [构建操作${i + 1}] 当前: ${action.command.length}`)
return
}
}
}
this.loading = true
this.$api.configApp({
appId: this.appId,
profileId: this.profileId,
stageType: 10,
env: {
bundlePath: this.bundlePath
},
buildActions: this.actions
}).then(() => {
this.$message.success('保存成功')
this.$emit('updated')
this.loading = false
}).catch(() => {
this.loading = false
})
}
},
filters: {
formatActionType(type, f) {
return enumValueOf(BUILD_ACTION_TYPE, type)[f]
}
},
mounted() {
this.initData(this.detail)
}
}
</script>
<style lang="less" scoped>
@label-width: 160px;
@action-handler-width: 120px;
@bundle-container-width: 994px;
@bundle-input-width: 700px;
@app-action-container-width: 994px;
@app-action-width: 876px;
@action-name-input-width: 700px;
@app-action-editor-width: 700px;
@app-action-editor-height: 250px;
@action-type-name-width: 700px;
@action-divider-min-width: 830px;
@action-divider-width: 990px;
@app-action-footer-width: 700px;
@footer-margin-left: 168px;
@desc-margin-left: 168px;
#app-build-conf-container {
padding: 18px 8px 0 8px;
overflow: auto;
.label {
width: @label-width;
font-size: 15px;
line-height: 32px;
}
#app-bundle-wrapper {
width: @bundle-container-width;
display: flex;
align-items: flex-start;
justify-content: flex-start;
align-content: center;
.bundle-input {
width: @bundle-input-width;
margin-left: 8px;
}
}
#app-action-container {
width: @app-action-container-width;
margin-top: 16px;
.app-action-wrapper {
width: 100%;
display: flex;
.action-label {
padding: 8px;
}
.app-action {
width: @app-action-width;
padding: 0 8px 8px 8px;
.action-name-wrapper {
display: flex;
align-items: center;
.action-name-input {
width: @action-name-input-width;
}
}
.action-editor-wrapper {
display: flex;
.app-action-editor {
width: @app-action-editor-width;
height: @app-action-editor-height;
margin-top: 8px;
}
}
.action-type-wrapper {
display: flex;
align-items: center;
.action-type-name {
width: @action-type-name-width;
}
}
}
.app-action-handler {
width: @action-handler-width;
margin: 8px 0 0 8px;
}
}
.action-divider {
min-width: @action-divider-min-width;
width: @action-divider-width;
margin: 16px 0;
}
}
#app-action-footer {
margin: 16px 0 8px @footer-margin-left;
width: @app-action-footer-width;
.app-action-footer-button {
width: 100%;
margin-bottom: 8px;
}
}
.config-description {
margin: 4px 0 0 @desc-margin-left;
display: block;
color: rgba(0, 0, 0, .45);
font-size: 13px;
}
}
</style>