142 lines
5.6 KiB
Vue
142 lines
5.6 KiB
Vue
<template>
|
|
<div class="space-y-4">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between bg-white p-4 rounded-lg shadow-sm border border-gray-200">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-gray-800">定时任务管理</h3>
|
|
<p class="text-sm text-gray-500">查看和控制系统定时任务的启用状态</p>
|
|
</div>
|
|
<button
|
|
@click="loadTasks"
|
|
:disabled="loading"
|
|
class="px-3 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded hover:bg-gray-200 disabled:opacity-50 flex items-center gap-1"
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4" :class="{'animate-spin': loading}">
|
|
<path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0v2.433l-.31-.31a7 7 0 00-11.712 3.138.75.75 0 001.449.39 5.5 5.5 0 019.201-2.466l.312.312h-2.433a.75.75 0 000 1.5h4.185a.75.75 0 00.53-.219z" clip-rule="evenodd" />
|
|
</svg>
|
|
刷新
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Messages -->
|
|
<div v-if="message" class="text-sm text-green-600 bg-green-50 px-4 py-3 rounded border border-green-100">{{ message }}</div>
|
|
<div v-if="error" class="text-sm text-red-600 bg-red-50 px-4 py-3 rounded border border-red-100">{{ error }}</div>
|
|
|
|
<!-- Tasks List -->
|
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">任务名称</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">命令</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">执行频率</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">状态</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<tr v-for="task in tasks" :key="task.name" class="hover:bg-gray-50">
|
|
<td class="px-6 py-4">
|
|
<div class="font-medium text-gray-900">{{ task.description }}</div>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded text-gray-700">{{ task.command }}</code>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<div class="text-sm text-gray-900">{{ task.frequency }}</div>
|
|
<div class="text-xs text-gray-400 font-mono">{{ task.cron }}</div>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span
|
|
:class="task.enabled ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'"
|
|
class="px-2 py-1 text-xs font-medium rounded-full"
|
|
>
|
|
{{ task.enabled ? '已启用' : '已禁用' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-right">
|
|
<button
|
|
@click="toggleTask(task)"
|
|
:disabled="task._toggling"
|
|
:class="task.enabled ? 'text-red-600 hover:text-red-800' : 'text-green-600 hover:text-green-800'"
|
|
class="text-sm font-medium disabled:opacity-50"
|
|
>
|
|
{{ task._toggling ? '处理中...' : (task.enabled ? '禁用' : '启用') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="tasks.length === 0 && !loading">
|
|
<td colspan="5" class="px-6 py-8 text-center text-gray-400">
|
|
暂无定时任务
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="text-center py-8 text-gray-400">
|
|
加载中...
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'ScheduledTasks',
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
tasks: [],
|
|
message: '',
|
|
error: ''
|
|
};
|
|
},
|
|
async mounted() {
|
|
await this.loadTasks();
|
|
},
|
|
methods: {
|
|
async loadTasks() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
try {
|
|
const res = await fetch('/api/admin/scheduled-tasks');
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
this.tasks = (data.data.tasks || []).map(t => ({ ...t, _toggling: false }));
|
|
} else {
|
|
this.error = data.message || '加载失败';
|
|
}
|
|
} catch (e) {
|
|
this.error = e.message;
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async toggleTask(task) {
|
|
task._toggling = true;
|
|
this.error = '';
|
|
this.message = '';
|
|
try {
|
|
const res = await fetch(`/api/admin/scheduled-tasks/${task.name}/toggle`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ enabled: !task.enabled })
|
|
});
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
task.enabled = data.data.enabled;
|
|
this.message = `任务 ${task.name} 已${task.enabled ? '启用' : '禁用'}`;
|
|
} else {
|
|
this.error = data.message || '操作失败';
|
|
}
|
|
} catch (e) {
|
|
this.error = e.message;
|
|
} finally {
|
|
task._toggling = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|