|
|
@@ -1,25 +1,127 @@
|
|
|
<template>
|
|
|
- <el-container>
|
|
|
- <el-header height="220">
|
|
|
- <h3>访问日志</h3>
|
|
|
+ <el-container class="log-container">
|
|
|
+ <el-header height="60px" class="log-header">
|
|
|
+ <div class="header-left">
|
|
|
+ <i class="el-icon-document" />
|
|
|
+ <h3>系统访问日志</h3>
|
|
|
+ </div>
|
|
|
+ <div class="header-right">
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon="el-icon-refresh-right"
|
|
|
+ :loading="loading"
|
|
|
+ @click="handleRefreshLatest"
|
|
|
+ >
|
|
|
+ 回到最新(重置)
|
|
|
+ </el-button>
|
|
|
+ <el-tag type="info" size="small" style="margin-left: 12px;">单页容纳: {{ pageSize }} 条</el-tag>
|
|
|
+ </div>
|
|
|
</el-header>
|
|
|
- <el-main>
|
|
|
+
|
|
|
+ <el-main v-loading="loading" class="log-main">
|
|
|
+ <el-table
|
|
|
+ :data="dataList"
|
|
|
+ border
|
|
|
+ stripe
|
|
|
+ size="small"
|
|
|
+ style="width: 100%"
|
|
|
+ :header-cell-style="{ background: '#f8fafc', color: '#475569' }"
|
|
|
+ >
|
|
|
+ <el-table-column prop="id" label="日志ID" width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="requestTime" label="请求时间" width="160" />
|
|
|
+
|
|
|
+ <el-table-column prop="requestMethod" label="方法" width="80" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-tag :type="getMethodTag(scope.row.requestMethod)" size="mini" effect="dark">
|
|
|
+ {{ scope.row.requestMethod }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="requestUrl" label="请求URL" min-width="220" show-overflow-tooltip />
|
|
|
+
|
|
|
+ <el-table-column prop="statusCode" label="状态码" width="90" align="center">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span :class="getStatusClass(scope.row.statusCode)">
|
|
|
+ {{ scope.row.statusCode }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="executeTime" label="耗时" width="90" align="right">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span :style="{ color: scope.row.executeTime > 1000 ? '#ef4444' : '#10b981', fontWeight: 'bold' }">
|
|
|
+ {{ scope.row.executeTime }} ms
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column prop="targetRoute" label="路由网关" width="150" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="targetService" label="下游服务实例" width="160" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="remoteAddr" label="来源IP" width="120" align="center" />
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="pagination-wrapper">
|
|
|
+ <span class="total-count">当前批次条数: {{ dataList.length }} / 估算总数: {{ totalSize }}</span>
|
|
|
+
|
|
|
+ <div class="pagination-actions">
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ icon="el-icon-refresh"
|
|
|
+ style="margin-right: 12px;"
|
|
|
+ :loading="loading"
|
|
|
+ @click="getData"
|
|
|
+ >
|
|
|
+ 刷新当前页
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <el-button-group>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ icon="el-icon-arrow-left"
|
|
|
+ :disabled="!hasPrev"
|
|
|
+ @click="handleNav('prev')"
|
|
|
+ >
|
|
|
+ 上一页
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ :disabled="!hasNext"
|
|
|
+ @click="handleNav('next')"
|
|
|
+ >
|
|
|
+ 下一页<i class="el-icon-arrow-right el-icon--right" />
|
|
|
+ </el-button>
|
|
|
+ </el-button-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</el-main>
|
|
|
</el-container>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+import { getAccessLog } from '@/api/tnb'
|
|
|
+
|
|
|
export default {
|
|
|
name: 'AdminAccessLog',
|
|
|
data() {
|
|
|
return {
|
|
|
- // 屏幕宽度, 为了控制分页条的大小
|
|
|
- screenWidth: document.body.clientWidth,
|
|
|
- currentPage: 1,
|
|
|
- pageSize: 12,
|
|
|
- totalSize: 0,
|
|
|
+ loading: false,
|
|
|
dataList: [],
|
|
|
- nextId: 0
|
|
|
+ // 分页相关游标状态
|
|
|
+ pageSize: 20,
|
|
|
+ totalSize: 0,
|
|
|
+ prevId: '',
|
|
|
+ nextId: '',
|
|
|
+ hasPrev: false,
|
|
|
+ hasNext: false,
|
|
|
+ // 当前查询使用的游标和方向
|
|
|
+ currentCursor: {
|
|
|
+ cursorId: '',
|
|
|
+ direction: '' // 'prev' 或 'next',第一页传空
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
created() {
|
|
|
@@ -27,11 +129,172 @@ export default {
|
|
|
this.getData()
|
|
|
},
|
|
|
methods: {
|
|
|
+ // 获取日志数据
|
|
|
getData() {
|
|
|
+ this.loading = true
|
|
|
+
|
|
|
+ // 组装带游标的入参
|
|
|
+ const params = {
|
|
|
+ pageSize: this.pageSize
|
|
|
+ }
|
|
|
+ if (this.currentCursor.cursorId) {
|
|
|
+ params.cursorId = this.currentCursor.cursorId
|
|
|
+ params.direction = this.currentCursor.direction
|
|
|
+ }
|
|
|
+
|
|
|
+ getAccessLog(params).then(resp => {
|
|
|
+ if (resp.code === 0 && resp.data) {
|
|
|
+ const { list, pageSize, totalSize, prevId, nextId, hasPrev, hasNext } = resp.data
|
|
|
+ this.dataList = list || []
|
|
|
+ this.pageSize = pageSize
|
|
|
+ this.totalSize = totalSize
|
|
|
+ this.prevId = prevId
|
|
|
+ this.nextId = nextId
|
|
|
+ this.hasPrev = hasPrev
|
|
|
+ this.hasNext = hasNext
|
|
|
+ } else {
|
|
|
+ this.$message.error(resp.msg || '获取日志失败')
|
|
|
+ }
|
|
|
+ }).catch(() => {
|
|
|
+ this.$message.error('网关连接异常')
|
|
|
+ }).finally(() => {
|
|
|
+ this.loading = false
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 🌟 新增方法:清空游标,直接回到第一页看最新的日志
|
|
|
+ handleRefreshLatest() {
|
|
|
+ this.currentCursor = {
|
|
|
+ cursorId: '',
|
|
|
+ direction: ''
|
|
|
+ }
|
|
|
+ this.getData()
|
|
|
+ this.$message.success('已刷新并展示最新日志')
|
|
|
+ },
|
|
|
+
|
|
|
+ // 翻页流转控制
|
|
|
+ handleNav(action) {
|
|
|
+ if (action === 'prev' && this.hasPrev) {
|
|
|
+ this.currentCursor = {
|
|
|
+ cursorId: this.prevId,
|
|
|
+ direction: 'prev'
|
|
|
+ }
|
|
|
+ this.getData()
|
|
|
+ } else if (action === 'next' && this.hasNext) {
|
|
|
+ this.currentCursor = {
|
|
|
+ cursorId: this.nextId,
|
|
|
+ direction: 'next'
|
|
|
+ }
|
|
|
+ this.getData()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 状态码视觉渲染器
|
|
|
+ getStatusClass(code) {
|
|
|
+ if (code >= 200 && code < 300) return 'status-2xx'
|
|
|
+ if (code >= 400 && code < 500) return 'status-4xx'
|
|
|
+ if (code >= 500) return 'status-5xx'
|
|
|
+ return 'status-info'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 请求方法标签色划分
|
|
|
+ getMethodTag(method) {
|
|
|
+ const maps = {
|
|
|
+ 'GET': 'success',
|
|
|
+ 'POST': '',
|
|
|
+ 'PUT': 'warning',
|
|
|
+ 'DELETE': 'danger'
|
|
|
+ }
|
|
|
+ return maps[method.toUpperCase()] || 'info'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style>
|
|
|
+<style scoped>
|
|
|
+.log-container {
|
|
|
+ background-color: #f4f7f9;
|
|
|
+ min-height: calc(100vh - 40px);
|
|
|
+ margin: 20px;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.log-header {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-bottom: 1px solid #e2e8f0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 0 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.header-left {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+.header-left i {
|
|
|
+ font-size: 20px;
|
|
|
+ color: #3b82f6;
|
|
|
+}
|
|
|
+.header-left h3 {
|
|
|
+ margin: 0;
|
|
|
+ color: #1e293b;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.header-right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.log-main {
|
|
|
+ background: #ffffff;
|
|
|
+ padding: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 翻页组件沙箱 */
|
|
|
+.pagination-wrapper {
|
|
|
+ margin-top: 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ background: #f8fafc;
|
|
|
+ padding: 12px 20px;
|
|
|
+ border-radius: 6px;
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+}
|
|
|
+
|
|
|
+.pagination-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.total-count {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #64748b;
|
|
|
+ font-family: monospace;
|
|
|
+}
|
|
|
+
|
|
|
+/* 状态码样式定义 */
|
|
|
+.status-2xx {
|
|
|
+ color: #10b981;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.status-4xx {
|
|
|
+ color: #f59e0b;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+.status-5xx {
|
|
|
+ color: #ef4444;
|
|
|
+ font-weight: bold;
|
|
|
+ background-color: #fee2e2;
|
|
|
+ padding: 2px 6px;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+.status-info {
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
</style>
|