#feature: add project&cronjob management

This commit is contained in:
2026-01-16 12:14:43 +08:00
parent bbe68839e3
commit 381d5e6e49
19 changed files with 2157 additions and 84 deletions

View File

@@ -0,0 +1,141 @@
<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>