2025-09-22 17:10:21 +08:00
|
|
|
|
<template>
|
2025-09-22 17:21:08 +08:00
|
|
|
|
<div class="plan-detail">
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<div v-if="loading" class="loading">
|
|
|
|
|
|
<el-skeleton animated />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="error" class="error">
|
|
|
|
|
|
<el-alert
|
|
|
|
|
|
:title="'加载计划内容失败 (ID: ' + planId + ')'"
|
|
|
|
|
|
:description="error"
|
|
|
|
|
|
type="error"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
@close="error = null"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-button type="primary" @click="fetchPlan" class="retry-btn">重新加载</el-button>
|
|
|
|
|
|
</div>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<div v-else-if="plan && plan.id">
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<el-card class="box-card">
|
|
|
|
|
|
<template #header>
|
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
|
<span>{{ plan.name }} - 内容</span>
|
2025-09-22 17:46:28 +08:00
|
|
|
|
<div>
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<template v-if="!isSubPlan">
|
|
|
|
|
|
<el-button class="button" type="primary" @click="savePlanContent" v-if="isEditingContent">保存</el-button>
|
|
|
|
|
|
<el-button class="button" @click="cancelEdit" v-if="isEditingContent">取消</el-button>
|
|
|
|
|
|
<el-button class="button" @click="enterEditMode" v-else>编辑内容</el-button>
|
|
|
|
|
|
</template>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Dynamic Add Buttons -->
|
|
|
|
|
|
<template v-if="isEditingContent">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="plan.content_type === 'sub_plans' || !plan.content_type"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="showAddSubPlanDialog"
|
|
|
|
|
|
>增加子计划</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="plan.content_type === 'tasks' || !plan.content_type"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="showAddTaskDialog"
|
|
|
|
|
|
>增加子任务</el-button>
|
|
|
|
|
|
</template>
|
2025-09-22 17:46:28 +08:00
|
|
|
|
</div>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Display Tasks -->
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<div v-if="plan.content_type === 'tasks'">
|
|
|
|
|
|
<h4>任务列表</h4>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<el-timeline v-if="plan.tasks.length > 0">
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<el-timeline-item
|
2025-09-22 18:10:12 +08:00
|
|
|
|
v-for="(task, index) in plan.tasks"
|
2025-09-22 17:46:28 +08:00
|
|
|
|
:key="task.id || 'new-task-' + index"
|
2025-09-22 18:10:12 +08:00
|
|
|
|
:timestamp="'执行顺序: ' + (task.execution_order !== undefined ? task.execution_order : index + 1)"
|
2025-09-22 17:10:21 +08:00
|
|
|
|
placement="top"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-card>
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<h5>{{ task.name }} ({{ task.type === 'waiting' ? '延时任务' : '未知任务' }})</h5>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<p>{{ task.description }}</p>
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<p v-if="task.type === 'waiting' && task.parameters?.delay_duration">
|
|
|
|
|
|
延时: {{ task.parameters.delay_duration }} 秒
|
2025-09-22 18:10:12 +08:00
|
|
|
|
</p>
|
2025-09-22 17:46:28 +08:00
|
|
|
|
<el-button-group v-if="isEditingContent">
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<el-button type="primary" size="small" @click="editTask(task)">编辑</el-button>
|
|
|
|
|
|
<el-button type="danger" size="small" @click="deleteTask(task)">删除</el-button>
|
|
|
|
|
|
</el-button-group>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</el-timeline-item>
|
|
|
|
|
|
</el-timeline>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<el-empty v-else description="暂无任务"></el-empty>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Display Sub-plans -->
|
2025-09-22 17:10:21 +08:00
|
|
|
|
<div v-else-if="plan.content_type === 'sub_plans'">
|
|
|
|
|
|
<h4>子计划列表</h4>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<div v-if="plan.sub_plans.length > 0">
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<div v-for="(subPlan, index) in plan.sub_plans" :key="subPlan.id || 'new-subplan-' + index" class="sub-plan-wrapper">
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<el-card>
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<div class="sub-plan-card-content">
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<!-- Pass child_plan_id to recursive PlanDetail -->
|
|
|
|
|
|
<plan-detail :plan-id="subPlan.child_plan_id" :is-sub-plan="true" />
|
2025-09-22 18:22:29 +08:00
|
|
|
|
<el-button-group v-if="isEditingContent" class="sub-plan-actions">
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<el-button type="danger" size="small" @click="deleteSubPlan(subPlan)">删除</el-button>
|
|
|
|
|
|
</el-button-group>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<el-empty v-else description="暂无子计划"></el-empty>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
<div v-else-if="!plan.content_type">
|
|
|
|
|
|
<el-empty description="请添加子计划或子任务"></el-empty>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
|
<el-empty description="没有计划数据"></el-empty>
|
|
|
|
|
|
</div>
|
2025-09-22 17:46:28 +08:00
|
|
|
|
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<!-- Add Sub-plan Dialog -->
|
2025-09-22 17:46:28 +08:00
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="addSubPlanDialogVisible"
|
|
|
|
|
|
title="选择子计划"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
@close="resetAddSubPlanDialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-select
|
|
|
|
|
|
v-model="selectedSubPlanId"
|
|
|
|
|
|
placeholder="请选择一个计划作为子计划"
|
|
|
|
|
|
filterable
|
|
|
|
|
|
style="width: 100%;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="item in availablePlans"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
:label="item.name"
|
|
|
|
|
|
:value="item.id"
|
|
|
|
|
|
></el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<el-button @click="addSubPlanDialogVisible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="confirmAddSubPlan">确定</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<!-- Add Task Dialog -->
|
2025-09-22 17:46:28 +08:00
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="addTaskDialogVisible"
|
|
|
|
|
|
title="增加子任务"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
@close="resetAddTaskDialog"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form :model="newTaskForm" ref="newTaskFormRef" :rules="newTaskRules" label-width="100px">
|
|
|
|
|
|
<el-form-item label="任务类型" prop="type">
|
|
|
|
|
|
<el-select v-model="newTaskForm.type" placeholder="请选择任务类型" style="width: 100%;">
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<!-- Only Delay Task for now -->
|
2025-09-22 17:46:28 +08:00
|
|
|
|
<el-option label="延时任务" value="delay_task"></el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="任务名称" prop="name">
|
|
|
|
|
|
<el-input v-model="newTaskForm.name" placeholder="请输入任务名称"></el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item label="任务描述" prop="description">
|
|
|
|
|
|
<el-input type="textarea" v-model="newTaskForm.description" placeholder="请输入任务描述"></el-input>
|
|
|
|
|
|
</el-form-item>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
<!-- Dynamic task component for specific parameters -->
|
|
|
|
|
|
<template v-if="newTaskForm.type === 'delay_task'">
|
|
|
|
|
|
<DelayTaskEditor
|
|
|
|
|
|
:parameters="newTaskForm.parameters"
|
|
|
|
|
|
@update:parameters="val => newTaskForm.parameters = val"
|
2025-09-22 18:22:29 +08:00
|
|
|
|
prop-path="parameters.delay_duration"
|
|
|
|
|
|
:is-editing="true"
|
|
|
|
|
|
/>
|
2025-09-22 18:10:12 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
<!-- More task types can be rendered here -->
|
2025-09-22 17:46:28 +08:00
|
|
|
|
</el-form>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<el-button @click="addTaskDialogVisible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="confirmAddTask">确定</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import apiClient from '../api/index.js';
|
2025-09-22 18:10:12 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
2025-09-22 18:22:29 +08:00
|
|
|
|
import { ArrowDown } from '@element-plus/icons-vue';
|
|
|
|
|
|
import DelayTaskEditor from './tasks/DelayTask.vue';
|
2025-09-22 17:10:21 +08:00
|
|
|
|
|
|
|
|
|
|
export default {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
name: 'PlanDetail',
|
|
|
|
|
|
components: {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
DelayTaskEditor,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
// Self-reference for recursion
|
|
|
|
|
|
'plan-detail': this,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
ArrowDown,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
props: {
|
|
|
|
|
|
planId: {
|
|
|
|
|
|
type: [Number, String],
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
},
|
2025-09-22 18:10:12 +08:00
|
|
|
|
isSubPlan: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false,
|
|
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
plan: {
|
|
|
|
|
|
id: null,
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
|
|
|
|
|
execution_type: 'automatic',
|
|
|
|
|
|
execute_num: 0,
|
|
|
|
|
|
cron_expression: '',
|
2025-09-22 18:22:29 +08:00
|
|
|
|
content_type: null,
|
|
|
|
|
|
sub_plans: [],
|
|
|
|
|
|
tasks: [],
|
2025-09-22 18:10:12 +08:00
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
loading: false,
|
|
|
|
|
|
error: null,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
isEditingContent: false,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
originalPlanState: null,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
// Add Sub-plan dialog
|
|
|
|
|
|
addSubPlanDialogVisible: false,
|
|
|
|
|
|
selectedSubPlanId: null,
|
|
|
|
|
|
availablePlans: [],
|
2025-09-22 17:46:28 +08:00
|
|
|
|
|
2025-09-22 18:10:12 +08:00
|
|
|
|
// Add Task dialog
|
|
|
|
|
|
addTaskDialogVisible: false,
|
|
|
|
|
|
newTaskForm: {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
type: 'delay_task',
|
2025-09-22 17:46:28 +08:00
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
2025-09-22 18:22:29 +08:00
|
|
|
|
parameters: { delay_duration: 1 }, // Initialize with delay_duration
|
2025-09-22 17:46:28 +08:00
|
|
|
|
},
|
2025-09-22 18:10:12 +08:00
|
|
|
|
newTaskRules: {
|
2025-09-22 17:46:28 +08:00
|
|
|
|
type: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
|
|
|
|
|
name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
|
2025-09-22 18:22:29 +08:00
|
|
|
|
// Add rule for delay_duration directly here, as it's the default type
|
|
|
|
|
|
'parameters.delay_duration': [{ required: true, message: '请输入延时时间', trigger: 'blur' }],
|
2025-09-22 17:46:28 +08:00
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
computed: {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
delayDurationRules() {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
return [{ required: true, message: '请输入延时时间', trigger: 'blur' }];
|
|
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
planId: {
|
|
|
|
|
|
immediate: true,
|
|
|
|
|
|
handler(newId) {
|
|
|
|
|
|
if (newId) {
|
|
|
|
|
|
this.fetchPlan();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-09-22 18:10:12 +08:00
|
|
|
|
'newTaskForm.type'(newType) {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
// This watch is less critical now since delay_task is the only option
|
|
|
|
|
|
// but kept for extensibility.
|
2025-09-22 18:10:12 +08:00
|
|
|
|
if (newType === 'delay_task') {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.newTaskRules['parameters.delay_duration'] = this.delayDurationRules;
|
|
|
|
|
|
if (!this.newTaskForm.parameters.delay_duration) {
|
|
|
|
|
|
this.newTaskForm.parameters.delay_duration = 1;
|
2025-09-22 18:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
if (this.newTaskRules['parameters.delay_duration']) {
|
|
|
|
|
|
delete this.newTaskRules['parameters.delay_duration'];
|
2025-09-22 18:10:12 +08:00
|
|
|
|
}
|
2025-09-22 18:22:29 +08:00
|
|
|
|
if (this.newTaskForm.parameters.delay_duration) {
|
|
|
|
|
|
delete this.newTaskForm.parameters.delay_duration;
|
2025-09-22 18:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async fetchPlan() {
|
|
|
|
|
|
this.loading = true;
|
|
|
|
|
|
this.error = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await apiClient.plans.get(this.planId);
|
2025-09-22 18:10:12 +08:00
|
|
|
|
this.plan = {
|
|
|
|
|
|
...response.data,
|
|
|
|
|
|
sub_plans: response.data.sub_plans || [],
|
|
|
|
|
|
tasks: response.data.tasks || [],
|
|
|
|
|
|
};
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.updateContentType();
|
2025-09-22 17:10:21 +08:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
this.error = err.message || '未知错误';
|
|
|
|
|
|
console.error(`加载计划 (ID: ${this.planId}) 失败:`, err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
this.loading = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-22 18:10:12 +08:00
|
|
|
|
updateContentType() {
|
|
|
|
|
|
if (this.plan.sub_plans.length > 0) {
|
|
|
|
|
|
this.plan.content_type = 'sub_plans';
|
|
|
|
|
|
} else if (this.plan.tasks.length > 0) {
|
|
|
|
|
|
this.plan.content_type = 'tasks';
|
|
|
|
|
|
} else {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.plan.content_type = null;
|
2025-09-22 18:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-22 18:22:29 +08:00
|
|
|
|
enterEditMode() {
|
|
|
|
|
|
this.isEditingContent = true;
|
|
|
|
|
|
this.originalPlanState = JSON.parse(JSON.stringify(this.plan));
|
2025-09-22 17:46:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
async savePlanContent() {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.updateContentType();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
try {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
const submitData = {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
id: this.plan.id,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
name: this.plan.name,
|
|
|
|
|
|
description: this.plan.description,
|
|
|
|
|
|
execution_type: this.plan.execution_type,
|
|
|
|
|
|
execute_num: this.plan.execute_num,
|
|
|
|
|
|
cron_expression: this.plan.cron_expression,
|
|
|
|
|
|
content_type: this.plan.content_type,
|
|
|
|
|
|
sub_plan_ids: this.plan.content_type === 'sub_plans'
|
2025-09-22 18:22:29 +08:00
|
|
|
|
? this.plan.sub_plans.map(sp => sp.child_plan_id)
|
2025-09-22 18:10:12 +08:00
|
|
|
|
: [],
|
|
|
|
|
|
tasks: this.plan.content_type === 'tasks'
|
|
|
|
|
|
? this.plan.tasks.map((task, index) => ({
|
|
|
|
|
|
name: task.name,
|
|
|
|
|
|
description: task.description,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
// Set type to 'waiting' for delay_task
|
|
|
|
|
|
type: task.type === 'delay_task' ? 'waiting' : task.type,
|
|
|
|
|
|
execution_order: index + 1,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
parameters: task.parameters || {},
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
delete submitData.execute_count;
|
|
|
|
|
|
delete submitData.status;
|
|
|
|
|
|
|
|
|
|
|
|
await apiClient.plans.update(this.planId, submitData);
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.success('计划内容已保存');
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.isEditingContent = false;
|
|
|
|
|
|
this.originalPlanState = null;
|
|
|
|
|
|
this.fetchPlan();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error('保存计划内容失败: ' + (error.message || '未知错误'));
|
|
|
|
|
|
console.error('保存计划内容失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
2025-09-22 18:22:29 +08:00
|
|
|
|
cancelEdit() {
|
|
|
|
|
|
this.plan = JSON.parse(JSON.stringify(this.originalPlanState));
|
|
|
|
|
|
this.isEditingContent = false;
|
|
|
|
|
|
this.originalPlanState = null;
|
|
|
|
|
|
ElMessage.info('已取消编辑');
|
|
|
|
|
|
},
|
2025-09-22 18:10:12 +08:00
|
|
|
|
|
|
|
|
|
|
// --- Sub-plan related methods ---
|
2025-09-22 17:46:28 +08:00
|
|
|
|
async showAddSubPlanDialog() {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
if (this.plan.tasks.length > 0) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm('当前计划包含任务,添加子计划将清空现有任务。是否继续?', '警告', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
});
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.plan.tasks = [];
|
2025-09-22 18:10:12 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-22 17:46:28 +08:00
|
|
|
|
this.addSubPlanDialogVisible = true;
|
|
|
|
|
|
await this.fetchAvailablePlans();
|
|
|
|
|
|
},
|
|
|
|
|
|
async fetchAvailablePlans() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await apiClient.plans.list();
|
|
|
|
|
|
this.availablePlans = response.data.plans.filter(p =>
|
2025-09-22 18:22:29 +08:00
|
|
|
|
p.id !== this.planId
|
2025-09-22 17:46:28 +08:00
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error('加载可用计划失败: ' + (error.message || '未知错误'));
|
|
|
|
|
|
console.error('加载可用计划失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
confirmAddSubPlan() {
|
|
|
|
|
|
if (!this.selectedSubPlanId) {
|
|
|
|
|
|
ElMessage.warning('请选择一个子计划');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectedPlan = this.availablePlans.find(p => p.id === this.selectedSubPlanId);
|
|
|
|
|
|
if (selectedPlan) {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
this.plan.sub_plans.push({
|
2025-09-22 18:22:29 +08:00
|
|
|
|
id: Date.now(),
|
2025-09-22 18:10:12 +08:00
|
|
|
|
child_plan_id: selectedPlan.id,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
child_plan: selectedPlan,
|
2025-09-22 18:10:12 +08:00
|
|
|
|
execution_order: this.plan.sub_plans.length + 1,
|
2025-09-22 17:46:28 +08:00
|
|
|
|
});
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.updateContentType();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.success(`子计划 "${selectedPlan.name}" 已添加`);
|
|
|
|
|
|
this.addSubPlanDialogVisible = false;
|
|
|
|
|
|
this.resetAddSubPlanDialog();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error('未找到选中的计划');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
resetAddSubPlanDialog() {
|
|
|
|
|
|
this.selectedSubPlanId = null;
|
|
|
|
|
|
this.availablePlans = [];
|
|
|
|
|
|
},
|
|
|
|
|
|
deleteSubPlan(subPlanToDelete) {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
ElMessageBox.confirm(`确认删除子计划 "${subPlanToDelete.child_plan?.name || '未知子计划'}" 吗?`, '提示', {
|
2025-09-22 17:46:28 +08:00
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}).then(() => {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
this.plan.sub_plans = this.plan.sub_plans.filter(sub => sub.id !== subPlanToDelete.id);
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.plan.sub_plans.forEach((item, index) => item.execution_order = index + 1);
|
|
|
|
|
|
this.updateContentType();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.success('子计划已删除');
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2025-09-22 18:10:12 +08:00
|
|
|
|
// --- Task related methods ---
|
|
|
|
|
|
async showAddTaskDialog() {
|
|
|
|
|
|
if (this.plan.sub_plans.length > 0) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm('当前计划包含子计划,添加任务将清空现有子计划。是否继续?', '警告', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
});
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.plan.sub_plans = [];
|
2025-09-22 18:10:12 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-22 17:46:28 +08:00
|
|
|
|
this.addTaskDialogVisible = true;
|
|
|
|
|
|
},
|
|
|
|
|
|
confirmAddTask() {
|
|
|
|
|
|
this.$refs.newTaskFormRef.validate(async (valid) => {
|
|
|
|
|
|
if (valid) {
|
|
|
|
|
|
const newTask = {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
id: Date.now(),
|
2025-09-22 18:10:12 +08:00
|
|
|
|
execution_order: this.plan.tasks.length + 1,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
// Map 'delay_task' UI type to 'waiting' backend type
|
|
|
|
|
|
type: this.newTaskForm.type === 'delay_task' ? 'waiting' : this.newTaskForm.type,
|
2025-09-22 17:46:28 +08:00
|
|
|
|
name: this.newTaskForm.name,
|
|
|
|
|
|
description: this.newTaskForm.description,
|
2025-09-22 18:22:29 +08:00
|
|
|
|
parameters: this.newTaskForm.parameters,
|
2025-09-22 17:46:28 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-09-22 18:10:12 +08:00
|
|
|
|
this.plan.tasks.push(newTask);
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.updateContentType();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.success(`子任务 "${newTask.name}" 已添加`);
|
|
|
|
|
|
this.addTaskDialogVisible = false;
|
|
|
|
|
|
this.resetAddTaskDialog();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
resetAddTaskDialog() {
|
|
|
|
|
|
this.$refs.newTaskFormRef.resetFields();
|
|
|
|
|
|
this.newTaskForm = {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
type: 'delay_task',
|
2025-09-22 17:46:28 +08:00
|
|
|
|
name: '',
|
|
|
|
|
|
description: '',
|
2025-09-22 18:22:29 +08:00
|
|
|
|
parameters: { delay_duration: 1 }, // Reset with default delay_duration
|
2025-09-22 17:46:28 +08:00
|
|
|
|
};
|
2025-09-22 17:16:05 +08:00
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
editTask(task) {
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.info('编辑任务功能正在开发中');
|
2025-09-22 17:10:21 +08:00
|
|
|
|
console.log('编辑任务:', task);
|
|
|
|
|
|
},
|
2025-09-22 17:46:28 +08:00
|
|
|
|
deleteTask(taskToDelete) {
|
|
|
|
|
|
ElMessageBox.confirm(`确认删除任务 "${taskToDelete.name}" 吗?`, '提示', {
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}).then(() => {
|
2025-09-22 18:10:12 +08:00
|
|
|
|
this.plan.tasks = this.plan.tasks.filter(task => task.id !== taskToDelete.id);
|
2025-09-22 18:22:29 +08:00
|
|
|
|
this.plan.tasks.forEach((item, index) => item.execution_order = index + 1);
|
|
|
|
|
|
this.updateContentType();
|
2025-09-22 17:46:28 +08:00
|
|
|
|
ElMessage.success('任务已删除');
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
2025-09-22 17:10:21 +08:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2025-09-22 17:21:08 +08:00
|
|
|
|
.plan-detail {
|
2025-09-22 17:10:21 +08:00
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.loading, .error {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.retry-btn {
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
}
|
2025-09-22 18:22:29 +08:00
|
|
|
|
.sub-plan-wrapper {
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
2025-09-22 17:10:21 +08:00
|
|
|
|
.sub-plan-container {
|
|
|
|
|
|
margin-left: 20px;
|
|
|
|
|
|
margin-top: 10px;
|
2025-09-22 18:22:29 +08:00
|
|
|
|
border-left: 2px solid #ebeef5;
|
2025-09-22 17:46:28 +08:00
|
|
|
|
padding-left: 10px;
|
2025-09-22 17:10:21 +08:00
|
|
|
|
}
|
2025-09-22 18:22:29 +08:00
|
|
|
|
.sub-plan-card-content {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.sub-plan-actions {
|
|
|
|
|
|
align-self: flex-end;
|
|
|
|
|
|
}
|
2025-09-22 17:10:21 +08:00
|
|
|
|
.card-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
2025-09-22 17:46:28 +08:00
|
|
|
|
/* 调整子计划卡片内部的header,避免重复样式 */
|
|
|
|
|
|
.sub-plan-container .card-header {
|
2025-09-22 18:22:29 +08:00
|
|
|
|
padding: 0;
|
2025-09-22 17:46:28 +08:00
|
|
|
|
}
|
2025-09-22 17:10:21 +08:00
|
|
|
|
</style>
|