定时任务增加系统定时任务
This commit is contained in:
@@ -30,8 +30,8 @@ export const crontabApi = {
|
||||
* 获取可创建的定时任务列表(从数据库获取任务元数据)
|
||||
* @returns Promise<ResultDomain<TaskMeta>>
|
||||
*/
|
||||
async getEnabledCrontabList(): Promise<ResultDomain<TaskMeta>> {
|
||||
const response = await api.get<TaskMeta>(`${this.baseUrl}/getEnabledCrontabList`);
|
||||
async getEnabledCrontabList(param: string): Promise<ResultDomain<TaskMeta>> {
|
||||
const response = await api.get<TaskMeta>(`${this.baseUrl}/getEnabledCrontabList`, { param });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="total > 0">
|
||||
<div class="pagination-container" v-if="pageParam.totalElements! > 0">
|
||||
<el-pagination
|
||||
v-model:current-page="pageParam.pageNumber"
|
||||
v-model:page-size="pageParam.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="total"
|
||||
:total="pageParam.totalElements"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
@@ -413,7 +413,6 @@ defineOptions({
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const crawlerList = ref<CrontabTask[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
// 爬虫元数据
|
||||
const taskMetaList = ref<TaskMeta[]>([]);
|
||||
@@ -440,7 +439,9 @@ const searchForm = reactive({
|
||||
// 分页参数
|
||||
const pageParam = reactive<PageParam>({
|
||||
pageNumber: 1,
|
||||
pageSize: 10
|
||||
pageSize: 10,
|
||||
totalElements: 0,
|
||||
totalPages: 0
|
||||
});
|
||||
|
||||
// 对话框状态
|
||||
@@ -705,7 +706,7 @@ function resetUserSelector() {
|
||||
// 加载爬虫模板(从数据库加载TaskMeta,转换为CrontabItem结构)
|
||||
async function loadCrawlerTemplates() {
|
||||
try {
|
||||
const result = await crontabApi.getEnabledCrontabList();
|
||||
const result = await crontabApi.getEnabledCrontabList("新闻爬取");
|
||||
if (result.success && result.dataList) {
|
||||
taskMetaList.value = result.dataList;
|
||||
|
||||
@@ -746,7 +747,7 @@ async function loadCrawlerList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const filter: Partial<CrontabTask> = {
|
||||
taskGroup: ''
|
||||
taskGroup: '新闻爬取'
|
||||
};
|
||||
if (searchForm.taskName) filter.taskName = searchForm.taskName;
|
||||
if (searchForm.status !== undefined) filter.status = searchForm.status;
|
||||
@@ -754,25 +755,17 @@ async function loadCrawlerList() {
|
||||
const result = await crontabApi.getTaskPage(filter, pageParam);
|
||||
if (result.success) {
|
||||
// 根据后端返回结构处理数据
|
||||
if (result.pageDomain) {
|
||||
crawlerList.value = result.pageDomain.dataList || [];
|
||||
total.value = result.pageDomain.pageParam?.totalElements || 0;
|
||||
} else if (result.dataList) {
|
||||
crawlerList.value = result.dataList;
|
||||
total.value = result.pageParam?.totalElements || 0;
|
||||
} else {
|
||||
crawlerList.value = [];
|
||||
total.value = 0;
|
||||
crawlerList.value = result.pageDomain?.dataList || [];
|
||||
if (result.pageDomain?.pageParam) {
|
||||
Object.assign(pageParam, result.pageDomain.pageParam);
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(result.message || '加载爬虫列表失败');
|
||||
crawlerList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('加载爬虫列表失败');
|
||||
crawlerList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -1210,14 +1203,38 @@ onMounted(() => {
|
||||
.crawler-list {
|
||||
min-height: 400px;
|
||||
|
||||
.crawler-card {
|
||||
// 让同一行的列等高
|
||||
:deep(.el-row) {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
// el-col 作为 flex 容器,使卡片能撑满高度
|
||||
:deep(.el-col) {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.crawler-card {
|
||||
transition: all 0.3s;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.el-card__footer) {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
714
schoolNewsWeb/src/views/admin/manage/crontab/SystemTaskView.vue
Normal file
714
schoolNewsWeb/src/views/admin/manage/crontab/SystemTaskView.vue
Normal file
@@ -0,0 +1,714 @@
|
||||
<template>
|
||||
<AdminLayout title="系统定时任务" subtitle="系统定时任务配置">
|
||||
<div class="news-crawler">
|
||||
<div class="header">
|
||||
<h2>系统定时任务配置</h2>
|
||||
<el-button type="primary" @click="handleAddTask">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增任务
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 说明卡片 -->
|
||||
<el-alert
|
||||
title="系统定时任务说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<p>系统定时任务用于执行平台内部的各类系统作业,例如热门资源推荐等。</p>
|
||||
<p>配置完成后,系统会按照设定的 Cron 表达式定时执行任务。</p>
|
||||
</el-alert>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-item">
|
||||
<span class="search-label">任务名称</span>
|
||||
<el-input
|
||||
v-model="searchForm.taskName"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
@keyup.enter="loadTaskList"
|
||||
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="loadTaskList">
|
||||
<el-icon><Search /></el-icon>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<el-empty
|
||||
v-if="!loading && taskList.length === 0"
|
||||
description="暂无系统任务"
|
||||
style="margin-top: 40px"
|
||||
/>
|
||||
|
||||
<!-- 系统任务列表 -->
|
||||
<div v-else class="crawler-list">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8" v-for="task in taskList" :key="task.taskId">
|
||||
<el-card class="crawler-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<el-icon class="title-icon"><Timer /></el-icon>
|
||||
<span>{{ task.taskName }}</span>
|
||||
</div>
|
||||
<el-tag
|
||||
:type="task.status === 1 ? 'success' : 'info'"
|
||||
size="small"
|
||||
>
|
||||
{{ task.status === 1 ? '运行中' : '已暂停' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="card-content">
|
||||
<div class="info-item">
|
||||
<span class="info-label">定时任务类型:</span>
|
||||
<span class="info-value">{{ task.taskGroup }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">定时任务:</span>
|
||||
<span class="info-value">{{ task.metaName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">执行周期:</span>
|
||||
<span class="info-value">{{ task.cronExpression }}</span>
|
||||
</div>
|
||||
<div class="info-item" v-if="task.description">
|
||||
<span class="info-label">描述:</span>
|
||||
<span class="info-value">{{ task.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="card-actions">
|
||||
<el-button
|
||||
v-if="task.status === 0"
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleToggleStatus(task)"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>
|
||||
启动
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleToggleStatus(task)"
|
||||
>
|
||||
<el-icon><VideoPause /></el-icon>
|
||||
暂停
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleExecuteNow(task)"
|
||||
>
|
||||
<el-icon><Promotion /></el-icon>
|
||||
执行一次
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEdit(task)"
|
||||
>
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="handleDelete(task)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="pageParam.totalElements! > 0">
|
||||
<el-pagination
|
||||
v-model:current-page="pageParam.pageNumber"
|
||||
v-model:page-size="pageParam.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="pageParam.totalElements"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="loadTaskList"
|
||||
@current-change="loadTaskList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 新增/编辑任务对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="600px"
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="taskFormRef"
|
||||
:model="taskForm"
|
||||
:rules="taskFormRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="任务类型" prop="metaId">
|
||||
<el-select
|
||||
v-model="taskForm.metaId"
|
||||
placeholder="请选择任务类型"
|
||||
style="width: 100%"
|
||||
@change="handleMetaChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="meta in taskMetaList"
|
||||
:key="meta.metaId"
|
||||
:label="meta.name"
|
||||
:value="meta.metaId"
|
||||
>
|
||||
<span>{{ meta.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 12px">
|
||||
{{ meta.description }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="任务名称" prop="taskName">
|
||||
<el-input
|
||||
v-model="taskForm.taskName"
|
||||
placeholder="请输入任务名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="任务描述" prop="description">
|
||||
<el-input
|
||||
v-model="taskForm.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入任务描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Cron表达式" prop="cronExpression">
|
||||
<el-input
|
||||
v-model="taskForm.cronExpression"
|
||||
placeholder="例如:0 0 1 * * ?(每天凌晨1点)"
|
||||
>
|
||||
<template #append>
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="300"
|
||||
trigger="click"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button>示例</el-button>
|
||||
</template>
|
||||
<div class="cron-examples">
|
||||
<div>0 0 1 * * ? - 每天凌晨1点</div>
|
||||
<div>0 0 */2 * * ? - 每2小时</div>
|
||||
<div>0 */30 * * * ? - 每30分钟</div>
|
||||
<div>0 0 0 * * ? - 每天零点</div>
|
||||
<div>0 0 12 * * ? - 每天中午12点</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="任务参数" prop="methodParams">
|
||||
<el-input
|
||||
v-model="taskForm.methodParams"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入JSON格式的参数,例如:{}"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="taskForm.status">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
||||
import {
|
||||
Plus,
|
||||
Search,
|
||||
Refresh,
|
||||
Timer,
|
||||
VideoPlay,
|
||||
VideoPause,
|
||||
Promotion,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue';
|
||||
import { crontabApi } from '@/apis/crontab';
|
||||
import AdminLayout from '@/views/admin/AdminLayout.vue';
|
||||
import type { CrontabTask, TaskMeta, PageParam, CreateTaskRequest } from '@/types';
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
taskName: '',
|
||||
status: undefined as number | undefined
|
||||
});
|
||||
|
||||
// 分页参数
|
||||
const pageParam = reactive<PageParam>({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalPages: 0,
|
||||
totalElements: 0
|
||||
});
|
||||
|
||||
// 任务列表
|
||||
const taskList = ref<CrontabTask[]>([]);
|
||||
const taskMetaList = ref<TaskMeta[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
// 对话框
|
||||
const dialogVisible = ref(false);
|
||||
const dialogTitle = ref('新增任务');
|
||||
const isEdit = ref(false);
|
||||
const taskFormRef = ref<FormInstance>();
|
||||
|
||||
// 任务表单
|
||||
const taskForm = reactive<Partial<CrontabTask>>({
|
||||
taskId: '',
|
||||
metaId: '',
|
||||
taskName: '',
|
||||
description: '',
|
||||
cronExpression: '0 0 1 * * ?',
|
||||
methodParams: '{}',
|
||||
status: 1
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const taskFormRules: FormRules = {
|
||||
metaId: [{ required: true, message: '请选择任务类型', trigger: 'change' }],
|
||||
taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
|
||||
cronExpression: [{ required: true, message: '请输入Cron表达式', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
// 加载任务元数据列表
|
||||
async function loadTaskMetaList() {
|
||||
try {
|
||||
const result = await crontabApi.getEnabledCrontabList('系统内部任务');
|
||||
if (result.success && result.dataList) {
|
||||
taskMetaList.value = result.dataList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载任务元数据失败:', error);
|
||||
ElMessage.error('加载任务类型失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 加载任务列表
|
||||
async function loadTaskList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const filter: Partial<CrontabTask> = {
|
||||
taskGroup: '系统内部任务'
|
||||
};
|
||||
if (searchForm.taskName) filter.taskName = searchForm.taskName;
|
||||
if (searchForm.status !== undefined) filter.status = searchForm.status;
|
||||
|
||||
const result = await crontabApi.getTaskPage(filter, pageParam);
|
||||
|
||||
if (result.success) {
|
||||
taskList.value = result.pageDomain?.dataList || [];
|
||||
if (result.pageDomain?.pageParam) {
|
||||
Object.assign(pageParam, result.pageDomain.pageParam);
|
||||
}
|
||||
} else {
|
||||
taskList.value = [];
|
||||
pageParam.totalElements = pageParam.totalElements;
|
||||
pageParam.totalPages = pageParam.totalPages;
|
||||
ElMessage.error(result.message || '加载任务列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载任务列表失败:', error);
|
||||
ElMessage.error('加载任务列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
function handleReset() {
|
||||
searchForm.taskName = '';
|
||||
searchForm.status = undefined;
|
||||
pageParam.pageNumber = 1;
|
||||
loadTaskList();
|
||||
}
|
||||
|
||||
// 新增任务
|
||||
function handleAddTask() {
|
||||
isEdit.value = false;
|
||||
dialogTitle.value = '新增系统任务';
|
||||
Object.assign(taskForm, {
|
||||
taskId: '',
|
||||
metaId: '',
|
||||
taskName: '',
|
||||
description: '',
|
||||
cronExpression: '0 0 1 * * ?',
|
||||
methodParams: '{}',
|
||||
status: 1
|
||||
});
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 编辑任务
|
||||
function handleEdit(task: CrontabTask) {
|
||||
isEdit.value = true;
|
||||
dialogTitle.value = '编辑系统任务';
|
||||
Object.assign(taskForm, {
|
||||
id: task.id,
|
||||
taskId: task.taskId,
|
||||
metaId: task.metaId,
|
||||
taskName: task.taskName,
|
||||
description: task.description,
|
||||
cronExpression: task.cronExpression,
|
||||
methodParams: task.methodParams || '{}',
|
||||
status: task.status
|
||||
});
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
// 元数据变更
|
||||
function handleMetaChange(metaId: string) {
|
||||
const meta = taskMetaList.value.find(m => m.metaId === metaId);
|
||||
if (meta) {
|
||||
taskForm.taskName = meta.name;
|
||||
taskForm.description = meta.description;
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
async function handleSubmit() {
|
||||
if (!taskFormRef.value) return;
|
||||
|
||||
await taskFormRef.value.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
|
||||
try {
|
||||
// 验证JSON格式
|
||||
try {
|
||||
JSON.parse(taskForm.methodParams || '{}');
|
||||
} catch {
|
||||
ElMessage.error('任务参数必须是有效的JSON格式');
|
||||
return;
|
||||
}
|
||||
|
||||
const meta = taskMetaList.value.find(m => m.metaId === taskForm.metaId);
|
||||
if (!meta) {
|
||||
ElMessage.error('未找到对应的任务类型');
|
||||
return;
|
||||
}
|
||||
|
||||
const taskData: CrontabTask = {
|
||||
...taskForm,
|
||||
beanName: meta.beanName,
|
||||
methodName: meta.methodName || 'execute',
|
||||
methodParams: taskForm.methodParams || '{}'
|
||||
} as CrontabTask;
|
||||
|
||||
const requestData: CreateTaskRequest = {
|
||||
task: taskData,
|
||||
metaId: taskForm.metaId!
|
||||
};
|
||||
|
||||
let result;
|
||||
if (isEdit.value) {
|
||||
result = await crontabApi.updateTask(requestData);
|
||||
} else {
|
||||
result = await crontabApi.createTask(requestData);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功');
|
||||
dialogVisible.value = false;
|
||||
loadTaskList();
|
||||
} else {
|
||||
ElMessage.error(result.message || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
function handleDialogClose() {
|
||||
taskFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
async function handleToggleStatus(task: CrontabTask) {
|
||||
try {
|
||||
let result;
|
||||
if (task.status === 1) {
|
||||
result = await crontabApi.pauseTask(task.taskId!);
|
||||
} else {
|
||||
result = await crontabApi.startTask(task.taskId!);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
ElMessage.success(task.status === 1 ? '已禁用' : '已启用');
|
||||
loadTaskList();
|
||||
} else {
|
||||
ElMessage.error(result.message || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换状态失败:', error);
|
||||
ElMessage.error('操作失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 立即执行
|
||||
async function handleExecuteNow(task: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要立即执行任务"${task.taskName}"吗?`,
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
const result = await crontabApi.executeTaskOnce(task.taskId!);
|
||||
if (result.success) {
|
||||
ElMessage.success('任务已提交执行');
|
||||
} else {
|
||||
ElMessage.error(result.message || '执行失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('执行任务失败:', error);
|
||||
ElMessage.error('执行失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除任务
|
||||
async function handleDelete(task: CrontabTask) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除任务"${task.taskName}"吗?此操作不可恢复。`,
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
const result = await crontabApi.deleteTask(task);
|
||||
if (result.success) {
|
||||
ElMessage.success('删除成功');
|
||||
loadTaskList();
|
||||
} else {
|
||||
ElMessage.error(result.message || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除任务失败:', error);
|
||||
ElMessage.error('删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
loadTaskMetaList();
|
||||
loadTaskList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.news-crawler {
|
||||
padding: 20px;
|
||||
background-color: #ffffff;
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.crawler-list {
|
||||
min-height: 400px;
|
||||
|
||||
// 让同一行的列等高
|
||||
:deep(.el-row) {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
// el-col 作为 flex 容器,使卡片能撑满高度
|
||||
:deep(.el-col) {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.crawler-card {
|
||||
transition: all 0.3s;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.el-card__footer) {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
|
||||
.title-icon {
|
||||
font-size: 20px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
|
||||
.info-label {
|
||||
min-width: 80px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
flex: 1;
|
||||
color: #606266;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.cron-examples {
|
||||
div {
|
||||
padding: 6px 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user