diff --git a/backend/apps/views/build.py b/backend/apps/views/build.py index 1cece72..1dfca71 100644 --- a/backend/apps/views/build.py +++ b/backend/apps/views/build.py @@ -382,6 +382,13 @@ class BuildTaskView(View): 'message': '任务名称、项目和环境不能为空' }) + # 检查任务名称是否已存在 + if BuildTask.objects.filter(name=name).exists(): + return JsonResponse({ + 'code': 400, + 'message': f'任务名称 "{name}" 已存在,请使用其他名称' + }) + # 验证参数配置格式 if parameters: import re @@ -649,6 +656,12 @@ class BuildTaskView(View): # 更新其他字段 if 'name' in data: + # 检查任务名称是否已存在 + if BuildTask.objects.filter(name=name).exclude(task_id=task_id).exists(): + return JsonResponse({ + 'code': 400, + 'message': f'任务名称 "{name}" 已存在,请使用其他名称' + }) task.name = name if 'description' in data: task.description = description diff --git a/web/src/router/index.js b/web/src/router/index.js index 262d1c6..e91f392 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -89,6 +89,12 @@ const routes = [ component: () => import('../views/build/BuildTaskEdit.vue'), meta: { title: '编辑构建任务', permission: '/build/tasks' } }, + { + path: 'tasks/copy', + name: 'build-task-copy', + component: () => import('../views/build/BuildTaskEdit.vue'), + meta: { title: '复制构建任务', permission: '/build/tasks' } + }, { path: 'history', name: 'build-history', diff --git a/web/src/views/build/BuildTaskEdit.vue b/web/src/views/build/BuildTaskEdit.vue index 7b97fde..ec06fdc 100644 --- a/web/src/views/build/BuildTaskEdit.vue +++ b/web/src/views/build/BuildTaskEdit.vue @@ -2,7 +2,7 @@
@@ -427,6 +427,7 @@ const router = useRouter(); const route = useRoute(); const formRef = ref(); const isEdit = ref(false); +const isCopy = ref(false); const submitLoading = ref(false); const loading = ref(false); const projectOptions = ref([]); @@ -665,7 +666,7 @@ const handleSubmit = async () => { submitData.external_script_token_id = undefined; } - if (!isEdit.value) { + if (!isEdit.value || isCopy.value) { delete submitData.task_id; } @@ -674,14 +675,26 @@ const handleSubmit = async () => { }); if (response.data.code === 200) { - message.success(`${isEdit.value ? '更新' : '创建'}构建任务成功`); + let successMsg = '创建构建任务成功'; + if (isEdit.value) { + successMsg = '更新构建任务成功'; + } else if (isCopy.value) { + successMsg = '复制构建任务成功'; + } + message.success(successMsg); router.push('/build/tasks'); } else { throw new Error(response.data.message); } } catch (error) { console.error('Submit task error:', error); - message.error(error.message || `${isEdit.value ? '更新' : '创建'}构建任务失败`); + let errorMsg = '创建构建任务失败'; + if (isEdit.value) { + errorMsg = '更新构建任务失败'; + } else if (isCopy.value) { + errorMsg = '复制构建任务失败'; + } + message.error(error.message || errorMsg); } finally { submitLoading.value = false; } @@ -770,6 +783,90 @@ const loadTaskDetail = async (taskId) => { } }; +// 复制任务加载任务详情 +const loadTaskDetailForCopy = async (sourceTaskId) => { + try { + loading.value = true; + const token = localStorage.getItem('token'); + const response = await axios.get(`/api/build/tasks/${sourceTaskId}`, { + headers: { 'Authorization': token } + }); + + if (response.data.code === 200) { + // 新任务不设置task_id + formState.task_id = ''; + formState.name = response.data.data.name + ' - Copy'; + formState.description = response.data.data.description; + formState.branch = response.data.data.branch; + + // 外部脚本库配置 + formState.use_external_script = response.data.data.use_external_script || false; + formState.external_script_repo_url = response.data.data.external_script_repo_url || ''; + formState.external_script_directory = response.data.data.external_script_directory || ''; + formState.external_script_branch = response.data.data.external_script_branch || ''; + formState.external_script_token_id = response.data.data.external_script_token_id || undefined; + + const stages = response.data.data.stages || []; + formState.stages = stages.map(stage => ({ + name: stage.name || '', + script: stage.script || '', + })); + + if (formState.stages.length === 0) { + formState.stages.push({ + name: '构建', + script: '', + }); + } + + // 加载参数配置 + const parameters = response.data.data.parameters || []; + formState.parameters = parameters.map(param => { + const choicesText = (param.choices || []).join('\n'); + const defaultValuesText = (param.default_values || []).join(','); + return { + name: param.name || '', + description: param.description || '', + choices: param.choices || [], + choicesText: choicesText, + choiceOptions: (param.choices || []).map(choice => ({ + label: choice, + value: choice + })), + default_values: param.default_values || [], + defaultValuesText: defaultValuesText, + }; + }); + + formState.notification_channels = response.data.data.notification_channels || []; + + if (response.data.data.project) { + formState.project_id = response.data.data.project.project_id; + } + if (response.data.data.environment) { + formState.environment_id = response.data.data.environment.environment_id; + } + if (response.data.data.git_token) { + formState.git_token_id = response.data.data.git_token.credential_id; + } + + // 加载相关选项数据 + await Promise.all([ + loadProjects(), + loadEnvironments(), + loadGitCredentials() + ]); + } else { + message.error(response.data.message || '加载任务详情失败'); + } + } catch (error) { + console.error('加载任务详情失败:', error); + message.error('加载任务详情失败'); + } finally { + loading.value = false; + } +}; + // 过滤选项方法 const filterOption = (input, option) => { return ( @@ -861,9 +958,14 @@ const truncateUrl = (url) => { onMounted(async () => { const taskId = route.query.task_id; + const sourceTaskId = route.query.source_task_id; + if (taskId) { isEdit.value = true; await loadTaskDetail(taskId); + } else if (sourceTaskId) { + isCopy.value = true; + await loadTaskDetailForCopy(sourceTaskId); } else { loadProjects(); loadEnvironments(); diff --git a/web/src/views/build/BuildTasks.vue b/web/src/views/build/BuildTasks.vue index 5e3d8b2..d22456b 100644 --- a/web/src/views/build/BuildTasks.vue +++ b/web/src/views/build/BuildTasks.vue @@ -194,6 +194,9 @@ 编辑 + + 复制 + { }); }; +// 处理复制任务 +const handleCopy = (record) => { + // 检查权限 + const module = 'build_task'; + const action = 'create'; + if (!hasFunctionPermission(module, action)) { + message.error('你没有创建构建任务的权限'); + return; + } + + stopLogUpdate(); // 确保在跳转前清理定时器 + router.push({ + name: 'build-task-copy', + query: { source_task_id: record.task_id } + }); +}; + // 下载日志 const handleDownloadLog = async () => { if (!selectedHistoryId.value) { @@ -1410,6 +1431,8 @@ const handleViewBuildDetail = (record) => { } }); }; + +