feat: admin.html task management tab (TaskConfig + TaskLog timeline)
- Add 任务管理 tab button - Show all 7 task modules table with enable/disable switch, schedule, params - Edit dialog for schedule/params editing - Click module to show el-timeline of recent 30 run logs - View result data dialog - Supports toggleModule, saveTaskConfig, loadTaskLogs
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
<el-button size="default" :type="activeTab === 'searchrankings' ? 'primary' : ''" @click="switchTab('searchrankings')">搜索排名</el-button>
|
<el-button size="default" :type="activeTab === 'searchrankings' ? 'primary' : ''" @click="switchTab('searchrankings')">搜索排名</el-button>
|
||||||
<el-button size="default" :type="activeTab === 'geo' ? 'primary' : ''" @click="switchTab('geo')">GEO 就绪度</el-button>
|
<el-button size="default" :type="activeTab === 'geo' ? 'primary' : ''" @click="switchTab('geo')">GEO 就绪度</el-button>
|
||||||
<el-button size="default" :type="activeTab === 'configitems' ? 'primary' : ''" @click="switchTab('configitems')">配置管理</el-button>
|
<el-button size="default" :type="activeTab === 'configitems' ? 'primary' : ''" @click="switchTab('configitems')">配置管理</el-button>
|
||||||
|
<el-button size="default" :type="activeTab === 'taskmanagement' ? 'primary' : ''" @click="switchTab('taskmanagement')">任务管理</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -816,6 +817,122 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeTab === 'taskmanagement'">
|
||||||
|
<div class="toolbar">
|
||||||
|
<span style="font-size:13px;color:#909399;">定时任务配置与运行记录 — 共 {{ taskConfigs.length }} 个模块</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="tcLoading" class="card-loading">加载中...</div>
|
||||||
|
<template v-else-if="taskConfigs.length === 0">
|
||||||
|
<div class="empty-state">
|
||||||
|
<el-icon style="font-size:48px;color:#c0c4cc;"><IconTimer /></el-icon>
|
||||||
|
<div class="empty-text">暂无任务配置</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-table :data="taskConfigs" border stripe style="width:100%" size="small"
|
||||||
|
@row-click="selectModule">
|
||||||
|
<el-table-column type="index" label="#" width="50"></el-table-column>
|
||||||
|
<el-table-column prop="module_id" label="模块ID" min-width="160"></el-table-column>
|
||||||
|
<el-table-column prop="enabled" label="启用" width="70">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-switch :model-value="row.enabled" @change="(val) => toggleModule(row, val)" @click.stop
|
||||||
|
style="--el-switch-on-color:#67c23a;"></el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="定时" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<code style="font-size:12px;background:#f4f4f5;padding:2px 6px;border-radius:4px;">{{ row.schedule || '-' }}</code>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="参数" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span style="font-size:12px;color:#606266;word-break:break-all;">
|
||||||
|
{{ row.params ? JSON.stringify(row.params).slice(0, 60) + (JSON.stringify(row.params).length > 60 ? '...' : '') : '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="上次运行" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.last_log" style="font-size:12px;">
|
||||||
|
<el-tag :type="row.last_log.status === 'success' ? 'success' : row.last_log.status === 'failed' ? 'danger' : row.last_log.status === 'running' ? 'warning' : 'info'" size="small" style="margin-right:4px;">
|
||||||
|
{{ row.last_log.status || '?' }}
|
||||||
|
</el-tag>
|
||||||
|
{{ formatDate(row.last_log.started_at) }}
|
||||||
|
</span>
|
||||||
|
<span v-else style="color:#c0c4cc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" @click.stop="showEditConfig(row)">编辑</el-button>
|
||||||
|
<el-button size="small" @click.stop="selectModule(row); showHistory = true">历史</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div v-if="tcEditDialogVisible">
|
||||||
|
<el-dialog v-model="tcEditDialogVisible" title="编辑任务配置" width="600px" :close-on-click-modal="false">
|
||||||
|
<el-form :model="tcEditForm" label-width="100px">
|
||||||
|
<el-form-item label="模块ID"><el-input v-model="tcEditForm.module_id" disabled/></el-form-item>
|
||||||
|
<el-form-item label="启用"><el-switch v-model="tcEditForm.enabled"/></el-form-item>
|
||||||
|
<el-form-item label="定时">
|
||||||
|
<el-input v-model="tcEditForm.schedule" placeholder="cron 表达式,如 0 30 1 * *"/>
|
||||||
|
<div style="font-size:12px;color:#909399;margin-top:4px;">格式: 分 时 日 月 周 (5段cron)</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="参数">
|
||||||
|
<el-input type="textarea" v-model="tcEditForm.paramsStr" :rows="5" placeholder="JSON 格式参数"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="tcEditDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveTaskConfig" :loading="tcEditSaving">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedModule && showHistory" style="margin-top:20px;">
|
||||||
|
<h4 style="margin:0 0 12px;font-size:14px;color:#303133;display:flex;align-items:center;gap:8px;">
|
||||||
|
运行记录:{{ selectedModule.module_id }}
|
||||||
|
<el-tag size="small" type="info" style="margin-left:4px;">最近 {{ taskLogs.length }} 条</el-tag>
|
||||||
|
<el-button size="small" style="margin-left:auto;" @click="showHistory = false">收起</el-button>
|
||||||
|
<el-button size="small" @click="loadTaskLogs(selectedModule)" :loading="tlLoading">刷新</el-button>
|
||||||
|
</h4>
|
||||||
|
<div v-if="tlLoading" class="card-loading">加载中...</div>
|
||||||
|
<template v-else-if="taskLogs.length === 0">
|
||||||
|
<div class="empty-state" style="padding:20px;">
|
||||||
|
<el-icon style="font-size:36px;color:#c0c4cc;"><IconDocument /></el-icon>
|
||||||
|
<div class="empty-text">暂无运行记录</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-timeline v-else>
|
||||||
|
<el-timeline-item v-for="log in taskLogs" :key="log.id"
|
||||||
|
:timestamp="formatDate(log.started_at)"
|
||||||
|
placement="top"
|
||||||
|
:color="log.status === 'success' ? '#67c23a' : log.status === 'failed' ? '#f56c6c' : log.status === 'running' ? '#e6a23c' : '#c0c4cc'">
|
||||||
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
|
||||||
|
<el-tag :type="log.status === 'success' ? 'success' : log.status === 'failed' ? 'danger' : log.status === 'running' ? 'warning' : 'info'" size="small" effect="dark">
|
||||||
|
{{ log.status }}
|
||||||
|
</el-tag>
|
||||||
|
<span v-if="log.task_name" style="font-size:12px;color:#909399;">{{ log.task_name }}</span>
|
||||||
|
<span v-if="log.duration" style="font-size:12px;color:#909399;">耗时 {{ log.duration }}s</span>
|
||||||
|
<span v-if="log.triggered_by" style="font-size:12px;color:#909399;">触发: {{ log.triggered_by }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="log.message" style="margin-top:6px;font-size:13px;color:#303133;background:#f9fafb;padding:8px 12px;border-radius:6px;border:1px solid #ebeef5;white-space:pre-wrap;word-break:break-word;">{{ log.message }}</div>
|
||||||
|
<div v-if="log.error_trace" style="margin-top:4px;font-size:12px;color:#f56c6c;background:#fef0f0;padding:6px 10px;border-radius:4px;white-space:pre-wrap;word-break:break-word;">{{ log.error_trace }}</div>
|
||||||
|
<div v-if="log.result_data" style="margin-top:4px;">
|
||||||
|
<el-button size="small" text @click="showResultData(log)">查看结果数据</el-button>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-dialog v-model="resultDialogVisible" title="结果数据" width="600px" :close-on-click-modal="false">
|
||||||
|
<pre style="max-height:400px;overflow-y:auto;background:#f9fafb;padding:12px;border-radius:6px;font-size:12px;white-space:pre-wrap;word-break:break-word;">{{ resultDialogContent }}</pre>
|
||||||
|
<template #footer><el-button @click="resultDialogVisible = false">关闭</el-button></template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
@@ -1465,6 +1582,85 @@ const llmConfigs = ref([]);
|
|||||||
const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); };
|
const formatDate = (dateStr) => { if (!dateStr) return '-'; return new Date(dateStr.replace(' ', 'T')).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); };
|
||||||
const logout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; };
|
const logout = () => { localStorage.removeItem('authToken'); localStorage.removeItem('userRole'); localStorage.removeItem('currentUser'); window.location.href = '/login.html'; };
|
||||||
|
|
||||||
|
const taskConfigs = ref([]);
|
||||||
|
const tcLoading = ref(false);
|
||||||
|
const selectedModule = ref(null);
|
||||||
|
const showHistory = ref(false);
|
||||||
|
const taskLogs = ref([]);
|
||||||
|
const tlLoading = ref(false);
|
||||||
|
|
||||||
|
const loadTaskConfigs = async () => {
|
||||||
|
tcLoading.value = true;
|
||||||
|
try {
|
||||||
|
const data = await api.get('/api/admin/task-configs');
|
||||||
|
taskConfigs.value = Array.isArray(data) ? data : (data.data || []);
|
||||||
|
} catch (e) { ElMessage.error('加载任务配置失败: ' + e.message); }
|
||||||
|
finally { tcLoading.value = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleModule = async (row, val) => {
|
||||||
|
try {
|
||||||
|
await api.put(`/api/admin/task-configs/${row.module_id}`, { enabled: val });
|
||||||
|
row.enabled = val;
|
||||||
|
ElMessage.success(val ? '已启用' : '已停用');
|
||||||
|
} catch (e) { ElMessage.error('操作失败: ' + e.message); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const tcEditDialogVisible = ref(false);
|
||||||
|
const tcEditSaving = ref(false);
|
||||||
|
const tcEditForm = reactive({ module_id: '', enabled: true, schedule: '', paramsStr: '{}' });
|
||||||
|
|
||||||
|
const showEditConfig = (row) => {
|
||||||
|
tcEditForm.module_id = row.module_id;
|
||||||
|
tcEditForm.enabled = row.enabled;
|
||||||
|
tcEditForm.schedule = row.schedule || '';
|
||||||
|
tcEditForm.paramsStr = row.params ? JSON.stringify(row.params, null, 2) : '{}';
|
||||||
|
tcEditDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveTaskConfig = async () => {
|
||||||
|
tcEditSaving.value = true;
|
||||||
|
try {
|
||||||
|
let params;
|
||||||
|
try { params = JSON.parse(tcEditForm.paramsStr); }
|
||||||
|
catch (e) { ElMessage.warning('参数格式不是有效的 JSON'); tcEditSaving.value = false; return; }
|
||||||
|
await api.put(`/api/admin/task-configs/${tcEditForm.module_id}`, {
|
||||||
|
enabled: tcEditForm.enabled,
|
||||||
|
schedule: tcEditForm.schedule,
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
tcEditDialogVisible.value = false;
|
||||||
|
await loadTaskConfigs();
|
||||||
|
} catch (e) { ElMessage.error('保存失败: ' + e.message); }
|
||||||
|
finally { tcEditSaving.value = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectModule = (row) => {
|
||||||
|
selectedModule.value = row;
|
||||||
|
showHistory.value = true;
|
||||||
|
loadTaskLogs(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadTaskLogs = async (row) => {
|
||||||
|
tlLoading.value = true;
|
||||||
|
try {
|
||||||
|
const data = await api.get(`/api/admin/task-configs/history/${row.module_id}?limit=30`);
|
||||||
|
taskLogs.value = Array.isArray(data) ? data : (data.data || []);
|
||||||
|
} catch (e) { ElMessage.error('加载运行记录失败: ' + e.message); taskLogs.value = []; }
|
||||||
|
finally { tlLoading.value = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultDialogVisible = ref(false);
|
||||||
|
const resultDialogContent = ref('');
|
||||||
|
const showResultData = (log) => {
|
||||||
|
try {
|
||||||
|
const d = typeof log.result_data === 'string' ? log.result_data : JSON.stringify(log.result_data, null, 2);
|
||||||
|
resultDialogContent.value = d;
|
||||||
|
} catch (e) { resultDialogContent.value = String(log.result_data || ''); }
|
||||||
|
resultDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const tabLoaders = {
|
const tabLoaders = {
|
||||||
llmconfigs: loadLLMConfigs, platformconfigs: loadPlatformConfigs, systemconfigs: loadSystemConfigs,
|
llmconfigs: loadLLMConfigs, platformconfigs: loadPlatformConfigs, systemconfigs: loadSystemConfigs,
|
||||||
users: fetchUsers,
|
users: fetchUsers,
|
||||||
@@ -1473,6 +1669,7 @@ const llmConfigs = ref([]);
|
|||||||
searchrankings: loadRankings,
|
searchrankings: loadRankings,
|
||||||
geo: loadGeoData,
|
geo: loadGeoData,
|
||||||
configitems: () => { loadSensitiveWords(); loadContentCleanRules(); },
|
configitems: () => { loadSensitiveWords(); loadContentCleanRules(); },
|
||||||
|
taskmanagement: loadTaskConfigs,
|
||||||
};
|
};
|
||||||
const loadedTabs = new Set([]);
|
const loadedTabs = new Set([]);
|
||||||
|
|
||||||
@@ -1533,6 +1730,10 @@ const llmConfigs = ref([]);
|
|||||||
loadContentCleanRules, showAddCleanRule, editCleanRule, saveCleanRule, deleteCleanRule,
|
loadContentCleanRules, showAddCleanRule, editCleanRule, saveCleanRule, deleteCleanRule,
|
||||||
rankings, rankingStats, rankingsLoading, loadRankings,
|
rankings, rankingStats, rankingsLoading, loadRankings,
|
||||||
geoOverview, geoReadinessScores, geoLoading, geoTracking, loadGeoData, triggerGeoTrack,
|
geoOverview, geoReadinessScores, geoLoading, geoTracking, loadGeoData, triggerGeoTrack,
|
||||||
|
taskConfigs, tcLoading, selectedModule, showHistory, taskLogs, tlLoading,
|
||||||
|
loadTaskConfigs, selectModule, toggleModule, loadTaskLogs,
|
||||||
|
tcEditDialogVisible, tcEditSaving, tcEditForm, showEditConfig, saveTaskConfig,
|
||||||
|
resultDialogVisible, resultDialogContent, showResultData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user