2025-10-25 18:47:00 +08:00
|
|
|
|
<template>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<AdminLayout title="定时任务" subtitle="定时任务管理">
|
|
|
|
|
|
<div class="task-management">
|
|
|
|
|
|
<div class="header">
|
|
|
|
|
|
<h2>定时任务管理</h2>
|
|
|
|
|
|
<el-button type="primary" @click="handleAdd">
|
|
|
|
|
|
<el-icon><Plus /></el-icon>
|
|
|
|
|
|
新增任务
|
2025-10-25 18:47:00 +08:00
|
|
|
|
</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<!-- 搜索筛选区域 -->
|
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
|
<div class="search-item">
|
|
|
|
|
|
<span class="search-label">任务名称</span>
|
2025-10-25 18:47:00 +08:00
|
|
|
|
<el-input
|
2025-10-28 19:04:35 +08:00
|
|
|
|
v-model="searchForm.taskName"
|
|
|
|
|
|
placeholder="请输入任务名称"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
clearable
|
2025-10-28 19:04:35 +08:00
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
|
style="width: 200px"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<div class="search-item">
|
|
|
|
|
|
<span class="search-label">任务分组</span>
|
2025-10-25 18:47:00 +08:00
|
|
|
|
<el-input
|
2025-10-28 19:04:35 +08:00
|
|
|
|
v-model="searchForm.taskGroup"
|
|
|
|
|
|
placeholder="请输入任务分组"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
clearable
|
2025-10-28 19:04:35 +08:00
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
|
style="width: 200px"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<div class="search-item">
|
|
|
|
|
|
<span class="search-label">状态</span>
|
|
|
|
|
|
<el-select
|
|
|
|
|
|
v-model="searchForm.status"
|
|
|
|
|
|
placeholder="请选择状态"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
clearable
|
2025-10-28 19:04:35 +08:00
|
|
|
|
style="width: 150px"
|
2025-10-25 18:47:00 +08:00
|
|
|
|
>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<el-option label="运行中" :value="1" />
|
|
|
|
|
|
<el-option label="已暂停" :value="0" />
|
2025-10-25 18:47:00 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<div class="search-actions">
|
|
|
|
|
|
<el-button type="primary" @click="handleSearch">
|
|
|
|
|
|
<el-icon><Search /></el-icon>
|
|
|
|
|
|
搜索
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button @click="handleReset">
|
|
|
|
|
|
<el-icon><Refresh /></el-icon>
|
|
|
|
|
|
重置
|
|
|
|
|
|
</el-button>
|
2025-10-25 18:47:00 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-28 19:04:35 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 任务列表 -->
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
:data="taskList"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
|
border
|
|
|
|
|
|
stripe
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column prop="taskName" label="任务名称" min-width="150" />
|
|
|
|
|
|
<el-table-column prop="taskGroup" label="任务分组" width="120" />
|
|
|
|
|
|
<el-table-column prop="beanName" label="Bean名称" min-width="150" />
|
|
|
|
|
|
<el-table-column prop="methodName" label="方法名称" width="120" />
|
|
|
|
|
|
<el-table-column prop="cronExpression" label="Cron表达式" min-width="150" />
|
|
|
|
|
|
<el-table-column label="状态" width="100">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
|
|
|
|
|
|
{{ row.status === 1 ? '运行中' : '已暂停' }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="并发" width="80">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag :type="row.concurrent === 1 ? 'success' : 'warning'" size="small">
|
|
|
|
|
|
{{ row.concurrent === 1 ? '允许' : '禁止' }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="createTime" label="创建时间" width="180" />
|
|
|
|
|
|
<el-table-column label="操作" width="300" fixed="right">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-if="row.status === 0"
|
|
|
|
|
|
type="success"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleStart(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
启动
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
v-else
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handlePause(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
暂停
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleExecute(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
执行一次
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleEdit(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
编辑
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
@click="handleDelete(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
删除
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
2025-10-31 19:13:21 +08:00
|
|
|
|
<div class="pagination-container" v-if="total > 0">
|
2025-10-28 19:04:35 +08:00
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="pageParam.pageNumber"
|
|
|
|
|
|
v-model:page-size="pageParam.pageSize"
|
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
|
:total="total"
|
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
|
@current-change="handlePageChange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑对话框 -->
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
|
:title="isEdit ? '编辑任务' : '新增任务'"
|
|
|
|
|
|
width="700px"
|
|
|
|
|
|
@close="resetForm"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="form-content">
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label required">任务名称</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.taskName"
|
|
|
|
|
|
placeholder="请输入任务名称"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label required">任务分组</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.taskGroup"
|
|
|
|
|
|
placeholder="请输入任务分组(如:SYSTEM、BUSINESS)"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label required">Bean名称</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.beanName"
|
|
|
|
|
|
placeholder="请输入Spring Bean名称"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label required">方法名称</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.methodName"
|
|
|
|
|
|
placeholder="请输入要执行的方法名"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label">方法参数</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.methodParams"
|
|
|
|
|
|
placeholder="请输入方法参数(JSON格式,可选)"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label required">Cron表达式</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.cronExpression"
|
|
|
|
|
|
placeholder="请输入Cron表达式(如:0 0 2 * * ?)"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #append>
|
|
|
|
|
|
<el-button @click="validateCron">验证</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
<span class="form-tip">
|
|
|
|
|
|
格式:秒 分 时 日 月 周 年(年可选)。
|
|
|
|
|
|
示例:0 0 2 * * ? 表示每天凌晨2点执行
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label">是否允许并发</span>
|
|
|
|
|
|
<el-radio-group v-model="formData.concurrent">
|
|
|
|
|
|
<el-radio :label="1">允许</el-radio>
|
|
|
|
|
|
<el-radio :label="0">禁止</el-radio>
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label">错过执行策略</span>
|
|
|
|
|
|
<el-select v-model="formData.misfirePolicy" placeholder="请选择策略">
|
|
|
|
|
|
<el-option label="立即执行" :value="1" />
|
|
|
|
|
|
<el-option label="执行一次" :value="2" />
|
|
|
|
|
|
<el-option label="放弃执行" :value="3" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-item">
|
|
|
|
|
|
<span class="form-label">任务描述</span>
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="formData.description"
|
|
|
|
|
|
type="textarea"
|
|
|
|
|
|
:rows="3"
|
|
|
|
|
|
placeholder="请输入任务描述"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
@click="handleSubmit"
|
|
|
|
|
|
:loading="submitting"
|
|
|
|
|
|
>
|
|
|
|
|
|
确定
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</AdminLayout>
|
2025-10-25 18:47:00 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, reactive, onMounted } from 'vue';
|
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
|
import { Plus, Search, Refresh } from '@element-plus/icons-vue';
|
|
|
|
|
|
import { crontabApi } from '@/apis/crontab';
|
|
|
|
|
|
import type { CrontabTask, PageParam } from '@/types';
|
2025-10-28 19:04:35 +08:00
|
|
|
|
import { AdminLayout } from '@/views/admin';
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
|
name: 'TaskManagementView'
|
|
|
|
|
|
});
|
2025-10-25 18:47:00 +08:00
|
|
|
|
// 数据状态
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const submitting = ref(false);
|
|
|
|
|
|
const taskList = ref<CrontabTask[]>([]);
|
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索表单
|
|
|
|
|
|
const searchForm = reactive({
|
|
|
|
|
|
taskName: '',
|
|
|
|
|
|
taskGroup: '',
|
|
|
|
|
|
status: undefined as number | undefined
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 分页参数
|
|
|
|
|
|
const pageParam = reactive<PageParam>({
|
2025-10-27 13:42:34 +08:00
|
|
|
|
pageNumber: 1,
|
|
|
|
|
|
pageSize: 20
|
2025-10-25 18:47:00 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 对话框状态
|
|
|
|
|
|
const dialogVisible = ref(false);
|
|
|
|
|
|
const isEdit = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = reactive<Partial<CrontabTask>>({
|
|
|
|
|
|
taskName: '',
|
2025-11-12 16:10:34 +08:00
|
|
|
|
taskGroup: '',
|
2025-10-25 18:47:00 +08:00
|
|
|
|
beanName: '',
|
|
|
|
|
|
methodName: '',
|
|
|
|
|
|
methodParams: '',
|
|
|
|
|
|
cronExpression: '',
|
|
|
|
|
|
status: 0,
|
|
|
|
|
|
concurrent: 0,
|
|
|
|
|
|
misfirePolicy: 2,
|
|
|
|
|
|
description: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 加载任务列表
|
|
|
|
|
|
const loadTaskList = async () => {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const filter: Partial<CrontabTask> = {};
|
|
|
|
|
|
if (searchForm.taskName) filter.taskName = searchForm.taskName;
|
|
|
|
|
|
if (searchForm.taskGroup) filter.taskGroup = searchForm.taskGroup;
|
|
|
|
|
|
if (searchForm.status !== undefined) filter.status = searchForm.status;
|
|
|
|
|
|
|
|
|
|
|
|
const result = await crontabApi.getTaskPage(filter, pageParam);
|
2025-11-12 16:10:34 +08:00
|
|
|
|
if (result.success) {
|
|
|
|
|
|
// 根据后端返回结构处理数据
|
|
|
|
|
|
if (result.pageDomain) {
|
|
|
|
|
|
taskList.value = result.pageDomain.dataList || [];
|
|
|
|
|
|
total.value = result.pageDomain.pageParam?.totalElements || 0;
|
|
|
|
|
|
} else if (result.dataList) {
|
|
|
|
|
|
taskList.value = result.dataList;
|
|
|
|
|
|
total.value = result.pageParam?.totalElements || 0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
taskList.value = [];
|
|
|
|
|
|
total.value = 0;
|
|
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || '加载任务列表失败');
|
|
|
|
|
|
taskList.value = [];
|
|
|
|
|
|
total.value = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载任务列表失败:', error);
|
|
|
|
|
|
ElMessage.error('加载任务列表失败');
|
|
|
|
|
|
taskList.value = [];
|
|
|
|
|
|
total.value = 0;
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handleSearch() {
|
2025-10-27 13:42:34 +08:00
|
|
|
|
pageParam.pageNumber = 1;
|
2025-10-25 18:47:00 +08:00
|
|
|
|
loadTaskList();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 重置搜索
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handleReset() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
searchForm.taskName = '';
|
|
|
|
|
|
searchForm.taskGroup = '';
|
|
|
|
|
|
searchForm.status = undefined;
|
2025-10-27 13:42:34 +08:00
|
|
|
|
pageParam.pageNumber = 1;
|
2025-10-25 18:47:00 +08:00
|
|
|
|
loadTaskList();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 分页变化
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handlePageChange(page: number) {
|
2025-10-27 13:42:34 +08:00
|
|
|
|
pageParam.pageNumber = page;
|
2025-10-25 18:47:00 +08:00
|
|
|
|
loadTaskList();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handleSizeChange(size: number) {
|
2025-10-27 13:42:34 +08:00
|
|
|
|
pageParam.pageSize = size;
|
|
|
|
|
|
pageParam.pageNumber = 1;
|
2025-10-25 18:47:00 +08:00
|
|
|
|
loadTaskList();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 新增任务
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handleAdd() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
isEdit.value = false;
|
|
|
|
|
|
resetFormData();
|
|
|
|
|
|
dialogVisible.value = true;
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 编辑任务
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function handleEdit(row: CrontabTask) {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
isEdit.value = true;
|
|
|
|
|
|
Object.assign(formData, row);
|
|
|
|
|
|
dialogVisible.value = true;
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 启动任务
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function handleStart(row: CrontabTask) {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const result = await crontabApi.startTask(row.taskId!);
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success('任务已启动');
|
|
|
|
|
|
loadTaskList();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || '启动失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('启动任务失败:', error);
|
|
|
|
|
|
ElMessage.error('启动任务失败');
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 暂停任务
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function handlePause(row: CrontabTask) {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const result = await crontabApi.pauseTask(row.taskId!);
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success('任务已暂停');
|
|
|
|
|
|
loadTaskList();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || '暂停失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('暂停任务失败:', error);
|
|
|
|
|
|
ElMessage.error('暂停任务失败');
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 执行一次
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function handleExecute(row: CrontabTask) {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确定立即执行任务"${row.taskName}"吗?`,
|
|
|
|
|
|
'确认执行',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await crontabApi.executeTaskOnce(row.taskId!);
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success('任务执行成功');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || '执行失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('执行任务失败:', error);
|
|
|
|
|
|
ElMessage.error('执行任务失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 删除任务
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function handleDelete(row: CrontabTask) {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
|
`确定要删除任务"${row.taskName}"吗?`,
|
|
|
|
|
|
'删除确认',
|
|
|
|
|
|
{
|
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await crontabApi.deleteTask(row);
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success('删除成功');
|
|
|
|
|
|
loadTaskList();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || '删除失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
|
console.error('删除任务失败:', error);
|
|
|
|
|
|
ElMessage.error('删除任务失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 验证Cron表达式
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function validateCron() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
if (!formData.cronExpression) {
|
|
|
|
|
|
ElMessage.warning('请输入Cron表达式');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await crontabApi.validateCronExpression(formData.cronExpression);
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success('Cron表达式验证通过');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || 'Cron表达式格式错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('验证Cron表达式失败:', error);
|
|
|
|
|
|
ElMessage.error('验证失败');
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 提交表单
|
2025-10-27 19:05:56 +08:00
|
|
|
|
async function handleSubmit() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
// 表单验证
|
|
|
|
|
|
if (!formData.taskName) {
|
|
|
|
|
|
ElMessage.warning('请输入任务名称');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.taskGroup) {
|
|
|
|
|
|
ElMessage.warning('请输入任务分组');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.beanName) {
|
|
|
|
|
|
ElMessage.warning('请输入Bean名称');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.methodName) {
|
|
|
|
|
|
ElMessage.warning('请输入方法名称');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.cronExpression) {
|
|
|
|
|
|
ElMessage.warning('请输入Cron表达式');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
submitting.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = { ...formData };
|
|
|
|
|
|
let result;
|
|
|
|
|
|
|
|
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
|
result = await crontabApi.updateTask(data as CrontabTask);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result = await crontabApi.createTask(data as CrontabTask);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
ElMessage.success(isEdit.value ? '更新成功' : '创建成功');
|
|
|
|
|
|
dialogVisible.value = false;
|
|
|
|
|
|
loadTaskList();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ElMessage.error(result.message || (isEdit.value ? '更新失败' : '创建失败'));
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('提交失败:', error);
|
|
|
|
|
|
ElMessage.error('提交失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
submitting.value = false;
|
|
|
|
|
|
}
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function resetForm() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
resetFormData();
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 重置表单数据
|
2025-10-27 19:05:56 +08:00
|
|
|
|
function resetFormData() {
|
2025-10-25 18:47:00 +08:00
|
|
|
|
Object.assign(formData, {
|
|
|
|
|
|
taskName: '',
|
2025-11-12 16:10:34 +08:00
|
|
|
|
taskGroup: '',
|
2025-10-25 18:47:00 +08:00
|
|
|
|
beanName: '',
|
|
|
|
|
|
methodName: '',
|
|
|
|
|
|
methodParams: '',
|
|
|
|
|
|
cronExpression: '',
|
|
|
|
|
|
status: 0,
|
|
|
|
|
|
concurrent: 0,
|
|
|
|
|
|
misfirePolicy: 2,
|
|
|
|
|
|
description: ''
|
|
|
|
|
|
});
|
2025-10-27 19:05:56 +08:00
|
|
|
|
}
|
2025-10-25 18:47:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadTaskList();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.task-management {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #141F38;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
background-color: #f8f9fa;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
|
|
|
|
|
|
.search-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.search-label {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-content {
|
|
|
|
|
|
.form-item {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.form-label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
|
|
|
|
|
|
&.required::before {
|
|
|
|
|
|
content: '*';
|
|
|
|
|
|
color: #f56c6c;
|
|
|
|
|
|
margin-right: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-tip {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|