From a8760652bda99cdc110ffa52fc280fcc3defc48e Mon Sep 17 00:00:00 2001 From: tangchao0503 <735056338@qq.com> Date: Tue, 2 Jun 2026 18:22:02 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=8C=E8=AE=A1=E5=88=92=E9=87=87?= =?UTF-8?q?=E9=9B=863=EF=BC=9A=20=E5=AE=9E=E7=8E=B0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E9=87=87=E9=9B=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HPPA/HPPA.cpp | 37 +- HPPA/HPPA.h | 4 + HPPA/HPPA.ui | 6 + HPPA/HPPA.vcxproj | 2 +- HPPA/HPPA.vcxproj.filters | 6 +- HPPA/TimedDataCollection.cpp | 42 +- HPPA/TimedDataCollection.h | 20 +- HPPA/TimedDataCollectionDataStructures.cpp | 336 ++++++++++++- HPPA/TimedDataCollectionDataStructures.h | 112 +++++ HPPA/TimedDataCollection_ui.ui | 543 ++++++++++----------- 10 files changed, 789 insertions(+), 319 deletions(-) diff --git a/HPPA/HPPA.cpp b/HPPA/HPPA.cpp index f4eff02..2057240 100644 --- a/HPPA/HPPA.cpp +++ b/HPPA/HPPA.cpp @@ -575,6 +575,10 @@ HPPA::HPPA(QWidget* parent) initMapTools(); + //定时采集 + connect(this->ui.mActionTimedDataCollection, SIGNAL(triggered()), this, SLOT(onTimedDataCollection())); + mTimedDataCollectionWindow = new TimedDataCollection(m_tmc); + QString strPath = QCoreApplication::applicationDirPath() + "/UILayout.ini"; QFile file(strPath); if (file.open(QIODevice::ReadOnly)) @@ -588,6 +592,23 @@ HPPA::HPPA(QWidget* parent) this->showMaximized(); } +void HPPA::onTimedDataCollection() +{ + QAction* checkedScenario = m_ScenarioActionGroup->checkedAction(); + QString checkedScenarioName = checkedScenario->objectName(); + if (checkedScenarioName == "mAction3DPlantPhenotypeScenario")//计划采集 + { + mTimedDataCollectionWindow->show(); + //mTimedDataCollectionWindow->exec(); + + return; + } + else if (checkedScenarioName == "mActionPlantPhenotypeScenario") + { + + } +} + void HPPA::initMenubarToolbar() { //自定义菜单栏和工具栏 @@ -1523,22 +1544,6 @@ bool HPPA::showResultMessageBox(QString title, QString msg) void HPPA::onStartRecordStep1() { - QAction* checkedScenario = m_ScenarioActionGroup->checkedAction(); - QString checkedScenarioName = checkedScenario->objectName(); - if (checkedScenarioName == "mAction3DPlantPhenotypeScenario")//计划采集 - { - TimedDataCollection* tmp = new TimedDataCollection(); - /*m_ic->setWindowFlags(Qt::Widget);*/ - tmp->show(); - //tmp->exec(); - - return; - } - else if (checkedScenarioName == "mActionPlantPhenotypeScenario") - { - - } - //判断移动平台 QAction* checked = moveplatformActionGroup->checkedAction(); if (!checked) diff --git a/HPPA/HPPA.h b/HPPA/HPPA.h index b8bd82b..6222afc 100644 --- a/HPPA/HPPA.h +++ b/HPPA/HPPA.h @@ -318,6 +318,8 @@ private: bool showResultMessageBox(QString title, QString msg); void disconnectImagerAndCleanup(); + TimedDataCollection* mTimedDataCollectionWindow; + public Q_SLOTS: void onPlotHyperspectralImageRgbImage(int fileNumber, int frameNumber, QString filePath); void focusPlotSpectralImg(int state); @@ -395,6 +397,8 @@ public Q_SLOTS: void onMapToolPanTriggered(); void onMapToolSpectralTriggered(); + + void onTimedDataCollection(); protected: void closeEvent(QCloseEvent* event) override; diff --git a/HPPA/HPPA.ui b/HPPA/HPPA.ui index ed4dd23..16c3dc1 100644 --- a/HPPA/HPPA.ui +++ b/HPPA/HPPA.ui @@ -105,6 +105,7 @@ color:white; + @@ -743,6 +744,11 @@ QPushButton:pressed 3D植物表型 + + + 定时采集 + + diff --git a/HPPA/HPPA.vcxproj b/HPPA/HPPA.vcxproj index f51981d..2da6df4 100644 --- a/HPPA/HPPA.vcxproj +++ b/HPPA/HPPA.vcxproj @@ -254,7 +254,7 @@ - + diff --git a/HPPA/HPPA.vcxproj.filters b/HPPA/HPPA.vcxproj.filters index 2a2ab2c..87b4009 100644 --- a/HPPA/HPPA.vcxproj.filters +++ b/HPPA/HPPA.vcxproj.filters @@ -384,6 +384,9 @@ Header Files + + Header Files + @@ -440,9 +443,6 @@ Header Files - - Header Files - diff --git a/HPPA/TimedDataCollection.cpp b/HPPA/TimedDataCollection.cpp index b49eb1a..cc7fcd7 100644 --- a/HPPA/TimedDataCollection.cpp +++ b/HPPA/TimedDataCollection.cpp @@ -1,9 +1,12 @@ #include "TimedDataCollection.h" #include #include +#include "TwoMotorControl.h" -TimedDataCollection::TimedDataCollection(QWidget* parent) +TimedDataCollection::TimedDataCollection(TwoMotorControl* motorControl, QWidget* parent) : QDialog(parent) + , m_motorControl(nullptr) + , m_scheduler(nullptr) { ui.setupUi(this); @@ -12,21 +15,52 @@ TimedDataCollection::TimedDataCollection(QWidget* parent) ui.treeWidget->setDropIndicatorShown(true); // 显示插入位置指示线 ui.treeWidget->setDragDropMode(QAbstractItemView::InternalMove); // 内部移动 + // 初始化调度器 + m_scheduler = new TaskScheduler(this); + + // 连接调度器信号 + connect(m_scheduler, &TaskScheduler::taskStarted, this, &TimedDataCollection::taskStarted); + connect(m_scheduler, &TaskScheduler::taskFinished, this, &TimedDataCollection::taskFinished); + connect(m_scheduler, &TaskScheduler::subTaskStarted, this, &TimedDataCollection::subTaskStarted); + connect(m_scheduler, &TaskScheduler::subTaskFinished, this, &TimedDataCollection::subTaskFinished); + connect(m_scheduler, &TaskScheduler::motorPositionUpdated, this, &TimedDataCollection::motorPositionUpdated); + connect(m_scheduler, &TaskScheduler::errorOccurred, this, &TimedDataCollection::errorOccurred); + //writeRead(); readTimedTaskFromFile("D:/0tmp/3Dtest/task.json"); - connect(ui.run_btn, SIGNAL(clicked()), this, SLOT(run())); + // 加载任务到调度器 + m_scheduler->loadTasks(m_loadedTasks); - connect(ui.run_btn, &QPushButton::clicked, this, &TimedDataCollection::run); + connect(ui.run_btn, &QPushButton::clicked, this, &TimedDataCollection::startScheduler); } TimedDataCollection::~TimedDataCollection() { + if (m_scheduler) { + m_scheduler->stop(); + } } -void TimedDataCollection::run() +void TimedDataCollection::startScheduler() { + if (m_scheduler) { + m_scheduler->start(); + } +} +void TimedDataCollection::stopScheduler() +{ + if (m_scheduler) { + m_scheduler->stop(); + } +} + +void TimedDataCollection::setMotorControl(TwoMotorControl* motorControl) +{ + if (m_scheduler) { + m_scheduler->setMotorControl(motorControl); + } } void TimedDataCollection::readTimedTaskFromFile(const QString& filePath) diff --git a/HPPA/TimedDataCollection.h b/HPPA/TimedDataCollection.h index 5c557a6..d1d7df5 100644 --- a/HPPA/TimedDataCollection.h +++ b/HPPA/TimedDataCollection.h @@ -8,23 +8,39 @@ #include "ui_TimedDataCollection_ui.h" #include "TimedDataCollectionDataStructures.h" +class TwoMotorControl; + class TimedDataCollection : public QDialog { Q_OBJECT public: - TimedDataCollection(QWidget* parent = nullptr); + TimedDataCollection(TwoMotorControl* motorControl, QWidget* parent = nullptr); ~TimedDataCollection(); void readTimedTaskFromFile(const QString& filePath); + // 设置马达控制器供调度器使用 + void setMotorControl(TwoMotorControl* motorControl); + public Q_SLOTS: - void run(); + void startScheduler(); + void stopScheduler(); + +Q_SIGNALS: + void taskStarted(int taskId); + void taskFinished(int taskId, bool success); + void subTaskStarted(int taskId, int subTaskIndex); + void subTaskFinished(int taskId, int subTaskIndex); + void motorPositionUpdated(double x, double y); + void errorOccurred(const QString& error); private: Ui::TimedDataCollection_ui ui; + TwoMotorControl* m_motorControl; void writeRead(); QVector m_loadedTasks; + TaskScheduler* m_scheduler; }; diff --git a/HPPA/TimedDataCollectionDataStructures.cpp b/HPPA/TimedDataCollectionDataStructures.cpp index 8c303b0..8cb9b64 100644 --- a/HPPA/TimedDataCollectionDataStructures.cpp +++ b/HPPA/TimedDataCollectionDataStructures.cpp @@ -1,4 +1,4 @@ -#include "TimedDataCollectionDataStructures.h" +#include "TimedDataCollectionDataStructures.h" #include #include #include @@ -194,3 +194,337 @@ bool TimedDataCollectionDataStructuresReaderWriter::jsonToTimedTask(const QJsonO return true; } + +// ==================== TaskExecutor 实现 ==================== + +TaskExecutor::TaskExecutor(TwoMotorControl* motorControl, QObject* parent) + : QObject(parent) + , m_motorControl(motorControl) + , m_currentSubTaskIndex(0) + , m_isRunning(false) +{ + if (m_motorControl) { + connect(m_motorControl, &TwoMotorControl::broadcastLocationSignal, + this, &TaskExecutor::onMotorLocationUpdate); + //connect(m_motorControl, &TwoMotorControl::sequenceComplete, + // this, &TaskExecutor::onSequenceComplete); + //connect(m_motorControl, &TwoMotorControl::errorOccurred, + // this, &TaskExecutor::onError); + } +} + +TaskExecutor::~TaskExecutor() +{ + stop(); +} + +void TaskExecutor::execute(const TimedTask& task) +{ + if (m_isRunning) { + qWarning() << "TaskExecutor: Already running, ignoring execute request"; + return; + } + + m_task = task; + m_currentSubTaskIndex = 0; + m_isRunning = true; + + qDebug() << "TaskExecutor: Starting task" << task.id; + + // 开始执行第一个子任务 + executeNextSubTask(); +} + +void TaskExecutor::stop() +{ + if (!m_isRunning) return; + + qDebug() << "TaskExecutor: Stopping task" << m_task.id; + + if (m_motorControl) { + m_motorControl->stop(); + } + + m_isRunning = false; + emit finished(false); +} + +void TaskExecutor::onSequenceComplete(int status) +{ + if (!m_isRunning) return; + + qDebug() << "TaskExecutor: Sequence complete, status:" << status; + + // 更新当前子任务状态 + if (m_currentSubTaskIndex < m_task.subTasks.size()) { + SubTask& subTask = m_task.subTasks[m_currentSubTaskIndex]; + subTask.status = (status == 0) ? TaskStatus::Finished : TaskStatus::Waiting; + + emit subTaskFinished(m_currentSubTaskIndex, subTask.type, (status == 0)); + } + + // 检查是否还有更多子任务 + m_currentSubTaskIndex++; + if (m_currentSubTaskIndex < m_task.subTasks.size()) { + // 执行下一个子任务 + QTimer::singleShot(100, this, &TaskExecutor::executeNextSubTask); + } else { + // 所有子任务完成 + m_isRunning = false; + qDebug() << "TaskExecutor: All subtasks completed"; + emit finished(true); + } +} + +void TaskExecutor::onMotorLocationUpdate(std::vector loc) +{ + if (loc.size() >= 2) { + emit motorPositionUpdated(loc[0], loc[1]); + } +} + +void TaskExecutor::onError(const QString& error) +{ + if (!m_isRunning) return; + + qWarning() << "TaskExecutor: Error occurred:" << error; + m_isRunning = false; + emit errorOccurred(error); + emit finished(false); +} + +void TaskExecutor::executeNextSubTask() +{ + if (!m_isRunning || m_currentSubTaskIndex >= m_task.subTasks.size()) { + return; + } + + SubTask& subTask = m_task.subTasks[m_currentSubTaskIndex]; + subTask.status = TaskStatus::Running; + subTask.startTime = QDateTime::currentDateTime(); + + qDebug() << "TaskExecutor: Starting subtask" << m_currentSubTaskIndex + << "type:" << static_cast(subTask.type); + + emit subTaskStarted(m_currentSubTaskIndex, subTask.type); + + //根据任务类型切换相机类型,还需要考虑相关信号和槽 + + // 如果有路径线文件,加载路径线并启动马达 + if (!subTask.pathLineFilePath.isEmpty()) { + loadPathLinesFromFile(subTask.pathLineFilePath); + } else { + qDebug() << "TaskExecutor: No path line file for subtask"; + } +} + +void TaskExecutor::loadPathLinesFromFile(const QString& filePath) +{ + // 从 .RecordLine3 文件加载路径线 + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "TaskExecutor: Failed to open path line file:" << filePath; + emit errorOccurred("Failed to open path line file: " + filePath); + return; + } + + double number; + file.read(reinterpret_cast(&number), sizeof(double)); + + QVector pathLines; + for (int i = 0; i < static_cast(number) / 6; ++i) { + PathLine line; + for (int j = 0; j < 6; ++j) { + double value; + file.read(reinterpret_cast(&value), sizeof(double)); + switch (j) { + case 0: line.targetYPosition = value; break; + case 1: line.speedTargetYPosition = value; break; + case 2: line.targetXMinPosition = value; break; + case 3: line.speedTargetXMinPosition = value; break; + case 4: line.targetXMaxPosition = value; break; + case 5: line.speedTargetXMaxPosition = value; break; + } + } + pathLines.append(line); + } + file.close(); + + qDebug() << "TaskExecutor: Loaded" << pathLines.size() << "path lines from" << filePath; + + // 通知马达控制器开始执行 + if (m_motorControl) { + emit m_motorControl->start(pathLines); + } +} + +// ==================== TaskScheduler 实现 ==================== + +TaskScheduler::TaskScheduler(QObject* parent) + : QObject(parent) + , m_timer(nullptr) + , m_motorControl(nullptr) + , m_currentExecutor(nullptr) + , m_currentTaskId(-1) +{ +} + +TaskScheduler::~TaskScheduler() +{ + stop(); +} + +void TaskScheduler::setMotorControl(TwoMotorControl* motorControl) +{ + m_motorControl = motorControl; +} + +void TaskScheduler::loadTasks(const QVector& tasks) +{ + m_tasks = tasks; + qDebug() << "TaskScheduler: Loaded" << tasks.size() << "tasks"; +} + +void TaskScheduler::start() +{ + if (m_timer) { + qDebug() << "TaskScheduler: Already running"; + return; + } + + qDebug() << "TaskScheduler: Starting"; + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &TaskScheduler::checkTasks); + m_timer->start(1000); // 每秒检查一次 + + emit schedulerStateChanged(true); +} + +void TaskScheduler::stop() +{ + if (m_timer) { + m_timer->stop(); + delete m_timer; + m_timer = nullptr; + } + + if (m_currentExecutor) { + m_currentExecutor->stop(); + m_currentExecutor->deleteLater(); + m_currentExecutor = nullptr; + } + + qDebug() << "TaskScheduler: Stopped"; + emit schedulerStateChanged(false); +} + +void TaskScheduler::checkTasks() +{ + if (!m_motorControl) return; + + QDateTime now = QDateTime::currentDateTime(); + + for (auto& task : m_tasks) { + if (task.status != TaskStatus::Waiting) continue; + if (task.scheduledTime > now) continue; + + // 到达计划时间,启动任务 + executeTask(task); + break; // 一次只执行一个任务 + } +} + +void TaskScheduler::onTaskFinished(bool success) +{ + if (m_currentTaskId > 0) { + TaskStatus status = success ? TaskStatus::Finished : TaskStatus::Waiting; + updateTaskStatus(m_currentTaskId, status); + emit taskFinished(m_currentTaskId, success); + } + + // 清理执行器 + if (m_currentExecutor) { + m_currentExecutor->deleteLater(); + m_currentExecutor = nullptr; + } + m_currentTaskId = -1; +} + +void TaskScheduler::onSubTaskStarted(int subTaskIndex, SubTaskType type) +{ + if (m_currentTaskId > 0) { + emit subTaskStarted(m_currentTaskId, subTaskIndex); + } +} + +void TaskScheduler::onSubTaskFinished(int subTaskIndex, SubTaskType type, bool success) +{ + if (m_currentTaskId > 0) { + emit subTaskFinished(m_currentTaskId, subTaskIndex); + } +} + +void TaskScheduler::onExecutorError(const QString& error) +{ + emitError(error); +} + +void TaskScheduler::onExecutorMotorPositionUpdated(double x, double y) +{ + emit motorPositionUpdated(x, y); +} + +void TaskScheduler::executeTask(TimedTask& task) +{ + if (!m_motorControl) { + emitError("Motor control not set"); + return; + } + + qDebug() << "TaskScheduler: Executing task" << task.id; + + updateTaskStatus(task.id, TaskStatus::Running); + m_currentTaskId = task.id; + + emit taskStarted(task.id); + + // 创建任务执行器 + m_currentExecutor = new TaskExecutor(m_motorControl, this); + + // 连接信号 + connect(m_currentExecutor, &TaskExecutor::finished, + this, &TaskScheduler::onTaskFinished); + connect(m_currentExecutor, &TaskExecutor::subTaskStarted, + this, &TaskScheduler::onSubTaskStarted); + connect(m_currentExecutor, &TaskExecutor::subTaskFinished, + this, &TaskScheduler::onSubTaskFinished); + connect(m_currentExecutor, &TaskExecutor::errorOccurred, + this, &TaskScheduler::onExecutorError); + connect(m_currentExecutor, &TaskExecutor::motorPositionUpdated, + this, &TaskScheduler::onExecutorMotorPositionUpdated); + + // 开始执行 + m_currentExecutor->execute(task); +} + +void TaskScheduler::updateTaskStatus(int taskId, TaskStatus status) +{ + for (auto& task : m_tasks) { + if (task.id == taskId) { + task.status = status; + if (status == TaskStatus::Running) { + task.startTime = QDateTime::currentDateTime(); + } else if (status == TaskStatus::Finished) { + task.endTime = QDateTime::currentDateTime(); + } + break; + } + } +} + +void TaskScheduler::emitError(const QString& error) +{ + qWarning() << "TaskScheduler: Error:" << error; + emit errorOccurred(error); +} diff --git a/HPPA/TimedDataCollectionDataStructures.h b/HPPA/TimedDataCollectionDataStructures.h index ef8be0b..a4976a6 100644 --- a/HPPA/TimedDataCollectionDataStructures.h +++ b/HPPA/TimedDataCollectionDataStructures.h @@ -4,6 +4,10 @@ #include #include #include +#include + +#include "TwoMotorControl.h" +#include "CaptureCoordinator.h" // ==================== 枚举定义 ==================== @@ -102,3 +106,111 @@ private: static QString subTaskTypeToString(SubTaskType type); static SubTaskType stringToSubTaskType(const QString& str); }; + +// ==================== 前向声明 ==================== + +class TwoMotorControl; + +// ==================== 任务执行器 ==================== + +class TaskExecutor : public QObject +{ + Q_OBJECT + +public: + explicit TaskExecutor(TwoMotorControl* motorControl, QObject* parent = nullptr); + ~TaskExecutor(); + + // 执行任务 + void execute(const TimedTask& task); + + // 获取当前执行的子任务索引 + int currentSubTaskIndex() const { return m_currentSubTaskIndex; } + + // 获取正在执行的任务 + const TimedTask& currentTask() const { return m_task; } + + // 是否正在执行 + bool isRunning() const { return m_isRunning; } + + // 停止执行 + void stop(); + +signals: + void finished(bool success); // 任务完成 + void subTaskStarted(int subTaskIndex, SubTaskType type); // 子任务开始 + void subTaskFinished(int subTaskIndex, SubTaskType type, bool success); // 子任务完成 + void motorPositionUpdated(double x, double y); // 马达位置更新 + void errorOccurred(const QString& error); // 错误发生 + +private slots: + void onSequenceComplete(int status); + void onMotorLocationUpdate(std::vector loc); + void onError(const QString& error); + +private: + void executeNextSubTask(); + void loadPathLinesFromFile(const QString& filePath); + + TwoMotorControl* m_motorControl; + TimedTask m_task; + int m_currentSubTaskIndex; + bool m_isRunning; +}; + +// ==================== 任务调度器 ==================== + +class TaskScheduler : public QObject +{ + Q_OBJECT + +public: + explicit TaskScheduler(QObject* parent = nullptr); + ~TaskScheduler(); + + // 设置马达控制器 + void setMotorControl(TwoMotorControl* motorControl); + + // 加载任务列表 + void loadTasks(const QVector& tasks); + + // 获取任务列表 + QVector tasks() const { return m_tasks; } + + // 开始调度 + void start(); + + // 停止调度 + void stop(); + + // 是否正在运行 + bool isRunning() const { return m_timer != nullptr && m_timer->isActive(); } + +signals: + void taskStarted(int taskId); // 任务开始 + void taskFinished(int taskId, bool success); // 任务完成 + void subTaskStarted(int taskId, int subTaskIndex); // 子任务开始 + void subTaskFinished(int taskId, int subTaskIndex); // 子任务完成 + void motorPositionUpdated(double x, double y); // 马达位置更新 + void errorOccurred(const QString& error); // 错误发生 + void schedulerStateChanged(bool running); // 调度器状态变化 + +private slots: + void checkTasks(); // 检查任务是否该启动 + void onTaskFinished(bool success); + void onSubTaskStarted(int subTaskIndex, SubTaskType type); + void onSubTaskFinished(int subTaskIndex, SubTaskType type, bool success); + void onExecutorError(const QString& error); + void onExecutorMotorPositionUpdated(double x, double y); + +private: + void executeTask(TimedTask& task); + void updateTaskStatus(int taskId, TaskStatus status); + void emitError(const QString& error); + + QTimer* m_timer; + QVector m_tasks; + TwoMotorControl* m_motorControl; + TaskExecutor* m_currentExecutor; + int m_currentTaskId; +}; diff --git a/HPPA/TimedDataCollection_ui.ui b/HPPA/TimedDataCollection_ui.ui index 546dca3..de0255e 100644 --- a/HPPA/TimedDataCollection_ui.ui +++ b/HPPA/TimedDataCollection_ui.ui @@ -6,314 +6,273 @@ 0 0 - 970 - 456 + 1160 + 576 Dialog - - - - 9 - 9 - 491 - 281 - - - - false - - - true - - - - 任务 - - - - - 计划时间 - - - - - 开始时间 - - - - - 结束时间 - - - - - 耗时 - - - - - 状态 - - - - - 1 - - - - - - - - - - - - - - - - - - - a - - - - - - - - - - - - - - - - - - - - b - - + + + + + + + + false + + + true + + + + 任务 + + + + + 计划时间 + + + + + 开始时间 + + + + + 结束时间 + + + + + 耗时 + + + + + 状态 + + + + + 1 + + + + + + + + + + + + + + + + + + + a + + + + + + + + + + + + + + + + + + + + b + + + + + + 2 + + + + + + + + + + + + + + + + + + + a + + + + + b + + + + + + 3 + + + + + + + + + + + + + + + + + + + a + + + + + b + + + + + + 4 + + + + + + + + + + + + + + + + + + + a + + + + + b + + + + + + 5 + + + + + + + + + + + + + + + + + + + a + + + + + b + + + + + + + + + 1 + + + + + + + - - - 2 - - - - - - - - - - - - - - - - - + + - a + 添加总计划任务 - - - - b - - + - - - 3 - - - - - - - - - - - - - - - - - + + - a + 删除总计划任务 - - - - b - - + - - - 4 - - - - - - - - - - - - - - - - - + + - a + 添加子任务 - - - - b - - + - - - 5 - - - - - - - - - - - - - - - - - - - a - - - - - b - - + + - - - - - 40 - 310 - 101 - 23 - - - - 添加总计划任务 - - - - - - 40 - 370 - 101 - 23 - - - - 添加子任务 - - - - - - 170 - 370 - 69 - 22 - - - - - - - 40 - 410 - 101 - 23 - - - - 删除子任务 - - - - - - 40 - 340 - 101 - 23 - - - - 删除总计划任务 - - - - - - 520 - 20 - 381 - 261 - - - - 1 - - - - - - - - 850 - 420 - 101 - 23 - - - - 运行 - - + + + + 删除子任务 + + + + + + + 运行 + + + +