Files
schoolNews/schoolNewsWeb/src/views/admin/manage/crontab/LogManagementView.vue
2025-10-27 13:42:34 +08:00

506 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="log-management">
<div class="header">
<h2>执行日志</h2>
<el-button type="danger" @click="handleCleanLogs">
<el-icon><Delete /></el-icon>
清理日志
</el-button>
</div>
<!-- 搜索筛选区域 -->
<div class="search-bar">
<div class="search-item">
<span class="search-label">任务名称</span>
<el-input
v-model="searchForm.taskName"
placeholder="请输入任务名称"
clearable
@keyup.enter="handleSearch"
style="width: 200px"
/>
</div>
<div class="search-item">
<span class="search-label">任务分组</span>
<el-input
v-model="searchForm.taskGroup"
placeholder="请输入任务分组"
clearable
@keyup.enter="handleSearch"
style="width: 200px"
/>
</div>
<div class="search-item">
<span class="search-label">执行状态</span>
<el-select
v-model="searchForm.executeStatus"
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="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</div>
</div>
<!-- 日志列表 -->
<el-table
:data="logList"
style="width: 100%"
v-loading="loading"
border
stripe
>
<el-table-column prop="taskName" label="任务名称" min-width="150" />
<el-table-column prop="taskGroup" label="任务分组" width="120" />
<el-table-column prop="beanName" label="Bean名称" min-width="150" />
<el-table-column prop="methodName" label="方法名称" width="120" />
<el-table-column label="执行状态" width="100">
<template #default="{ row }">
<el-tag :type="row.executeStatus === 1 ? 'success' : 'danger'" size="small">
{{ row.executeStatus === 1 ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="executeDuration" label="执行时长" width="100">
<template #default="{ row }">
{{ row.executeDuration }}ms
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="180" />
<el-table-column prop="endTime" label="结束时间" width="180" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
size="small"
@click="handleViewDetail(row)"
>
查看详情
</el-button>
<el-button
type="danger"
size="small"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper" v-if="total > 0">
<el-pagination
v-model:current-page="pageParam.pageNumber"
v-model:page-size="pageParam.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
<!-- 详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="执行日志详情"
width="700px"
>
<div class="detail-content" v-if="currentLog">
<div class="detail-item">
<span class="detail-label">任务名称</span>
<span class="detail-value">{{ currentLog.taskName }}</span>
</div>
<div class="detail-item">
<span class="detail-label">任务分组</span>
<span class="detail-value">{{ currentLog.taskGroup }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Bean名称</span>
<span class="detail-value">{{ currentLog.beanName }}</span>
</div>
<div class="detail-item">
<span class="detail-label">方法名称</span>
<span class="detail-value">{{ currentLog.methodName }}</span>
</div>
<div class="detail-item" v-if="currentLog.methodParams">
<span class="detail-label">方法参数</span>
<span class="detail-value">{{ currentLog.methodParams }}</span>
</div>
<div class="detail-item">
<span class="detail-label">执行状态</span>
<el-tag :type="currentLog.executeStatus === 1 ? 'success' : 'danger'" size="small">
{{ currentLog.executeStatus === 1 ? '成功' : '失败' }}
</el-tag>
</div>
<div class="detail-item">
<span class="detail-label">执行时长</span>
<span class="detail-value">{{ currentLog.executeDuration }}ms</span>
</div>
<div class="detail-item">
<span class="detail-label">开始时间</span>
<span class="detail-value">{{ currentLog.startTime }}</span>
</div>
<div class="detail-item">
<span class="detail-label">结束时间</span>
<span class="detail-value">{{ currentLog.endTime }}</span>
</div>
<div class="detail-item" v-if="currentLog.executeMessage">
<span class="detail-label">执行结果</span>
<div class="detail-message">{{ currentLog.executeMessage }}</div>
</div>
<div class="detail-item" v-if="currentLog.exceptionInfo">
<span class="detail-label">异常信息</span>
<div class="detail-exception">{{ currentLog.exceptionInfo }}</div>
</div>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 清理日志对话框 -->
<el-dialog
v-model="cleanDialogVisible"
title="清理日志"
width="400px"
>
<div class="clean-dialog-content">
<el-alert
title="清理操作不可恢复,请谨慎操作!"
type="warning"
:closable="false"
show-icon
/>
<div class="clean-item">
<span class="clean-label">保留天数</span>
<el-input-number
v-model="cleanDays"
:min="1"
:max="365"
controls-position="right"
/>
<span class="clean-tip"></span>
</div>
<div class="clean-desc">
将删除 {{ cleanDays }} 天前的所有执行日志
</div>
</div>
<template #footer>
<el-button @click="cleanDialogVisible = false">取消</el-button>
<el-button
type="danger"
@click="handleConfirmClean"
:loading="submitting"
>
确定清理
</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Delete, Search, Refresh } from '@element-plus/icons-vue';
import { crontabApi } from '@/apis/crontab';
import type { CrontabLog, PageParam } from '@/types';
// 数据状态
const loading = ref(false);
const submitting = ref(false);
const logList = ref<CrontabLog[]>([]);
const total = ref(0);
const currentLog = ref<CrontabLog | null>(null);
// 搜索表单
const searchForm = reactive({
taskName: '',
taskGroup: '',
executeStatus: undefined as number | undefined
});
// 分页参数
const pageParam = reactive<PageParam>({
pageNumber: 1,
pageSize: 20
});
// 对话框状态
const detailDialogVisible = ref(false);
const cleanDialogVisible = ref(false);
const cleanDays = ref(30);
// 加载日志列表
const loadLogList = async () => {
loading.value = true;
try {
const filter: Partial<CrontabLog> = {};
if (searchForm.taskName) filter.taskName = searchForm.taskName;
if (searchForm.taskGroup) filter.taskGroup = searchForm.taskGroup;
if (searchForm.executeStatus !== undefined) filter.executeStatus = searchForm.executeStatus;
const result = await crontabApi.getLogPage(filter, pageParam);
if (result.success && result.dataList) {
logList.value = result.dataList;
total.value = result.pageParam?.totalElements || 0;
} else {
ElMessage.error(result.message || '加载日志列表失败');
logList.value = [];
total.value = 0;
}
} catch (error) {
console.error('加载日志列表失败:', error);
ElMessage.error('加载日志列表失败');
logList.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
// 搜索
const handleSearch = () => {
pageParam.pageNumber = 1;
loadLogList();
};
// 重置搜索
const handleReset = () => {
searchForm.taskName = '';
searchForm.taskGroup = '';
searchForm.executeStatus = undefined;
pageParam.pageNumber = 1;
loadLogList();
};
// 分页变化
const handlePageChange = (page: number) => {
pageParam.pageNumber = page;
loadLogList();
};
const handleSizeChange = (size: number) => {
pageParam.pageSize = size;
pageParam.pageNumber = 1;
loadLogList();
};
// 查看详情
const handleViewDetail = async (row: CrontabLog) => {
try {
const result = await crontabApi.getLogById(row.id!);
if (result.success && result.data) {
currentLog.value = result.data;
detailDialogVisible.value = true;
} else {
ElMessage.error(result.message || '获取详情失败');
}
} catch (error) {
console.error('获取日志详情失败:', error);
ElMessage.error('获取日志详情失败');
}
};
// 删除日志
const handleDelete = async (row: CrontabLog) => {
try {
await ElMessageBox.confirm(
'确定要删除这条日志吗?',
'删除确认',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
const result = await crontabApi.deleteLog(row);
if (result.success) {
ElMessage.success('删除成功');
loadLogList();
} else {
ElMessage.error(result.message || '删除失败');
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('删除日志失败:', error);
ElMessage.error('删除日志失败');
}
}
};
// 清理日志
const handleCleanLogs = () => {
cleanDialogVisible.value = true;
};
// 确认清理
const handleConfirmClean = async () => {
submitting.value = true;
try {
const result = await crontabApi.cleanLogs(cleanDays.value);
if (result.success) {
ElMessage.success(`已清理 ${result.data || 0} 条日志`);
cleanDialogVisible.value = false;
loadLogList();
} else {
ElMessage.error(result.message || '清理失败');
}
} catch (error) {
console.error('清理日志失败:', error);
ElMessage.error('清理日志失败');
} finally {
submitting.value = false;
}
};
// 初始化
onMounted(() => {
loadLogList();
});
</script>
<style scoped lang="scss">
.log-management {
padding: 20px;
background-color: #fff;
border-radius: 4px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #141F38;
}
}
.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;
}
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
}
.detail-content {
.detail-item {
display: flex;
align-items: flex-start;
margin-bottom: 16px;
font-size: 14px;
.detail-label {
min-width: 100px;
color: #606266;
font-weight: 500;
}
.detail-value {
flex: 1;
color: #303133;
word-break: break-all;
}
.detail-message,
.detail-exception {
flex: 1;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 13px;
color: #303133;
white-space: pre-wrap;
word-break: break-all;
max-height: 300px;
overflow-y: auto;
}
.detail-exception {
background-color: #fef0f0;
color: #f56c6c;
}
}
}
.clean-dialog-content {
.clean-item {
display: flex;
align-items: center;
gap: 12px;
margin: 20px 0;
.clean-label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
.clean-tip {
font-size: 14px;
color: #909399;
}
}
.clean-desc {
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
font-size: 13px;
color: #606266;
}
}
}
</style>