Files
schoolNews/schoolNewsWeb/src/views/admin/manage/crontab/TaskManagementView.vue
2025-11-12 16:10:34 +08:00

634 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<AdminLayout title="定时任务" subtitle="定时任务管理">
<div class="task-management">
<div class="header">
<h2>定时任务管理</h2>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增任务
</el-button>
</div>
<!-- 搜索筛选区域 -->
<div class="search-bar">
<div class="search-item">
<span class="search-label">任务名称</span>
<el-input
v-model="searchForm.taskName"
placeholder="请输入任务名称"
clearable
@keyup.enter="handleSearch"
style="width: 200px"
/>
</div>
<div class="search-item">
<span class="search-label">任务分组</span>
<el-input
v-model="searchForm.taskGroup"
placeholder="请输入任务分组"
clearable
@keyup.enter="handleSearch"
style="width: 200px"
/>
</div>
<div class="search-item">
<span class="search-label">状态</span>
<el-select
v-model="searchForm.status"
placeholder="请选择状态"
clearable
style="width: 150px"
>
<el-option label="运行中" :value="1" />
<el-option label="已暂停" :value="0" />
</el-select>
</div>
<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>
</div>
</div>
<!-- 任务列表 -->
<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>
<!-- 分页 -->
<div class="pagination-container" v-if="total > 0">
<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>
</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';
import { AdminLayout } from '@/views/admin';
defineOptions({
name: 'TaskManagementView'
});
// 数据状态
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>({
pageNumber: 1,
pageSize: 20
});
// 对话框状态
const dialogVisible = ref(false);
const isEdit = ref(false);
// 表单数据
const formData = reactive<Partial<CrontabTask>>({
taskName: '',
taskGroup: '',
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);
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;
}
} 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;
}
};
// 搜索
function handleSearch() {
pageParam.pageNumber = 1;
loadTaskList();
}
// 重置搜索
function handleReset() {
searchForm.taskName = '';
searchForm.taskGroup = '';
searchForm.status = undefined;
pageParam.pageNumber = 1;
loadTaskList();
}
// 分页变化
function handlePageChange(page: number) {
pageParam.pageNumber = page;
loadTaskList();
}
function handleSizeChange(size: number) {
pageParam.pageSize = size;
pageParam.pageNumber = 1;
loadTaskList();
}
// 新增任务
function handleAdd() {
isEdit.value = false;
resetFormData();
dialogVisible.value = true;
}
// 编辑任务
function handleEdit(row: CrontabTask) {
isEdit.value = true;
Object.assign(formData, row);
dialogVisible.value = true;
}
// 启动任务
async function handleStart(row: CrontabTask) {
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('启动任务失败');
}
}
// 暂停任务
async function handlePause(row: CrontabTask) {
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('暂停任务失败');
}
}
// 执行一次
async function handleExecute(row: CrontabTask) {
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('执行任务失败');
}
}
}
// 删除任务
async function handleDelete(row: CrontabTask) {
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('删除任务失败');
}
}
}
// 验证Cron表达式
async function validateCron() {
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('验证失败');
}
}
// 提交表单
async function handleSubmit() {
// 表单验证
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;
}
}
// 重置表单
function resetForm() {
resetFormData();
}
// 重置表单数据
function resetFormData() {
Object.assign(formData, {
taskName: '',
taskGroup: '',
beanName: '',
methodName: '',
methodParams: '',
cronExpression: '',
status: 0,
concurrent: 0,
misfirePolicy: 2,
description: ''
});
}
// 初始化
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>