1
This commit is contained in:
163
app/views/LogPage.vue
Normal file
163
app/views/LogPage.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div class="page-log">
|
||||
<h2 class="page-title">运行日志</h2>
|
||||
|
||||
<!-- 日志控制 -->
|
||||
<div class="log-controls-section">
|
||||
<div class="log-controls">
|
||||
<Button class="btn" @click="handleClearLogs">清空日志</Button>
|
||||
<Button class="btn" @click="handleExportLogs">导出日志</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志内容 -->
|
||||
<div class="log-content-section">
|
||||
<div class="log-container" id="log-container">
|
||||
<div v-for="(log, index) in logEntries" :key="index" class="log-entry">
|
||||
<span class="log-time">[{{ log.time }}]</span>
|
||||
<span :class="['log-level', log.level.toLowerCase()]">[{{ log.level }}]</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
<div v-if="logEntries.length === 0" class="log-empty">
|
||||
<p>暂无日志记录</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Button } from '../components/PrimeVue';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'LogPage',
|
||||
components: {
|
||||
Button
|
||||
},
|
||||
computed: {
|
||||
...mapState('log', ['logs']),
|
||||
logEntries() {
|
||||
return this.logs;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToBottom();
|
||||
},
|
||||
updated() {
|
||||
this.scrollToBottom();
|
||||
},
|
||||
methods: {
|
||||
...mapActions('log', ['clearLogs', 'exportLogs']),
|
||||
handleClearLogs() {
|
||||
this.clearLogs();
|
||||
},
|
||||
handleExportLogs() {
|
||||
this.exportLogs();
|
||||
},
|
||||
scrollToBottom() {
|
||||
this.$nextTick(() => {
|
||||
const logContainer = document.getElementById('log-container');
|
||||
if (logContainer) {
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
logEntries() {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-log {
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.log-controls-section {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.log-content-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.log-container {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 4px;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #808080;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
margin-right: 8px;
|
||||
font-weight: 600;
|
||||
|
||||
&.info {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
&.warn {
|
||||
color: #ffa726;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
&.debug {
|
||||
color: #90caf9;
|
||||
}
|
||||
}
|
||||
|
||||
.log-message {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.log-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #808080;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user