<el-select
v-model="selectedType"
placeholder="选择日志类型"
clearable
style="width: 200px;"
>
<el-option
v-for="type in typeOptions"
:key="type"
:label="type"
:value="type"
/>
</el-select>
<el-table
:data="pagedLogs"
style="width: 100%"
border
stripe
v-loading="loading"
>
<el-table-column prop="sn" label="设备SN" width="180" />
<el-table-column prop="type" label="类型" width="100" />
<el-table-column prop="type_label" label="类型标签" width="120" />
<el-table-column prop="label" label="标签" width="120" />
<el-table-column prop="message" label="消息" min-width="300" />
<el-table-column prop="create_time" label="时间" width="180">
<template #default="{ row }">
{{ formatTime(row.create_time) }}
</template>
</el-table-column>
</el-table>
<el-pagination
background
layout="total, prev, pager, next"
:total="filteredLogs.length"
:page-size="pageSize"
v-model:current-page="currentPage"
/>
</template>
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
// 响应式数据
const logs = ref([])
const selectedType = ref('')
const eventSource = ref(null)
const loading = ref(false)
// 分页相关状态
const currentPage = ref(1)
const pageSize = ref(20)
// 从日志中提取所有日志类型(去重)
const typeOptions = computed(() => {
const types = new Set()
logs.value.forEach(log => {
if (log.type) types.add(log.type)
})
return Array.from(types)
})
// 过滤后的日志:根据选择的日志类型
const filteredLogs = computed(() => {
if (!selectedType.value) return logs.value
return logs.value.filter(log => log.type === selectedType.value)
})
// 分页后的日志
const pagedLogs = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
return filteredLogs.value.slice(start, end)
})
// 格式化时间
const formatTime = (timestamp) => {
if (!timestamp) return ''
try {
const date = new Date(timestamp)
return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}`
} catch {
return timestamp
}
}
// 初始化SSE连接
const initSSE = () => {
const sseUrl = 'http://127.0.0.1:8888/sse/logs'
if (eventSource.value) {
eventSource.value.close()
}
loading.value = true
eventSource.value = new EventSource(sseUrl)
eventSource.value.onmessage = (event) => {
try {
const newLogs = JSON.parse(event.data)
// 直接替换整个日志数组
logs.value = newLogs
loading.value = false
} catch (error) {
console.error('解析日志数据失败:', error)
loading.value = false
}
}
eventSource.value.onerror = (error) => {
console.error('SSE连接错误:', error)
loading.value = false
// 错误时尝试重新连接
setTimeout(initSSE, 3000)
}
}
onMounted(() => {
initSSE()
})
onBeforeUnmount(() => {
if (eventSource.value) {
eventSource.value.close()
}
})
</script>
<style scoped>
.container {
padding: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.left-section {
display: flex;
align-items: center;
}
.right-section {
display: flex;
align-items: center;
}
.el-table {
flex: 1;
overflow: hidden;
}
/* 分页居中样式 */
.pagination-center {
display: flex;
justify-content: center; /* 水平居中 */
margin-top: 15px;
}
/* 调整分页组件样式 */
.el-pagination {
--el-pagination-bg-color: #fff;
--el-pagination-button-disabled-bg-color: #fff;
--el-pagination-button-bg-color: #fff;
--el-pagination-hover-color: #409eff;
--el-pagination-button-radius: 4px;
}
/* 分页按钮样式优化 */
.el-pagination .btn-prev,
.el-pagination .btn-next,
.el-pager li {
min-width: 32px;
height: 32px;
line-height: 32px;
margin: 0 4px;
border-radius: 4px;
}
/* 当前页样式 */
.el-pager li.is-active {
background-color: #409eff;
color: #fff;
font-weight: bold;
}
/* 表格边框优化 */
.el-table--border {
border: 1px solid #ebeef5;
border-radius: 4px;
}
/* 单元格内边距 */
.el-table td, .el-table th {
padding: 8px 0;
}
</style>