This commit is contained in:
2025-10-31 21:56:33 +08:00
parent fcd1b1c140
commit 840fb98988
3 changed files with 264 additions and 235 deletions

View File

@@ -197,10 +197,7 @@ import http from '../utils/http';
* @returns {Promise<Array<PigBatchResponseDTO>>} * @returns {Promise<Array<PigBatchResponseDTO>>}
*/ */
export const getPigBatches = (params) => { export const getPigBatches = (params) => {
const newParams = { return http.get('/api/v1/pig-batches', params);
is_active: params.is_active,
};
return http.get('/api/v1/pig-batches', { params: newParams });
}; };
/** /**
@@ -209,7 +206,7 @@ export const getPigBatches = (params) => {
* @returns {Promise<PigBatchResponseDTO>} * @returns {Promise<PigBatchResponseDTO>}
*/ */
export const createPigBatch = (batchData) => { export const createPigBatch = (batchData) => {
return http.post('/api/v1/pig-batches', batchData); return http.post('/api/v1/pig-batches', batchData);
}; };
/** /**
@@ -218,7 +215,7 @@ export const createPigBatch = (batchData) => {
* @returns {Promise<PigBatchResponseDTO>} * @returns {Promise<PigBatchResponseDTO>}
*/ */
export const getPigBatchById = (id) => { export const getPigBatchById = (id) => {
return http.get(`/api/v1/pig-batches/${id}`); return http.get(`/api/v1/pig-batches/${id}`);
}; };
/** /**
@@ -228,7 +225,7 @@ export const getPigBatchById = (id) => {
* @returns {Promise<PigBatchResponseDTO>} * @returns {Promise<PigBatchResponseDTO>}
*/ */
export const updatePigBatch = (id, batchData) => { export const updatePigBatch = (id, batchData) => {
return http.put(`/api/v1/pig-batches/${id}`, batchData); return http.put(`/api/v1/pig-batches/${id}`, batchData);
}; };
/** /**
@@ -237,7 +234,7 @@ export const updatePigBatch = (id, batchData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const deletePigBatch = (id) => { export const deletePigBatch = (id) => {
return http.delete(`/api/v1/pig-batches/${id}`); return http.delete(`/api/v1/pig-batches/${id}`);
}; };
// --- 猪批次业务操作 --- // --- 猪批次业务操作 ---
@@ -249,7 +246,7 @@ export const deletePigBatch = (id) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const assignPensToBatch = (id, pensData) => { export const assignPensToBatch = (id, pensData) => {
return http.post(`/api/v1/pig-batches/assign-pens/${id}`, pensData); return http.post(`/api/v1/pig-batches/assign-pens/${id}`, pensData);
}; };
/** /**
@@ -259,7 +256,7 @@ export const assignPensToBatch = (id, pensData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const removePenFromBatch = (penID, batchID) => { export const removePenFromBatch = (penID, batchID) => {
return http.delete(`/api/v1/pig-batches/remove-pen/${penID}/${batchID}`); return http.delete(`/api/v1/pig-batches/remove-pen/${penID}/${batchID}`);
}; };
/** /**
@@ -269,7 +266,7 @@ export const removePenFromBatch = (penID, batchID) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const buyPigsForBatch = (id, buyData) => { export const buyPigsForBatch = (id, buyData) => {
return http.post(`/api/v1/pig-batches/buy-pigs/${id}`, buyData); return http.post(`/api/v1/pig-batches/buy-pigs/${id}`, buyData);
}; };
/** /**
@@ -279,7 +276,7 @@ export const buyPigsForBatch = (id, buyData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const sellPigsFromBatch = (id, sellData) => { export const sellPigsFromBatch = (id, sellData) => {
return http.post(`/api/v1/pig-batches/sell-pigs/${id}`, sellData); return http.post(`/api/v1/pig-batches/sell-pigs/${id}`, sellData);
}; };
/** /**
@@ -289,7 +286,7 @@ export const sellPigsFromBatch = (id, sellData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const movePigsIntoPen = (id, moveData) => { export const movePigsIntoPen = (id, moveData) => {
return http.post(`/api/v1/pig-batches/move-pigs-into-pen/${id}`, moveData); return http.post(`/api/v1/pig-batches/move-pigs-into-pen/${id}`, moveData);
}; };
/** /**
@@ -299,7 +296,7 @@ export const movePigsIntoPen = (id, moveData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const transferPigsWithinBatch = (id, transferData) => { export const transferPigsWithinBatch = (id, transferData) => {
return http.post(`/api/v1/pig-batches/transfer-within-batch/${id}`, transferData); return http.post(`/api/v1/pig-batches/transfer-within-batch/${id}`, transferData);
}; };
/** /**
@@ -309,7 +306,7 @@ export const transferPigsWithinBatch = (id, transferData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const transferPigsAcrossBatches = (sourceBatchID, transferData) => { export const transferPigsAcrossBatches = (sourceBatchID, transferData) => {
return http.post(`/api/v1/pig-batches/transfer-across-batches/${sourceBatchID}`, transferData); return http.post(`/api/v1/pig-batches/transfer-across-batches/${sourceBatchID}`, transferData);
}; };
/** /**
@@ -319,7 +316,7 @@ export const transferPigsAcrossBatches = (sourceBatchID, transferData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const reclassifyPenToNewBatch = (fromBatchID, reclassifyData) => { export const reclassifyPenToNewBatch = (fromBatchID, reclassifyData) => {
return http.post(`/api/v1/pig-batches/reclassify-pen/${fromBatchID}`, reclassifyData); return http.post(`/api/v1/pig-batches/reclassify-pen/${fromBatchID}`, reclassifyData);
}; };
// --- 猪只数量变更记录 --- // --- 猪只数量变更记录 ---
@@ -331,7 +328,7 @@ export const reclassifyPenToNewBatch = (fromBatchID, reclassifyData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordSickPigsInBatch = (id, sickData) => { export const recordSickPigsInBatch = (id, sickData) => {
return http.post(`/api/v1/pig-batches/record-sick-pigs/${id}`, sickData); return http.post(`/api/v1/pig-batches/record-sick-pigs/${id}`, sickData);
}; };
/** /**
@@ -341,7 +338,7 @@ export const recordSickPigsInBatch = (id, sickData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordSickPigRecoveryInBatch = (id, recoveryData) => { export const recordSickPigRecoveryInBatch = (id, recoveryData) => {
return http.post(`/api/v1/pig-batches/record-sick-pig-recovery/${id}`, recoveryData); return http.post(`/api/v1/pig-batches/record-sick-pig-recovery/${id}`, recoveryData);
}; };
/** /**
@@ -351,7 +348,7 @@ export const recordSickPigRecoveryInBatch = (id, recoveryData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordSickPigDeathInBatch = (id, deathData) => { export const recordSickPigDeathInBatch = (id, deathData) => {
return http.post(`/api/v1/pig-batches/record-sick-pig-death/${id}`, deathData); return http.post(`/api/v1/pig-batches/record-sick-pig-death/${id}`, deathData);
}; };
/** /**
@@ -361,7 +358,7 @@ export const recordSickPigDeathInBatch = (id, deathData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordSickPigCullInBatch = (id, cullData) => { export const recordSickPigCullInBatch = (id, cullData) => {
return http.post(`/api/v1/pig-batches/record-sick-pig-cull/${id}`, cullData); return http.post(`/api/v1/pig-batches/record-sick-pig-cull/${id}`, cullData);
}; };
/** /**
@@ -371,7 +368,7 @@ export const recordSickPigCullInBatch = (id, cullData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordDeathInBatch = (id, deathData) => { export const recordDeathInBatch = (id, deathData) => {
return http.post(`/api/v1/pig-batches/record-death/${id}`, deathData); return http.post(`/api/v1/pig-batches/record-death/${id}`, deathData);
}; };
/** /**
@@ -381,7 +378,7 @@ export const recordDeathInBatch = (id, deathData) => {
* @returns {Promise<Response>} * @returns {Promise<Response>}
*/ */
export const recordCullInBatch = (id, cullData) => { export const recordCullInBatch = (id, cullData) => {
return http.post(`/api/v1/pig-batches/record-cull/${id}`, cullData); return http.post(`/api/v1/pig-batches/record-cull/${id}`, cullData);
}; };
// --- 新增的猪栏和猪舍API --- // --- 新增的猪栏和猪舍API ---
@@ -391,7 +388,7 @@ export const recordCullInBatch = (id, cullData) => {
* @returns {Promise<Array<PenResponse>>} * @returns {Promise<Array<PenResponse>>}
*/ */
export const getAllPens = () => { export const getAllPens = () => {
return http.get('/api/v1/pens'); return http.get('/api/v1/pens');
}; };
/** /**
@@ -399,29 +396,29 @@ export const getAllPens = () => {
* @returns {Promise<Array<PigHouseResponse>>} * @returns {Promise<Array<PigHouseResponse>>}
*/ */
export const getAllPigHouses = () => { export const getAllPigHouses = () => {
return http.get('/api/v1/pig-houses'); return http.get('/api/v1/pig-houses');
}; };
export const PigBatchApi = { export const PigBatchApi = {
getPigBatches, getPigBatches,
createPigBatch, createPigBatch,
getPigBatchById, getPigBatchById,
updatePigBatch, updatePigBatch,
deletePigBatch, deletePigBatch,
assignPensToBatch, assignPensToBatch,
removePenFromBatch, removePenFromBatch,
buyPigsForBatch, buyPigsForBatch,
sellPigsFromBatch, sellPigsFromBatch,
movePigsIntoPen, movePigsIntoPen,
transferPigsWithinBatch, transferPigsWithinBatch,
transferPigsAcrossBatches, transferPigsAcrossBatches,
reclassifyPenToNewBatch, reclassifyPenToNewBatch,
recordSickPigsInBatch, recordSickPigsInBatch,
recordSickPigRecoveryInBatch, recordSickPigRecoveryInBatch,
recordSickPigDeathInBatch, recordSickPigDeathInBatch,
recordSickPigCullInBatch, recordSickPigCullInBatch,
recordDeathInBatch, recordDeathInBatch,
recordCullInBatch, recordCullInBatch,
getAllPens, getAllPens,
getAllPigHouses, getAllPigHouses,
}; };

View File

@@ -1,15 +1,15 @@
<template> <template>
<div class="plan-detail"> <div class="plan-detail">
<div v-if="loading" class="loading"> <div v-if="loading" class="loading">
<el-skeleton animated /> <el-skeleton animated/>
</div> </div>
<div v-else-if="error" class="error"> <div v-else-if="error" class="error">
<el-alert <el-alert
:title="'加载计划内容失败 (ID: ' + planId + ')'" :title="'加载计划内容失败 (ID: ' + planId + ')'"
:description="error" :description="error"
type="error" type="error"
show-icon show-icon
@close="error = null" @close="error = null"
/> />
<el-button type="primary" @click="fetchPlan" class="retry-btn">重新加载</el-button> <el-button type="primary" @click="fetchPlan" class="retry-btn">重新加载</el-button>
</div> </div>
@@ -20,27 +20,35 @@
<span>{{ plan.name }} - 内容</span> <span>{{ plan.name }} - 内容</span>
<div> <div>
<template v-if="!isSubPlan"> <template v-if="!isSubPlan">
<el-button class="button" type="primary" @click="savePlanContent" v-if="isEditingContent" :disabled="plan.plan_type === '系统任务'">保存</el-button> <el-button class="button" type="primary" @click="savePlanContent" v-if="isEditingContent"
<el-button class="button" type="danger" @click="cancelEdit" v-if="isEditingContent" :disabled="plan.plan_type === '系统任务'">取消</el-button> :disabled="plan.plan_type === '系统任务'">保存
<el-button class="button" @click="enterEditMode" v-else :disabled="plan.plan_type === '系统任务'">编辑内容</el-button> </el-button>
<el-button class="button" type="danger" @click="cancelEdit" v-if="isEditingContent"
:disabled="plan.plan_type === '系统任务'">取消
</el-button>
<el-button class="button" @click="enterEditMode" v-else :disabled="plan.plan_type === '系统任务'">
编辑内容
</el-button>
</template> </template>
<!-- Dynamic Add Buttons --> <!-- Dynamic Add Buttons -->
<template v-if="isEditingContent"> <template v-if="isEditingContent">
<el-button <el-button
v-if="plan.content_type === 'sub_plans' || !plan.content_type" v-if="plan.content_type === 'sub_plans' || !plan.content_type"
type="primary" type="primary"
size="small" size="small"
@click="showAddSubPlanDialog" @click="showAddSubPlanDialog"
:disabled="plan.plan_type === '系统任务'" :disabled="plan.plan_type === '系统任务'"
>增加子计划</el-button> >增加子计划
</el-button>
<el-button <el-button
v-if="plan.content_type === 'tasks' || !plan.content_type" v-if="plan.content_type === 'tasks' || !plan.content_type"
type="primary" type="primary"
size="small" size="small"
@click="showTaskEditorDialog()" @click="showTaskEditorDialog()"
:disabled="plan.plan_type === '系统任务'" :disabled="plan.plan_type === '系统任务'"
>增加子任务</el-button> >增加子任务
</el-button>
</template> </template>
</div> </div>
</div> </div>
@@ -51,10 +59,10 @@
<h4>任务列表</h4> <h4>任务列表</h4>
<el-timeline v-if="plan.tasks.length > 0"> <el-timeline v-if="plan.tasks.length > 0">
<el-timeline-item <el-timeline-item
v-for="(task, index) in plan.tasks" v-for="(task, index) in plan.tasks"
:key="task.id || 'new-task-' + index" :key="task.id || 'new-task-' + index"
:timestamp="'执行顺序: ' + (task.execution_order !== undefined ? task.execution_order : index + 1)" :timestamp="'执行顺序: ' + (task.execution_order !== undefined ? task.execution_order : index + 1)"
placement="top" placement="top"
> >
<el-card> <el-card>
<h5>{{ task.name }} ({{ task.type === 'waiting' ? '延时任务' : '未知任务' }})</h5> <h5>{{ task.name }} ({{ task.type === 'waiting' ? '延时任务' : '未知任务' }})</h5>
@@ -63,8 +71,12 @@
延时: {{ task.parameters.delay_duration }} 延时: {{ task.parameters.delay_duration }}
</p> </p>
<el-button-group v-if="isEditingContent"> <el-button-group v-if="isEditingContent">
<el-button type="primary" size="small" @click="editTask(task)" :disabled="plan.plan_type === '系统任务'">编辑</el-button> <el-button type="primary" size="small" @click="editTask(task)"
<el-button type="danger" size="small" @click="deleteTask(task)" :disabled="plan.plan_type === '系统任务'">删除</el-button> :disabled="plan.plan_type === '系统任务'">编辑
</el-button>
<el-button type="danger" size="small" @click="deleteTask(task)"
:disabled="plan.plan_type === '系统任务'">删除
</el-button>
</el-button-group> </el-button-group>
</el-card> </el-card>
</el-timeline-item> </el-timeline-item>
@@ -76,13 +88,16 @@
<div v-else-if="plan.content_type === 'sub_plans'"> <div v-else-if="plan.content_type === 'sub_plans'">
<h4>子计划列表</h4> <h4>子计划列表</h4>
<div v-if="plan.sub_plans.length > 0"> <div v-if="plan.sub_plans.length > 0">
<div v-for="(subPlan, index) in plan.sub_plans" :key="subPlan.id || 'new-subplan-' + index" class="sub-plan-wrapper"> <div v-for="(subPlan, index) in plan.sub_plans" :key="subPlan.id || 'new-subplan-' + index"
class="sub-plan-wrapper">
<el-card> <el-card>
<div class="sub-plan-card-content"> <div class="sub-plan-card-content">
<!-- Pass child_plan_id to recursive PlanDetail --> <!-- Pass child_plan_id to recursive PlanDetail -->
<plan-detail :plan-id="subPlan.child_plan_id" :is-sub-plan="true" /> <plan-detail :plan-id="subPlan.child_plan_id" :is-sub-plan="true"/>
<el-button-group v-if="isEditingContent" class="sub-plan-actions"> <el-button-group v-if="isEditingContent" class="sub-plan-actions">
<el-button type="danger" size="small" @click="deleteSubPlan(subPlan)" :disabled="plan.plan_type === '系统任务'">删除</el-button> <el-button type="danger" size="small" @click="deleteSubPlan(subPlan)"
:disabled="plan.plan_type === '系统任务'">删除
</el-button>
</el-button-group> </el-button-group>
</div> </div>
</el-card> </el-card>
@@ -102,22 +117,22 @@
<!-- Add Sub-plan Dialog --> <!-- Add Sub-plan Dialog -->
<el-dialog <el-dialog
v-model="addSubPlanDialogVisible" v-model="addSubPlanDialogVisible"
title="选择子计划" title="选择子计划"
width="600px" width="600px"
@close="resetAddSubPlanDialog" @close="resetAddSubPlanDialog"
> >
<el-select <el-select
v-model="selectedSubPlanId" v-model="selectedSubPlanId"
placeholder="请选择一个计划作为子计划" placeholder="请选择一个计划作为子计划"
filterable filterable
style="width: 100%;" style="width: 100%;"
> >
<el-option <el-option
v-for="item in availablePlans" v-for="item in availablePlans"
:key="item.id" :key="item.id"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
></el-option> ></el-option>
</el-select> </el-select>
<template #footer> <template #footer>
@@ -130,32 +145,35 @@
<!-- Task Editor Dialog (for Add and Edit) --> <!-- Task Editor Dialog (for Add and Edit) -->
<el-dialog <el-dialog
v-model="taskEditorDialogVisible" v-model="taskEditorDialogVisible"
:title="isEditingTask ? '编辑子任务' : '增加子任务'" :title="isEditingTask ? '编辑子任务' : '增加子任务'"
width="600px" width="600px"
@close="resetTaskEditorDialog" @close="resetTaskEditorDialog"
> >
<el-form :model="currentTaskForm" ref="taskFormRef" :rules="taskFormRules" label-width="100px"> <el-form :model="currentTaskForm" ref="taskFormRef" :rules="taskFormRules" label-width="100px">
<el-form-item label="任务类型" prop="type"> <el-form-item label="任务类型" prop="type">
<el-select v-model="currentTaskForm.type" placeholder="请选择任务类型" style="width: 100%;" :disabled="isEditingTask || plan.plan_type === '系统任务'"> <el-select v-model="currentTaskForm.type" placeholder="请选择任务类型" style="width: 100%;"
:disabled="isEditingTask || plan.plan_type === '系统任务'">
<!-- Only Delay Task for now --> <!-- Only Delay Task for now -->
<el-option label="延时任务" value="delay_task"></el-option> <el-option label="延时任务" value="delay_task"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="任务名称" prop="name"> <el-form-item label="任务名称" prop="name">
<el-input v-model="currentTaskForm.name" placeholder="请输入任务名称" :disabled="plan.plan_type === '系统任务'"></el-input> <el-input v-model="currentTaskForm.name" placeholder="请输入任务名称"
:disabled="plan.plan_type === '系统任务'"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="任务描述" prop="description"> <el-form-item label="任务描述" prop="description">
<el-input type="textarea" v-model="currentTaskForm.description" placeholder="请输入任务描述" :disabled="plan.plan_type === '系统任务'"></el-input> <el-input type="textarea" v-model="currentTaskForm.description" placeholder="请输入任务描述"
:disabled="plan.plan_type === '系统任务'"></el-input>
</el-form-item> </el-form-item>
<!-- Dynamic task component for specific parameters --> <!-- Dynamic task component for specific parameters -->
<template v-if="currentTaskForm.type === 'delay_task'"> <template v-if="currentTaskForm.type === 'delay_task'">
<DelayTaskEditor <DelayTaskEditor
:parameters="currentTaskForm.parameters" :parameters="currentTaskForm.parameters"
@update:parameters="val => currentTaskForm.parameters = val" @update:parameters="val => currentTaskForm.parameters = val"
prop-path="parameters.delay_duration" prop-path="parameters.delay_duration"
:is-editing="true" :is-editing="true"
:disabled="plan.plan_type === '系统任务'" :disabled="plan.plan_type === '系统任务'"
/> />
</template> </template>
<!-- More task types can be rendered here --> <!-- More task types can be rendered here -->
@@ -172,8 +190,8 @@
<script> <script>
import apiClient from '../api/index.js'; import apiClient from '../api/index.js';
import { ElMessage, ElMessageBox } from 'element-plus'; import {ElMessage, ElMessageBox} from 'element-plus';
import { ArrowDown } from '@element-plus/icons-vue'; import {ArrowDown} from '@element-plus/icons-vue';
import DelayTaskEditor from './tasks/DelayTask.vue'; import DelayTaskEditor from './tasks/DelayTask.vue';
export default { export default {
@@ -227,15 +245,15 @@ export default {
parameters: {}, parameters: {},
}, },
taskFormRules: { taskFormRules: {
type: [{ required: true, message: '请选择任务类型', trigger: 'change' }], type: [{required: true, message: '请选择任务类型', trigger: 'change'}],
name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }], name: [{required: true, message: '请输入任务名称', trigger: 'blur'}],
// Rule for delay_duration will be added/removed dynamically // Rule for delay_duration will be added/removed dynamically
}, },
}; };
}, },
computed: { computed: {
delayDurationRules() { delayDurationRules() {
return [{ required: true, message: '请输入延时时间', trigger: 'blur' }]; return [{required: true, message: '请输入延时时间', trigger: 'blur'}];
}, },
}, },
watch: { watch: {
@@ -306,17 +324,17 @@ export default {
execute_num: this.plan.execute_num, execute_num: this.plan.execute_num,
cron_expression: this.plan.cron_expression, cron_expression: this.plan.cron_expression,
sub_plan_ids: this.plan.content_type === 'sub_plans' sub_plan_ids: this.plan.content_type === 'sub_plans'
? this.plan.sub_plans.map(sp => sp.child_plan_id) ? this.plan.sub_plans.map(sp => sp.child_plan_id)
: [], : [],
tasks: this.plan.content_type === 'tasks' tasks: this.plan.content_type === 'tasks'
? this.plan.tasks.map((task, index) => ({ ? this.plan.tasks.map((task, index) => ({
name: task.name, name: task.name,
description: task.description, description: task.description,
type: task.type, type: task.type,
execution_order: index + 1, execution_order: index + 1,
parameters: task.parameters || {}, parameters: task.parameters || {},
})) }))
: [], : [],
}; };
delete submitData.execute_count; delete submitData.execute_count;
@@ -358,9 +376,9 @@ export default {
}, },
async fetchAvailablePlans() { async fetchAvailablePlans() {
try { try {
const response = await apiClient.plans.getPlans({ plan_type: '自定义任务' }); const response = await apiClient.plans.getPlans({plan_type: '自定义任务', page: 1, page_size: 1000});
this.availablePlans = response.data.plans.filter(p => this.availablePlans = response.data.plans.filter(p =>
p.id !== this.planId p.id !== this.planId
); );
} catch (error) { } catch (error) {
ElMessage.error('加载可用计划失败: ' + (error.message || '未知错误')); ElMessage.error('加载可用计划失败: ' + (error.message || '未知错误'));
@@ -412,17 +430,17 @@ export default {
console.log("PlanDetail: Showing task editor dialog."); console.log("PlanDetail: Showing task editor dialog.");
if (this.plan.sub_plans.length > 0 && !task) { if (this.plan.sub_plans.length > 0 && !task) {
ElMessageBox.confirm('当前计划包含子计划,添加任务将清空现有子计划。是否继续?', '警告', { ElMessageBox.confirm('当前计划包含子计划,添加任务将清空现有子计划。是否继续?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
this.plan.sub_plans = []; this.plan.sub_plans = [];
this.taskEditorDialogVisible = true; this.taskEditorDialogVisible = true;
this.prepareTaskForm(task); this.prepareTaskForm(task);
}).catch(() => { }).catch(() => {
// User cancelled // User cancelled
}); });
return; return;
} }
this.taskEditorDialogVisible = true; this.taskEditorDialogVisible = true;
this.prepareTaskForm(task); this.prepareTaskForm(task);
@@ -447,7 +465,7 @@ export default {
this.isEditingTask = false; this.isEditingTask = false;
this.editingTaskOriginalId = null; this.editingTaskOriginalId = null;
// For new tasks, ensure delay_duration is reactive from start // For new tasks, ensure delay_duration is reactive from start
this.currentTaskForm.parameters = { delay_duration: null }; this.currentTaskForm.parameters = {delay_duration: null};
console.log("PlanDetail: Prepared currentTaskForm for adding:", JSON.parse(JSON.stringify(this.currentTaskForm))); console.log("PlanDetail: Prepared currentTaskForm for adding:", JSON.parse(JSON.stringify(this.currentTaskForm)));
} }
// Manually trigger watch for type to ensure rules and default parameters are set // Manually trigger watch for type to ensure rules and default parameters are set
@@ -479,15 +497,14 @@ export default {
name: this.currentTaskForm.name, name: this.currentTaskForm.name,
description: this.currentTaskForm.description, description: this.currentTaskForm.description,
type: this.currentTaskForm.type === 'delay_task' ? 'waiting' : this.currentTaskForm.type, type: this.currentTaskForm.type === 'delay_task' ? 'waiting' : this.currentTaskForm.type,
parameters: { ...this.currentTaskForm.parameters }, // Deep copy parameters to ensure new reference parameters: {...this.currentTaskForm.parameters}, // Deep copy parameters to ensure new reference
}; };
this.plan.tasks.splice(index, 1, updatedTask); // Replace the old task with the new one this.plan.tasks.splice(index, 1, updatedTask); // Replace the old task with the new one
ElMessage.success(`子任务 "${this.currentTaskForm.name}" 已更新`); ElMessage.success(`子任务 "${this.currentTaskForm.name}" 已更新`);
} else { } else {
ElMessage.error('未找到要编辑的任务'); ElMessage.error('未找到要编辑的任务');
} }
} } else {
else {
// Add a new task // Add a new task
const newTask = { const newTask = {
id: Date.now(), id: Date.now(),
@@ -495,7 +512,7 @@ export default {
type: this.currentTaskForm.type === 'delay_task' ? 'waiting' : this.currentTaskForm.type, type: this.currentTaskForm.type === 'delay_task' ? 'waiting' : this.currentTaskForm.type,
name: this.currentTaskForm.name, name: this.currentTaskForm.name,
description: this.currentTaskForm.description, description: this.currentTaskForm.description,
parameters: { ...this.currentTaskForm.parameters }, // Deep copy parameters to ensure new reference parameters: {...this.currentTaskForm.parameters}, // Deep copy parameters to ensure new reference
}; };
this.plan.tasks = [...this.plan.tasks, newTask]; // Create a new array reference this.plan.tasks = [...this.plan.tasks, newTask]; // Create a new array reference
ElMessage.success(`子任务 "${newTask.name}" 已添加`); ElMessage.success(`子任务 "${newTask.name}" 已添加`);
@@ -544,35 +561,43 @@ export default {
.plan-detail { .plan-detail {
margin-top: 10px; margin-top: 10px;
} }
.loading, .error { .loading, .error {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
} }
.retry-btn { .retry-btn {
margin-top: 15px; margin-top: 15px;
} }
.sub-plan-wrapper { .sub-plan-wrapper {
margin-bottom: 10px; margin-bottom: 10px;
} }
.sub-plan-container { .sub-plan-container {
margin-left: 20px; margin-left: 20px;
margin-top: 10px; margin-top: 10px;
border-left: 2px solid #ebeef5; border-left: 2px solid #ebeef5;
padding-left: 10px; padding-left: 10px;
} }
.sub-plan-card-content { .sub-plan-card-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
} }
.sub-plan-actions { .sub-plan-actions {
align-self: flex-end; align-self: flex-end;
} }
.card-header { .card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
/* 调整子计划卡片内部的header避免重复样式 */ /* 调整子计划卡片内部的header避免重复样式 */
.sub-plan-container .card-header { .sub-plan-container .card-header {
padding: 0; padding: 0;

View File

@@ -6,12 +6,15 @@
<div class="title-container"> <div class="title-container">
<h2 class="page-title">计划管理</h2> <h2 class="page-title">计划管理</h2>
<el-button type="text" @click="loadPlans" class="refresh-btn" title="刷新计划列表"> <el-button type="text" @click="loadPlans" class="refresh-btn" title="刷新计划列表">
<el-icon :size="20"><Refresh /></el-icon> <el-icon :size="20">
<Refresh/>
</el-icon>
</el-button> </el-button>
</div> </div>
<div class="filter-and-add"> <div class="filter-and-add">
<el-select v-model="planTypeFilter" placeholder="选择计划类型" @change="loadPlans" style="width: 150px; margin-right: 10px;"> <el-select v-model="planTypeFilter" placeholder="选择计划类型" @change="loadPlans"
style="width: 150px; margin-right: 10px;">
<el-option label="所有任务" value="所有任务"></el-option> <el-option label="所有任务" value="所有任务"></el-option>
<el-option label="自定义任务" value="自定义任务"></el-option> <el-option label="自定义任务" value="自定义任务"></el-option>
<el-option label="系统任务" value="系统任务"></el-option> <el-option label="系统任务" value="系统任务"></el-option>
@@ -23,33 +26,33 @@
<!-- 加载状态 --> <!-- 加载状态 -->
<div v-if="loading" class="loading"> <div v-if="loading" class="loading">
<el-skeleton animated /> <el-skeleton animated/>
</div> </div>
<!-- 错误状态 --> <!-- 错误状态 -->
<div v-else-if="error" class="error"> <div v-else-if="error" class="error">
<el-alert <el-alert
title="获取计划数据失败" title="获取计划数据失败"
:description="error" :description="error"
type="error" type="error"
show-icon show-icon
closable closable
@close="error = null" @close="error = null"
/> />
<el-button type="primary" @click="loadPlans" class="retry-btn">重新加载</el-button> <el-button type="primary" @click="loadPlans" class="retry-btn">重新加载</el-button>
</div> </div>
<el-table <el-table
v-else v-else
:data="plans" :data="plans"
style="width: 100%" style="width: 100%"
class="plan-list-table" class="plan-list-table"
:fit="true" :fit="true"
:scrollbar-always-on="true" :scrollbar-always-on="true"
@sort-change="handleSortChange"> @sort-change="handleSortChange">
<el-table-column prop="id" label="计划ID" min-width="100" sortable="custom" /> <el-table-column prop="id" label="计划ID" min-width="100" sortable="custom"/>
<el-table-column prop="name" label="计划名称" min-width="120" sortable="custom" /> <el-table-column prop="name" label="计划名称" min-width="120" sortable="custom"/>
<el-table-column prop="description" label="计划描述" min-width="150" /> <el-table-column prop="description" label="计划描述" min-width="150"/>
<el-table-column prop="execution_type" label="执行类型" min-width="150" sortable="custom"> <el-table-column prop="execution_type" label="执行类型" min-width="150" sortable="custom">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.execution_type === 'manual'">手动</el-tag> <el-tag v-if="scope.row.execution_type === 'manual'">手动</el-tag>
@@ -57,7 +60,7 @@
<el-tag v-else type="warning">自动({{ scope.row.execute_num }})</el-tag> <el-tag v-else type="warning">自动({{ scope.row.execute_num }})</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="execute_count" label="已执行次数" min-width="120" sortable="custom" /> <el-table-column prop="execute_count" label="已执行次数" min-width="120" sortable="custom"/>
<el-table-column prop="status" label="状态" min-width="100" sortable="custom"> <el-table-column prop="status" label="状态" min-width="100" sortable="custom">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status === 0" type="danger">禁用计划</el-tag> <el-tag v-if="scope.row.status === 0" type="danger">禁用计划</el-tag>
@@ -73,18 +76,21 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="280"> <el-table-column label="操作" width="280">
<template #default="scope"> <template #default="scope">
<el-button size="small" @click="editPlan(scope.row)" :disabled="scope.row.plan_type === '系统任务'">编辑</el-button> <el-button size="small" @click="editPlan(scope.row)" :disabled="scope.row.plan_type === '系统任务'">编辑
</el-button>
<el-button size="small" @click="showDetails(scope.row)">详情</el-button> <el-button size="small" @click="showDetails(scope.row)">详情</el-button>
<el-button <el-button
size="small" size="small"
:type="scope.row.status === 1 ? 'warning' : 'primary'" :type="scope.row.status === 1 ? 'warning' : 'primary'"
@click="scope.row.status === 1 ? stopPlan(scope.row) : startPlan(scope.row)" @click="scope.row.status === 1 ? stopPlan(scope.row) : startPlan(scope.row)"
:loading="stoppingPlanId === scope.row.id || startingPlanId === scope.row.id" :loading="stoppingPlanId === scope.row.id || startingPlanId === scope.row.id"
:disabled="scope.row.plan_type === '系统任务'" :disabled="scope.row.plan_type === '系统任务'"
> >
{{ scope.row.status === 1 ? '停止' : '启动' }} {{ scope.row.status === 1 ? '停止' : '启动' }}
</el-button> </el-button>
<el-button size="small" type="danger" @click="deletePlan(scope.row)" :disabled="scope.row.plan_type === '系统任务'">删除</el-button> <el-button size="small" type="danger" @click="deletePlan(scope.row)"
:disabled="scope.row.plan_type === '系统任务'">删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -93,23 +99,23 @@
<!-- 计划表单 --> <!-- 计划表单 -->
<PlanForm <PlanForm
v-model:visible="dialogVisible" v-model:visible="dialogVisible"
:plan-data="currentPlan" :plan-data="currentPlan"
:is-edit="isEdit" :is-edit="isEdit"
@success="handlePlanSuccess" @success="handlePlanSuccess"
@cancel="handlePlanCancel" @cancel="handlePlanCancel"
/> />
<!-- 计划详情 --> <!-- 计划详情 -->
<el-dialog <el-dialog
v-model="detailsVisible" v-model="detailsVisible"
title="计划详情" title="计划详情"
width="70%" width="70%"
top="5vh" top="5vh"
> >
<plan-detail <plan-detail
v-if="detailsVisible" v-if="detailsVisible"
:plan-id="selectedPlanIdForDetails" :plan-id="selectedPlanIdForDetails"
/> />
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@@ -122,7 +128,7 @@
</template> </template>
<script> <script>
import { Refresh } from '@element-plus/icons-vue'; import {Refresh} from '@element-plus/icons-vue';
import apiClient from '../../api'; import apiClient from '../../api';
import PlanForm from '../../components/PlanForm.vue'; import PlanForm from '../../components/PlanForm.vue';
import PlanDetail from '../../components/PlanDetail.vue'; // 导入新组件 import PlanDetail from '../../components/PlanDetail.vue'; // 导入新组件
@@ -168,7 +174,7 @@ export default {
this.error = null; this.error = null;
try { try {
const response = await apiClient.plans.getPlans({ plan_type: this.planTypeFilter }); // 传递 plan_typeFilter const response = await apiClient.plans.getPlans({plan_type: this.planTypeFilter, page: 1, page_size: 1000}); // 传递 plan_typeFilter
let fetchedPlans = response.data?.plans || []; let fetchedPlans = response.data?.plans || [];
// Default sort by ID ascending // Default sort by ID ascending
fetchedPlans.sort((a, b) => a.id - b.id); fetchedPlans.sort((a, b) => a.id - b.id);
@@ -183,7 +189,7 @@ export default {
}, },
// 处理表格排序 // 处理表格排序
handleSortChange({ prop, order }) { handleSortChange({prop, order}) {
if (!order) { if (!order) {
// 恢复原始顺序 // 恢复原始顺序
this.plans = [...this.originalPlans]; this.plans = [...this.originalPlans];
@@ -259,7 +265,7 @@ export default {
}, },
editPlan(plan) { editPlan(plan) {
this.currentPlan = { ...plan }; this.currentPlan = {...plan};
this.isEdit = true; this.isEdit = true;
this.dialogVisible = true; this.dialogVisible = true;
}, },
@@ -411,6 +417,7 @@ export default {
.plan-list { .plan-list {
padding: 10px; padding: 10px;
} }
.card-header { .card-header {
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;