Files
EmailBill/Web/src/views/LogView.vue
孙诚 b2339c1c5e
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 20s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
feat: update VSCode settings for ESLint and Prettier integration
chore: refactor ESLint configuration for improved linting rules and performance

fix: handle push event data parsing in service worker

style: adjust tabbar item properties for better readability in App.vue

refactor: remove unused functions and improve code clarity in TransactionDetail.vue

fix: ensure consistent event handling in CalendarView.vue

style: clean up component structure and formatting in various Vue files

chore: update launch script for better command execution

feat: add ESLint configuration file for consistent code style across the project

fix: resolve issues with button click events in multiple components
2026-01-07 14:33:30 +08:00

464 lines
9.3 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="page-container-flex log-view">
<van-nav-bar
title="查看日志"
left-text="返回"
left-arrow
placeholder
@click-left="handleBack"
/>
<div class="scroll-content">
<!-- 搜索和筛选 -->
<div class="filter-section">
<van-search
v-model="searchKeyword"
placeholder="输入关键词筛选日志"
@search="handleSearch"
@clear="handleClear"
/>
<div class="filter-row">
<van-dropdown-menu>
<van-dropdown-item v-model="selectedLevel" :options="levelOptions" @change="handleSearch" />
<van-dropdown-item v-model="selectedDate" :options="dateOptions" @change="handleSearch" />
</van-dropdown-menu>
</div>
</div>
<!-- 下拉刷新区域 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 加载提示 -->
<van-loading v-if="loading && !logList.length" vertical style="padding: 50px 0">
加载中...
</van-loading>
<!-- 日志列表 -->
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
class="log-list"
@load="onLoad"
>
<div
v-for="(log, index) in logList"
:key="index"
class="log-item"
:class="getLevelClass(log.level)"
>
<div class="log-header">
<span class="log-level">{{ log.level }}</span>
<span class="log-time">{{ formatTime(log.timestamp) }}</span>
</div>
<div class="log-message">{{ log.message }}</div>
</div>
<!-- 空状态 -->
<van-empty
v-if="!loading && !logList.length"
description="暂无日志"
image="search"
/>
</van-list>
<!-- 底部安全距离 -->
<div style="height: 20px"></div>
</van-pull-refresh>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { showToast } from 'vant'
import { getLogList, getAvailableDates } from '@/api/log'
const router = useRouter()
// 数据状态
const logList = ref([])
const loading = ref(false)
const finished = ref(false)
const refreshing = ref(false)
const fetching = ref(false)
// 分页参数
const pageIndex = ref(1)
const pageSize = ref(100)
const total = ref(0)
// 筛选参数
const searchKeyword = ref('')
const selectedLevel = ref('')
const selectedDate = ref('')
// 日志级别选项
const levelOptions = ref([
{ text: '全部级别', value: '' },
{ text: 'VRB', value: 'VRB' },
{ text: 'DBG', value: 'DBG' },
{ text: 'INF', value: 'INF' },
{ text: 'WRN', value: 'WRN' },
{ text: 'ERR', value: 'ERR' },
{ text: 'FTL', value: 'FTL' }
])
// 日期选项
const dateOptions = ref([
{ text: '全部日期', value: '' }
])
/**
* 返回上一页
*/
const handleBack = () => {
router.back()
}
/**
* 获取日志级别对应的样式类
*/
const getLevelClass = (level) => {
const levelMap = {
'ERR': 'level-error',
'FTL': 'level-fatal',
'WRN': 'level-warning',
'INF': 'level-info',
'DBG': 'level-debug',
'VRB': 'level-verbose'
}
return levelMap[level] || 'level-default'
}
/**
* 格式化时间显示
*/
const formatTime = (timestamp) => {
// 提取时间部分,去掉时区
const match = timestamp.match(/(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})/)
return match ? match[1] : timestamp
}
/**
* 加载日志数据
*/
const loadLogs = async (reset = false) => {
if (fetching.value) return
fetching.value = true
try {
if (reset) {
pageIndex.value = 1
logList.value = []
finished.value = false
}
const params = {
pageIndex: pageIndex.value,
pageSize: pageSize.value
}
if (searchKeyword.value) {
params.searchKeyword = searchKeyword.value
}
if (selectedLevel.value) {
params.logLevel = selectedLevel.value
}
if (selectedDate.value) {
params.date = selectedDate.value
}
const response = await getLogList(params)
if (response.success) {
const newLogs = response.data || []
if (reset) {
logList.value = newLogs
} else {
logList.value = [...logList.value, ...newLogs]
}
total.value = response.total
// 判断是否还有更多数据
// total = -1 表示总数未知,此时只根据返回数据量判断
if (total.value === -1) {
// 如果返回的数据少于请求的数量,说明没有更多了
finished.value = newLogs.length < pageSize.value
} else {
// 如果有明确的总数,则判断是否已加载完全部数据
if (logList.value.length >= total.value || newLogs.length < pageSize.value) {
finished.value = true
} else {
finished.value = false
}
}
} else {
showToast(response.message || '获取日志失败')
finished.value = true
}
} catch (error) {
console.error('加载日志失败:', error)
showToast('加载日志失败')
finished.value = true
} finally {
fetching.value = false
loading.value = false
refreshing.value = false
}
}
/**
* 下拉刷新
*/
const onRefresh = async () => {
await loadLogs(true)
}
/**
* 加载更多
*/
const onLoad = async () => {
if (finished.value || fetching.value) return
// 如果是第一次加载
if (pageIndex.value === 1 && logList.value.length === 0) {
await loadLogs(false)
} else {
// 后续分页加载
pageIndex.value++
await loadLogs(false)
}
}
/**
* 搜索处理
*/
const handleSearch = () => {
loadLogs(true)
}
/**
* 清空搜索
*/
const handleClear = () => {
searchKeyword.value = ''
handleSearch()
}
/**
* 加载可用日期列表
*/
const loadAvailableDates = async () => {
try {
const response = await getAvailableDates()
if (response.success && response.data) {
const dates = response.data.map(date => ({
text: formatDate(date),
value: date
}))
dateOptions.value = [
{ text: '全部日期', value: '' },
...dates
]
}
} catch (error) {
console.error('加载日期列表失败:', error)
}
}
/**
* 格式化日期显示
*/
const formatDate = (dateStr) => {
// dateStr 格式: 20251229
if (dateStr.length === 8) {
return `${dateStr.substring(0, 4)}-${dateStr.substring(4, 6)}-${dateStr.substring(6, 8)}`
}
return dateStr
}
// 组件挂载时加载数据
onMounted(() => {
loadAvailableDates()
// 不在这里调用 loadLogs让 van-list 的 @load 事件自动触发
})
</script>
<style scoped>
.log-view {
height: 100vh;
background-color: #f5f5f5;
}
@media (prefers-color-scheme: dark) {
.log-view {
background-color: #1a1a1a;
}
}
.filter-section {
background-color: #ffffff;
position: sticky;
top: 0;
z-index: 10;
}
@media (prefers-color-scheme: dark) {
.filter-section {
background-color: #2c2c2c;
}
}
.filter-row {
padding: 0;
}
.log-list {
padding: 4px 12px;
}
.log-item {
background-color: #ffffff;
border-left: 3px solid #1989fa;
margin-bottom: 4px;
padding: 6px 10px;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
transition: all 0.2s;
}
.log-item:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
.log-item {
background-color: #2c2c2c;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.log-item:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
}
.log-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
font-size: 11px;
}
.log-level {
display: inline-block;
padding: 1px 6px;
border-radius: 2px;
font-weight: bold;
color: #ffffff;
min-width: 36px;
text-align: center;
font-size: 10px;
}
.log-time {
color: #969799;
font-size: 10px;
}
@media (prefers-color-scheme: dark) {
.log-time {
color: #858585;
}
}
.log-message {
color: #323233;
line-height: 1.4;
word-break: break-word;
white-space: pre-wrap;
}
@media (prefers-color-scheme: dark) {
.log-message {
color: #e0e0e0;
}
}
/* 不同日志级别的颜色 */
.level-verbose .log-level {
background-color: #b0b0b0;
}
.level-debug .log-level {
background-color: #1989fa;
}
.level-info .log-level {
background-color: #07c160;
}
.level-warning .log-level {
background-color: #ff976a;
border-left-color: #ff976a;
}
.level-error .log-level {
background-color: #ee0a24;
}
.level-fatal .log-level {
background-color: #8b0000;
}
.level-default .log-level {
background-color: #646566;
}
.level-verbose {
border-left-color: #b0b0b0;
}
.level-debug {
border-left-color: #1989fa;
}
.level-info {
border-left-color: #07c160;
}
.level-warning {
border-left-color: #ff976a;
}
.level-error {
border-left-color: #ee0a24;
}
.level-fatal {
border-left-color: #8b0000;
}
.level-default {
border-left-color: #646566;
}
/* 优化下拉菜单样式 */
:deep(.van-dropdown-menu) {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}
@media (prefers-color-scheme: dark) {
:deep(.van-dropdown-menu) {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
}
</style>