<template>
|
<div class="mian_log">
|
<div class="log_tree">
|
<div class="log_search">
|
<el-input v-model.lazy="filterText" placeholder="请输入目录名称" clearable />
|
<el-button type="success" @click="ReloadTree()">刷 新</el-button>
|
</div>
|
<!-- 添加树节点搜索结果提示 -->
|
<div class="search-tip" v-if="filterText && filteredCount !== null">
|
找到 {{ filteredCount }} 个匹配的目录
|
</div>
|
<el-tree class="log_el_tree" ref="treeRef" :data="treeData" :props="treeProps"
|
:filter-node-method="treeFilter" node-key="dirPath" highlight-current>
|
<template #default="{ node, data }">
|
<div class="treeItem">
|
<!-- 高亮匹配的关键字 -->
|
<div v-html="highlightText(node.label)"></div>
|
<div style="padding-left: 10px;" v-if="isFile(data)">
|
<a href="javascript:;" @click="GetContent(data)"> 打开 </a>
|
</div>
|
</div>
|
</template>
|
</el-tree>
|
</div>
|
|
<div class="log_line"></div>
|
|
<div class="log_content">
|
<div>
|
<h2>{{ this.fileName }}</h2>
|
<div class="log_size">{{ GetSize() }}</div>
|
<el-checkbox v-model="keepRead">保持读取</el-checkbox>
|
</div>
|
|
<!-- 添加内容搜索栏 -->
|
<div class="content-search" v-if="content">
|
<el-input v-model="contentSearchText" placeholder="在日志中搜索..." size="small" clearable
|
@keyup.enter="searchInContent" />
|
<el-button type="primary" size="small" @click="searchInContent">搜索</el-button>
|
<span v-if="searchResults.length > 0" class="search-info">
|
{{ currentMatchIndex + 1 }}/{{ searchResults.length }}
|
</span>
|
<el-button v-if="searchResults.length > 0" type="info" size="small"
|
@click="prevMatch">上一处</el-button>
|
<el-button v-if="searchResults.length > 0" type="info" size="small"
|
@click="nextMatch">下一处</el-button>
|
</div>
|
|
<div ref="pre" v-if="content">
|
<el-progress :percentage="percentage" @click="ChangePercent($event)" color="#f56c6c" />
|
</div>
|
<!-- 修改内容显示区域,支持高亮 -->
|
<div ref="showdata" @scroll="handleScroll($event)" class="content-display"
|
v-html="highlightContent"></div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
export default ({
|
data() {
|
return {
|
filterText: "",
|
treeData: [],
|
treeProps: {
|
children: 'dirs',
|
label: 'dirName',
|
},
|
keepRead: false,
|
|
percentage: 100,
|
topStartPos: 0,
|
fileSize: "",
|
filePath: "",
|
fileName: "",
|
content: "",
|
isClick: false,
|
|
keepReadTimer: null,
|
|
// 新增搜索相关属性
|
contentSearchText: "",
|
searchResults: [],
|
currentMatchIndex: -1,
|
searchHighlights: [],
|
filteredCount: null,
|
originalContent: "", // 保存原始内容用于搜索
|
}
|
},
|
watch: {
|
filterText(val) {
|
this.$refs.treeRef.filter(val)
|
this.updateFilteredCount()
|
},
|
keepRead(val) {
|
if (val) {
|
this.keepReadTimer = setInterval(() => {
|
if (this.filePath) {
|
var parm = {
|
path: this.filePath,
|
percent: 100,
|
maxsize_KB: 10
|
}
|
this.percentage = 100;
|
this.http.post('/api/Sys_Log/GetLogData', parm).then((res) => {
|
this.content = res.data.content;
|
this.originalContent = res.data.content;
|
this.topStartPos = res.data.startIndex;
|
this.$nextTick(function () {
|
this.$refs.showdata.scrollTo(0, this.$refs.showdata.scrollHeight)
|
// 重新应用搜索高亮
|
if (this.contentSearchText) {
|
this.searchInContent();
|
}
|
})
|
})
|
}
|
}, 1500);
|
} else {
|
clearInterval(this.keepReadTimer);
|
}
|
},
|
content(newVal) {
|
this.originalContent = newVal;
|
if (this.contentSearchText) {
|
this.searchInContent();
|
} else {
|
this.clearHighlights();
|
}
|
}
|
},
|
computed: {
|
// 计算高亮后的内容
|
highlightContent() {
|
if (!this.content || !this.contentSearchText || this.searchResults.length === 0) {
|
return this.content;
|
}
|
|
let highlightedContent = this.content;
|
const searchText = this.contentSearchText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
const regex = new RegExp(`(${searchText})`, 'gi');
|
|
// 使用全局替换添加高亮标记
|
highlightedContent = highlightedContent.replace(regex, (match, p1, offset) => {
|
// 检查是否当前匹配项
|
const isCurrentMatch = this.searchResults.some((result, index) =>
|
result.index === offset && index === this.currentMatchIndex
|
);
|
|
if (isCurrentMatch) {
|
return `<span class="highlight-current">${match}</span>`;
|
} else {
|
return `<span class="highlight">${match}</span>`;
|
}
|
});
|
|
return highlightedContent;
|
}
|
},
|
methods: {
|
// 树节点过滤方法
|
treeFilter(value, data) {
|
if (!value) return true
|
return data.dirName.toLowerCase().includes(value.toLowerCase())
|
},
|
|
// 更新过滤计数
|
updateFilteredCount() {
|
if (!this.filterText) {
|
this.filteredCount = null;
|
return;
|
}
|
|
// 递归计算匹配的节点数
|
const countMatches = (nodes) => {
|
let count = 0;
|
for (const node of nodes) {
|
if (node.dirName.toLowerCase().includes(this.filterText.toLowerCase())) {
|
count++;
|
}
|
if (node.dirs && node.dirs.length) {
|
count += countMatches(node.dirs);
|
}
|
}
|
return count;
|
};
|
|
this.filteredCount = countMatches(this.treeData);
|
},
|
|
// 高亮树节点中的文本
|
highlightText(text) {
|
if (!this.filterText || !text) return text;
|
|
const index = text.toLowerCase().indexOf(this.filterText.toLowerCase());
|
if (index === -1) return text;
|
|
const before = text.substring(0, index);
|
const match = text.substring(index, index + this.filterText.length);
|
const after = text.substring(index + this.filterText.length);
|
|
return `${before}<span class="highlight">${match}</span>${after}`;
|
},
|
|
// 在内容中搜索
|
searchInContent() {
|
if (!this.contentSearchText || !this.content) {
|
this.clearHighlights();
|
return;
|
}
|
|
const searchText = this.contentSearchText.toLowerCase();
|
const content = this.content;
|
|
// 查找所有匹配项的位置
|
const results = [];
|
let index = content.toLowerCase().indexOf(searchText);
|
|
while (index !== -1) {
|
results.push({
|
index: index,
|
length: this.contentSearchText.length
|
});
|
index = content.toLowerCase().indexOf(searchText, index + 1);
|
}
|
|
this.searchResults = results;
|
|
if (results.length > 0) {
|
this.currentMatchIndex = 0;
|
this.scrollToMatch(this.currentMatchIndex);
|
} else {
|
this.currentMatchIndex = -1;
|
this.$message.info('未找到匹配内容');
|
}
|
},
|
|
// 滚动到指定匹配项
|
scrollToMatch(matchIndex) {
|
if (matchIndex < 0 || matchIndex >= this.searchResults.length) return;
|
|
const match = this.searchResults[matchIndex];
|
const preContent = this.content.substring(0, match.index);
|
const lines = preContent.split('\n');
|
|
// 估计滚动位置(简化版)
|
const lineHeight = 20; // 假设每行高度
|
const estimatedScrollTop = lines.length * lineHeight;
|
|
this.$nextTick(() => {
|
this.$refs.showdata.scrollTop = estimatedScrollTop - 100;
|
});
|
},
|
|
// 上一处匹配
|
prevMatch() {
|
if (this.searchResults.length === 0) return;
|
this.currentMatchIndex = (this.currentMatchIndex - 1 + this.searchResults.length) % this.searchResults.length;
|
this.scrollToMatch(this.currentMatchIndex);
|
},
|
|
// 下一处匹配
|
nextMatch() {
|
if (this.searchResults.length === 0) return;
|
this.currentMatchIndex = (this.currentMatchIndex + 1) % this.searchResults.length;
|
this.scrollToMatch(this.currentMatchIndex);
|
},
|
|
// 清除高亮
|
clearHighlights() {
|
this.searchResults = [];
|
this.currentMatchIndex = -1;
|
},
|
|
isFile(data) {
|
if (data.dirPath) {
|
return data.dirPath.indexOf(".log") != -1 || data.dirPath.indexOf(".txt") != -1;
|
}
|
return false;
|
},
|
ChangePercent(event) {
|
var bar = window.document.getElementsByClassName('el-progress-bar')[0]
|
var res = event.offsetX / bar.offsetWidth * 100;
|
var percent = parseFloat(res.toFixed(2));
|
if (percent < 1) {
|
percent = 0;
|
}
|
if (percent > 99) {
|
percent = 100;
|
}
|
this.percentage = percent;
|
var parm = {
|
path: this.filePath,
|
percent: this.percentage,
|
maxsize_KB: 20
|
}
|
|
this.http.post('/api/Sys_Log/GetLogData', parm).then((res) => {
|
this.isClick = true;
|
this.content = res.data.content;
|
this.originalContent = res.data.content;
|
this.topStartPos = res.data.startIndex;
|
this.$nextTick(function () {
|
if (percent == 100) {
|
this.$refs.showdata.scrollTo(0, this.$refs.showdata.scrollHeight)
|
} else {
|
this.$refs.showdata.scrollTo(0, 1)
|
}
|
})
|
setTimeout(() => {
|
this.isClick = false;
|
}, 1000)
|
})
|
},
|
GetContent(data) {
|
var parm = {
|
path: data.dirPath,
|
percent: 100,
|
maxsize_KB: 20
|
}
|
this.filePath = data.dirPath;
|
this.fileName = data.dirName;
|
this.percentage = parm.percent;
|
this.contentSearchText = ""; // 清空搜索
|
this.clearHighlights();
|
|
this.http.post('/api/Sys_Log/GetLogData', parm).then((res) => {
|
this.content = res.data.content;
|
this.originalContent = res.data.content;
|
this.topStartPos = res.data.startIndex;
|
this.fileSize = res.data.len;
|
this.$nextTick(function () {
|
this.$refs.showdata.scrollTo(0, this.$refs.showdata.scrollHeight);
|
})
|
})
|
},
|
handleScroll() {
|
const scrollTop = this.$refs.showdata.scrollTop;
|
if (scrollTop == 0 && !this.isClick) {
|
if (this.topStartPos == 0) {
|
this.percentage = 0;
|
return;
|
}
|
var parm = {
|
path: this.filePath,
|
maxsize_KB: 10,
|
topStartPos: this.topStartPos
|
}
|
this.http.post('/api/Sys_Log/GetLogData', parm).then((res) => {
|
this.topStartPos = res.data.startIndex;
|
var per = res.data.startIndex / res.data.len * 100;
|
|
this.percentage = parseFloat(per.toFixed(2));
|
if (this.percentage == 0 && res.data.startIndex != 0) {
|
this.percentage = 0.1;
|
}
|
this.content = res.data.content + this.content;
|
this.originalContent = res.data.content + this.originalContent;
|
this.$nextTick(function () {
|
this.$refs.showdata.scrollTo(0, 1)
|
// 重新搜索
|
if (this.contentSearchText) {
|
this.searchInContent();
|
}
|
})
|
})
|
}
|
},
|
DonwLog(data) {
|
var url = this.http.axios.defaults.baseURL + 'api/Sys_Log/DownLog';
|
url += "?filePath=" + data.dirPath;
|
window.location.href = encodeURI(url);
|
},
|
ReloadTree() {
|
this.http.post("/api/Sys_Log/GetLogList", {}, "加载目录中……")
|
.then(res => {
|
this.treeData = res.data.dirs;
|
this.updateFilteredCount();
|
})
|
},
|
GetSize(size) {
|
var size = this.fileSize;
|
if (!size) {
|
return ''
|
} else if (size < 1024) {
|
size = size + "B"
|
} else if (size < 1024 * 1024) {
|
size = (size / 1024).toFixed(2) + "KB"
|
} else {
|
size = (size / 1024 / 1024).toFixed(2) + "MB"
|
}
|
return "文件大小:" + size;
|
}
|
},
|
mounted() {
|
this.http.post("/api/Sys_Log/GetLogList", {}, "加载目录中……")
|
.then(res => {
|
this.treeData = res.data.dirs;
|
})
|
}
|
})
|
</script>
|
|
<style scoped>
|
.mian_log {
|
height: calc(100vh - 96px);
|
padding: 10px 10px;
|
display: flex;
|
width: 100%;
|
}
|
|
.log_tree {
|
width: 420px;
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.log_search {
|
display: flex;
|
}
|
|
.log_search :last-child {
|
margin-left: 5px;
|
}
|
|
.search-tip {
|
font-size: 12px;
|
color: #909399;
|
padding: 5px 0;
|
}
|
|
.log_el_tree {
|
width: 100%;
|
overflow-x: auto;
|
flex: 1;
|
}
|
|
.treeItem {
|
overflow-x: auto;
|
flex: 1;
|
display: flex;
|
justify-content: space-between;
|
}
|
|
.treeItem::-webkit-scrollbar {
|
height: 5px;
|
}
|
|
.treeItem::-webkit-scrollbar-thumb {
|
background: #ccc;
|
}
|
|
.treeItem::-webkit-scrollbar-thumb:hover {
|
background: orange;
|
}
|
|
.log_line {
|
margin: 5px 16px;
|
border-left: 2px dashed orange;
|
}
|
|
.log_content {
|
flex: 1;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.log_content>div:first-child {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
|
.log_size {
|
font-size: 13px;
|
color: #bbb;
|
}
|
|
.content-search {
|
display: flex;
|
gap: 5px;
|
align-items: center;
|
padding: 10px 0;
|
border-bottom: 1px solid #eee;
|
}
|
|
.search-info {
|
font-size: 12px;
|
color: #666;
|
margin: 0 5px;
|
}
|
|
.content-display {
|
flex: 1;
|
overflow-y: scroll;
|
margin-top: 5px;
|
padding-top: 5px;
|
border-top: 1px solid black;
|
white-space: pre-wrap;
|
word-break: break-all;
|
}
|
|
a {
|
color: blue;
|
}
|
|
:deep(.highlight) {
|
background-color: #ffeb3b;
|
color: #000;
|
padding: 0;
|
border-radius: 2px;
|
}
|
|
:deep(.highlight-current) {
|
background-color: #ff9800;
|
color: #000;
|
padding: 0;
|
border-radius: 2px;
|
font-weight: bold;
|
}
|
</style>
|