update
This commit is contained in:
parent
4c03b09278
commit
04758174f0
@ -121,6 +121,9 @@
|
||||
<a-menu-item key="system-time-periods">
|
||||
<router-link to="/system/time-periods">时段配置</router-link>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="system-tasks">
|
||||
<router-link to="/system/tasks">任务管理</router-link>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
@ -384,6 +387,11 @@ export default defineComponent({
|
||||
key: 'system-time-periods',
|
||||
title: '时段配置',
|
||||
path: '/system/time-periods'
|
||||
},
|
||||
{
|
||||
key: 'system-tasks',
|
||||
title: '任务管理',
|
||||
path: '/system/tasks'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -128,6 +128,11 @@ const routes = [
|
||||
path: 'time-periods',
|
||||
component: () => import('@/views/system/TimePeriodList.vue'),
|
||||
meta: { title: '时段配置' }
|
||||
},
|
||||
{
|
||||
path: 'tasks',
|
||||
component: () => import('@/views/system/TaskManager.vue'),
|
||||
meta: { title: '任务管理' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
323
src/views/system/TaskManager.vue
Normal file
323
src/views/system/TaskManager.vue
Normal file
@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<page-container>
|
||||
<div class="task-manager">
|
||||
<div class="system-status-card">
|
||||
<a-card title="系统状态" :bordered="false">
|
||||
<a-descriptions :column="3">
|
||||
<a-descriptions-item label="运行状态">
|
||||
<a-tag :color="systemStatus.running ? 'success' : 'error'">
|
||||
{{ systemStatus.running ? '运行中' : '已停止' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="任务数量">
|
||||
{{ systemStatus.job_count || 0 }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="时区">
|
||||
{{ systemStatus.timezone || 'Asia/Shanghai' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
<div class="table-header">
|
||||
<h1>定时任务管理</h1>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="refreshData">
|
||||
<template #icon><reload-outlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="tableData"
|
||||
:pagination="pagination"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="record.active ? 'success' : 'default'">
|
||||
{{ record.active ? '运行中' : '已暂停' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'next_run_time'">
|
||||
{{ formatDateTime(record.next_run_time) }}
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button
|
||||
v-if="record.active"
|
||||
type="primary"
|
||||
danger
|
||||
size="small"
|
||||
:loading="record.loading"
|
||||
@click="pauseJob(record)"
|
||||
>
|
||||
暂停
|
||||
</a-button>
|
||||
<a-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="record.loading"
|
||||
@click="resumeJob(record)"
|
||||
>
|
||||
恢复
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</page-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent, ref, onMounted } from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import request from '@/utils/request'
|
||||
import PageContainer from '@/components/PageContainer.vue'
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TaskManager',
|
||||
components: {
|
||||
PageContainer,
|
||||
ReloadOutlined
|
||||
},
|
||||
setup() {
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const systemStatus = ref({
|
||||
running: false,
|
||||
job_count: 0,
|
||||
timezone: 'Asia/Shanghai'
|
||||
})
|
||||
|
||||
const pagination = ref({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 条记录`
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
title: '触发器',
|
||||
dataIndex: 'trigger',
|
||||
key: 'trigger',
|
||||
width: 250,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '下次执行时间',
|
||||
key: 'next_run_time',
|
||||
dataIndex: 'next_run_time',
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
fixed: 'right'
|
||||
}
|
||||
]
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (value) => {
|
||||
if (!value) return '-'
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
// 获取系统状态
|
||||
const fetchSystemStatus = async () => {
|
||||
try {
|
||||
const res = await request.get('/api/scheduler/status')
|
||||
if (res.code === 200) {
|
||||
systemStatus.value = res.data
|
||||
} else {
|
||||
message.error(res.message || '获取系统状态失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取系统状态失败:', error)
|
||||
message.error('获取系统状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取任务列表
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await request.get('/api/scheduler/jobs')
|
||||
if (res.code === 200) {
|
||||
// 为每个任务添加loading状态
|
||||
tableData.value = (res.data || []).map(item => ({
|
||||
...item,
|
||||
loading: false
|
||||
}))
|
||||
pagination.value.total = tableData.value.length
|
||||
} else {
|
||||
message.error(res.message || '获取数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取任务列表失败:', error)
|
||||
message.error('获取数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = async () => {
|
||||
await Promise.all([fetchSystemStatus(), fetchData()])
|
||||
message.success('数据已刷新')
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleTableChange = (pag) => {
|
||||
pagination.value.current = pag.current
|
||||
pagination.value.pageSize = pag.pageSize
|
||||
fetchData()
|
||||
}
|
||||
|
||||
// 暂停任务
|
||||
const pauseJob = async (record) => {
|
||||
try {
|
||||
// 设置当前任务的loading状态
|
||||
const index = tableData.value.findIndex(item => item.id === record.id)
|
||||
if (index !== -1) {
|
||||
tableData.value[index].loading = true
|
||||
}
|
||||
|
||||
const res = await request.post(`/api/scheduler/jobs/${record.id}/pause`)
|
||||
if (res.code === 200) {
|
||||
message.success('任务已暂停')
|
||||
// 更新任务状态
|
||||
if (index !== -1) {
|
||||
tableData.value[index].active = false
|
||||
}
|
||||
// 刷新系统状态
|
||||
fetchSystemStatus()
|
||||
} else {
|
||||
message.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('暂停任务失败:', error)
|
||||
message.error('操作失败')
|
||||
} finally {
|
||||
// 清除loading状态
|
||||
const index = tableData.value.findIndex(item => item.id === record.id)
|
||||
if (index !== -1) {
|
||||
tableData.value[index].loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复任务
|
||||
const resumeJob = async (record) => {
|
||||
try {
|
||||
// 设置当前任务的loading状态
|
||||
const index = tableData.value.findIndex(item => item.id === record.id)
|
||||
if (index !== -1) {
|
||||
tableData.value[index].loading = true
|
||||
}
|
||||
|
||||
const res = await request.post(`/api/scheduler/jobs/${record.id}/resume`)
|
||||
if (res.code === 200) {
|
||||
message.success('任务已恢复')
|
||||
// 更新任务状态
|
||||
if (index !== -1) {
|
||||
tableData.value[index].active = true
|
||||
}
|
||||
// 刷新系统状态
|
||||
fetchSystemStatus()
|
||||
} else {
|
||||
message.error(res.message || '操作失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('恢复任务失败:', error)
|
||||
message.error('操作失败')
|
||||
} finally {
|
||||
// 清除loading状态
|
||||
const index = tableData.value.findIndex(item => item.id === record.id)
|
||||
if (index !== -1) {
|
||||
tableData.value[index].loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refreshData()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
tableData,
|
||||
columns,
|
||||
pagination,
|
||||
systemStatus,
|
||||
formatDateTime,
|
||||
handleTableChange,
|
||||
pauseJob,
|
||||
resumeJob,
|
||||
refreshData
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-manager {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.system-status-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.table-header h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(.ant-table-content) {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
:deep(.ant-descriptions-item-label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user