Files
base_lms/nladmin-ui/src/views/lucene/index.vue

501 lines
13 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="app-container">
<div class="head-container">
<el-form
:inline="true"
label-position="right"
label-width="=100px"
label-suffix=":"
>
<el-form-item :label="$t('Log.label')">
<el-select
v-model="query.label"
clearable
size="mini"
:placeholder="$t('Log.label')"
style="width: 240px;"
>
<el-option
v-for="item in tagList"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t('Log.level')">
<el-select
v-model="query.level"
clearable
size="mini"
:placeholder="$t('Log.level')"
>
<el-option
v-for="item in levelOptions"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="$t('Log.search')">
<el-input
v-model="query.message"
clearable
size="mini"
:placeholder="$t('Log.content')"
/>
</el-form-item>
<el-form-item :label="$t('Log.link_id')">
<el-input
v-model="query.tlogTraceId"
clearable
size="mini"
:placeholder="$t('Log.link_id_msg')"
/>
</el-form-item>
<el-form-item :label="$t('Log.time')">
<el-date-picker
v-model="query.createTime"
type="datetimerange"
:range-separator="$t('Log.to')"
:start-placeholder="$t('Log.start_time')"
:end-placeholder="$t('Log.end_time')"
/>
</el-form-item>
<el-form-item label="压缩查看" prop="isRequest">
<el-switch
v-model="compressedView"
active-color="#409EFF"
inactive-color="#F56C6C"
:active-value="true"
:inactive-valu="false"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="mini" @click="queryData">
{{ $t('common.Query') }}
</el-button>
</el-form-item>
</el-form>
</div>
<!--表格渲染-->
<el-card shadow="hover" style="width: 100%; height: 620px; overflow-y: auto" class="log-warpper">
<div style="width: 100%">
<div v-for="(log, index) in logs" :key="index">
<div v-if="compressedView" style="margin-bottom: 5px; font-size: 16px;">
<span style="color: #000000;">{{ timestampToDate(log.time) }}</span> -
<span style="color: #000569;">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span> -
<span style="color: #111fc7;">{{ log.severity }}</span> -
<span style="color: #3498db;">{{ log.logger }}</span> -
<span style="color: #28b23a;">[{{ log.thread }}]</span> -
<span style="color: #d51313;">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span> -
<span
style="color: #000000;"
class="sql-message"
@click="handleCopySQL(log.message)"
>
{{ log.message }}
</span>
</div>
<div v-else class="log-detailed">
<el-row class="log-row">
<el-col :span="2" class="log-label">
时间
</el-col>
<el-col :span="22">
<span class="log-time">{{ timestampToDate(log.time) }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
host
</el-col>
<el-col :span="22">
<span class="log-host">{{ log.host }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
执行IP
</el-col>
<el-col :span="22">
<span class="log-ip">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
API路径
</el-col>
<el-col :span="22">
<span class="log-api">{{ log.apiPath === null ? '无' : log.apiPath }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
MDC标签
</el-col>
<el-col :span="22">
<span class="log-tag">{{ log.tag === null ? '未配置MDC' : log.tag }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
日志级别
</el-col>
<el-col :span="22">
<span :class="['log-level', getLevelClass(log.severity)]">{{ log.severity }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
类名
</el-col>
<el-col :span="22">
<span class="log-logger">{{ log.logger }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
线程名
</el-col>
<el-col :span="22">
<span class="log-thread">{{ log.thread }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
链路标识
</el-col>
<el-col :span="22">
<span class="log-trace">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span>
</el-col>
</el-row>
<el-row class="log-row">
<el-col :span="2" class="log-label">
日志信息
</el-col>
<el-col :span="22">
<span class="log-message sql-message" @click="handleCopySQL(log.message)">{{ transSQL(log.message) }}</span>
</el-col>
</el-row>
</div>
</div>
</div>
</el-card>
<!-- 分页 -->
<el-pagination
:page-sizes="[100, 500, 1000]"
:page-size.sync="query.size"
:total="query.total"
:current-page.sync="query.page"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import luceneOperation from '@/views/lucene/api/lucene'
export default {
name: 'LuceneLog',
data() {
return {
permission: {
add: ['admin', 'param:add'],
edit: ['admin', 'param:edit'],
del: ['admin', 'param:del']
},
levelOptions: [{
value: 'DEBUG',
label: 'DEBUG'
}, {
value: 'INFO',
label: 'INFO'
}, {
value: 'ERROR',
label: 'ERROR'
}, {
value: 'WARN',
label: 'WARN'
}],
rules: {},
logs: [],
tagList: [],
query: {
tlogTraceId: '',
message: '',
page: 0,
size: 100,
total: 0,
createTime: ''
},
compressedView: true
}
},
created() {
this.queryData()
this.getTagList()
},
methods: {
getLevelClass(level) {
if (!level) return 'level-unknown'
switch (level.toUpperCase()) {
case 'ERROR': return 'level-error'
case 'WARN': return 'level-warn'
case 'DEBUG': return 'level-debug'
case 'INFO': return 'level-info'
default: return 'level-unknown'
}
},
timestampToDate(timestamp) {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
// 添加毫秒处理强制3位数
const milliseconds = String(date.getMilliseconds()).padStart(3, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
},
// 处理SQL复制
handleCopySQL(message) {
// 1. 使用正则匹配SQL语句
const sqlMatch = message.match(/\[ SQL: ([^\]]+)\s*\]/)
if (sqlMatch && sqlMatch[1]) {
const sql = sqlMatch[1].trim()
this.copyToClipboard(sql)
} else {
this.$message.warning('未检测到可复制的SQL语句')
}
},
transSQL(message) {
const sqlMatch = message.match(/\[ SQL: ([^\]]+)\s*\]/)
// 兼容性更好的写法
if (!sqlMatch || !sqlMatch[1]) {
return message
}
const rawSQL = sqlMatch[1].trim()
return rawSQL
},
// 复制文本到剪贴板
copyToClipboard(text) {
// 现代浏览器方法
if (navigator.clipboard) {
navigator.clipboard.writeText(text)
.then(() => this.showSuccess())
.catch(err => this.showError(err))
}
// 兼容旧浏览器
else {
const textarea = document.createElement('textarea')
textarea.value = text
textarea.style.position = 'fixed'
document.body.appendChild(textarea)
textarea.select()
try {
const success = document.execCommand('copy')
success ? this.showSuccess() : this.showError()
} catch (err) {
this.showError(err)
}
document.body.removeChild(textarea)
}
},
// 显示成功提示
showSuccess() {
this.$message({
message: 'SQL已复制到剪贴板',
type: 'success',
duration: 2000
})
},
// 显示错误提示
showError(err) {
console.error('复制失败:', err)
this.$message.error('复制失败,请手动复制')
},
handleSizeChange(val) {
this.query.size = val
this.queryData()
},
handleCurrentChange(val) {
this.query.page = val
this.queryData()
},
getTagList() {
luceneOperation.getTagName().then(res => {
this.tagList = res
})
},
queryData() {
if (this.query.createTime) {
this.query.startTime = this.query.createTime[0]
this.query.endTime = this.query.createTime[1]
}
luceneOperation.getLogData(this.query).then(res => {
console.log(res)
this.logs = res.content
this.query.total = res.totalElements
})
}
}
}
</script>
<style scoped>
/* 详细视图样式 */
.log-detailed {
padding: 10px;
border: 1px solid #EBEEF5;
border-radius: 4px;
background-color: #fafafa;
}
.log-row {
margin-bottom: 5px;
line-height: 22px;
}
.log-label {
font-weight: 600;
color: #606266;
text-align: right;
padding-right: 12px;
}
/* 日志元素样式 */
.log-time {
color: #303133;
font-weight: bold;
}
.log-ip {
color: #000569;
padding: 2px 4px;
background-color: #f0f9ff;
border-radius: 3px;
}
.log-level {
font-weight: bold;
padding: 2px 6px;
border-radius: 3px;
}
.level-error .log-level,
.log-level.level-error {
background-color: #fef0f0;
color: #F56C6C;
}
.level-warn .log-level,
.log-level.level-warn {
background-color: #fdf6ec;
color: #E6A23C;
}
.level-info .log-level,
.log-level.level-info {
background-color: #ecf5ff;
color: #409EFF;
}
.level-debug .log-level,
.log-level.level-debug {
background-color: #f0f9eb;
color: #67C23A;
}
.log-logger {
color: #3498db;
font-family: monospace;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-thread {
color: #28b23a;
font-family: monospace;
}
.log-trace {
color: #d51313;
font-family: monospace;
background-color: #fff7f7;
padding: 2px 4px;
border-radius: 3px;
}
.log-host {
color: #048815;
}
.log-api {
color: #8c6a03;
font-family: monospace;
}
.log-tag {
color: #d20118;
}
.log-message {
color: #000000;
display: block;
margin-top: 4px;
padding: 6px;
background-color: #f8f8f8;
border-radius: 3px;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
}
/* SQL语句样式 */
.sql-message {
cursor: pointer;
transition: all 0.3s;
position: relative;
background: #f4faff;
color: #2d8cf0;
font-weight: bold;
border-left: 3px solid #2d8cf0;
transition: background 0.2s;
}
.sql-message:hover {
background-color: #ecf5ff;
box-shadow: 0 0 6px rgba(32, 160, 255, 0.3);
}
.sql-message:hover::after {
content: "点击复制SQL";
position: absolute;
right: 10px;
top: 3px;
font-size: 12px;
color: #409EFF;
background-color: #ecf5ff;
padding: 2px 5px;
border-radius: 3px;
}
.sql-message.copied {
background: #e1f3d8;
color: #67c23a;
}
</style>