Files
abot/admin/dashboard/templates/wx_logs.html
2025-05-28 17:16:15 +08:00

218 lines
8.2 KiB
HTML
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.
{% extends "base.html" %}
{% block title %}微信日志查看{% endblock %}
{% block content %}
<div>
<el-card class="log-card">
<template slot="header">
<div class="flex items-center justify-between">
<span class="text-lg font-semibold">日志查看</span>
<div class="flex items-center space-x-4">
<el-radio-group v-model="logType" size="small" @change="loadLogs">
<el-radio-button label="info">信息日志</el-radio-button>
<el-radio-button label="error">错误日志</el-radio-button>
<el-radio-button label="debug">调试日志</el-radio-button>
</el-radio-group>
<el-select v-model="logLines" size="small" @change="loadLogs">
<el-option label="最近100行" :value="100"></el-option>
<el-option label="最近500行" :value="500"></el-option>
<el-option label="最近1000行" :value="1000"></el-option>
</el-select>
<el-select v-model="refreshInterval" size="small" style="width: 100px;" @change="handleRefreshInterval">
<el-option label="手动" :value="0"></el-option>
<el-option label="1秒" :value="1"></el-option>
<el-option label="3秒" :value="3"></el-option>
<el-option label="5秒" :value="5"></el-option>
<el-option label="10秒" :value="10"></el-option>
<el-option label="60秒" :value="60"></el-option>
</el-select>
<el-button size="small" type="primary" @click="loadLogs">刷新</el-button>
</div>
</div>
</template>
<div v-loading="loading" class="relative">
<div class="mb-4 flex items-center">
<el-input v-model="searchQuery" placeholder="搜索日志内容" clearable @input="filterLogs" size="small" style="width: 300px;"></el-input>
<el-checkbox v-model="autoScroll" class="ml-4">自动滚动到底部</el-checkbox>
</div>
<div v-if="filteredLogs.length > 0" class="log-content">
{% raw %}
<div v-for="(log, index) in filteredLogs" :key="index" :class="['log-entry', `log-level-${log.level}`]">
<span class="log-timestamp">{{ formatTimestamp(log.timestamp) }}</span>
<span class="log-level" :class="{'text-blue-500': log.level === 'INFO', 'text-yellow-500': log.level === 'WARNING', 'text-red-500': log.level === 'ERROR'}">{{ log.level }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
{% endraw %}
</div>
<div v-else class="empty-log">
<el-empty description="暂无日志内容"></el-empty>
</div>
</div>
</el-card>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
loading: false,
logType: 'info',
logLines: 100,
logContent: [],
filteredLogs: [],
searchQuery: '',
autoScroll: true,
refreshInterval: 0,
refreshTimer: null,
currentView: '9'
}
},
mounted() {
this.loadLogs();
},
beforeDestroy() {
this.clearRefreshTimer();
},
methods: {
async loadLogs() {
this.loading = true;
try {
const response = await axios.get(`/api/wx_logs?type=${this.logType}&lines=${this.logLines}`);
if (response.data.success) {
// 解析文本日志行为对象数组
this.logContent = this.parseLogLines(response.data.data.content || []);
this.filterLogs();
this.scrollToBottom();
} else {
this.$message.error('加载日志失败');
}
} catch (error) {
console.error('加载日志出错:', error);
this.$message.error('加载日志出错');
} finally {
this.loading = false;
}
},
parseLogLines(lines) {
return lines.map(line => {
// 解析日志行格式如2025-05-28 17:10:04.758 | INFO | module:func:line - message
const parts = line.split(' | ');
if (parts.length < 4) {
return { timestamp: '', level: '', message: line }; // 格式不正确时保留原始行
}
const [timestamp, level, , message] = parts;
return {
timestamp: timestamp.trim(),
level: level.trim(),
message: message.trim()
};
}).filter(log => log.timestamp && log.level && log.message); // 过滤无效日志
},
filterLogs() {
if (!this.searchQuery) {
this.filteredLogs = this.logContent;
} else {
const query = this.searchQuery.toLowerCase();
this.filteredLogs = this.logContent.filter(log =>
(log.message && log.message.toLowerCase().includes(query)) ||
(log.level && log.level.toLowerCase().includes(query)) ||
(log.timestamp && log.timestamp.toLowerCase().includes(query))
);
}
this.$nextTick(() => this.scrollToBottom());
},
formatTimestamp(timestamp) {
if (!timestamp) return '';
try {
return new Date(timestamp).toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
});
} catch (e) {
return timestamp; // 解析失败时返回原始时间戳
}
},
scrollToBottom() {
if (this.autoScroll) {
this.$nextTick(() => {
const logDiv = this.$el.querySelector('.log-content');
if (logDiv) {
logDiv.scrollTop = logDiv.scrollHeight;
}
});
}
},
handleRefreshInterval() {
this.clearRefreshTimer();
if (this.refreshInterval > 0) {
this.refreshTimer = setInterval(() => {
this.loadLogs();
}, this.refreshInterval * 1000);
}
},
clearRefreshTimer() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
}
});
</script>
<style scoped>
.log-card {
margin: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.log-content {
max-height: 600px;
overflow-y: auto;
background-color: #f8fafc;
padding: 16px;
border-radius: 4px;
font-family: 'Courier New', Courier, monospace;
}
.log-entry {
display: flex;
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
}
.log-timestamp {
flex: 0 0 180px;
color: #6b7280;
font-size: 0.9rem;
}
.log-level {
flex: 0 0 100px;
font-weight: 600;
font-size: 0.9rem;
}
.log-message {
flex: 1;
word-break: break-all;
font-size: 0.9rem;
}
.log-level-INFO { background-color: #e6f3ff; }
.log-level-WARNING { background-color: #fefcbf; }
.log-level-ERROR { background-color: #fee2e2; }
.empty-log {
padding: 40px 0;
text-align: center;
}
</style>
{% endblock %}