【前端Vue】如何在log-viewer组件中添加搜索定位功能
**log-viewer组件合集**
【前端Vue】如何优雅地展示带行号的日志文件或文本内容(log-viewer组件的使用)
【前端Vue】使用log-viewer组件时的踩坑记录
【前端Vue】log-viewer组件的使用技巧
【前端Vue】如何在log-viewer组件中添加搜索定位功能
首先在包含log-viewer组件的渲染标签中加入搜索框和一些操作按钮,支持通过搜索框输入和清空搜索内容,支持点击操作按钮上下定位搜索结果,并且可以显示搜索结果数量和当前所处结果的序号。输入框增加了clearable就能够直接在输入框中使用内置的点击叉叉图标清空内容
<!-- 搜索控件 --><div class="log-search-container"><el-inputref="searchInput"v-model="searchText"placeholder="输入搜索关键词"size="mini"class="log-search-input"@keyup.enter.native="searchNext"@input="onSearchInput"clearable></el-input><div class="search-info" v-if="searchText">{{ searchCurrentIndex + 1 }} / {{ searchResults.length }}</div><el-button-group><el-buttonv-if="searchText && searchResults.length > 0"size="mini"icon="el-icon-arrow-up"@click="searchPrev"></el-button><el-buttonicon="el-icon-arrow-down"v-if="searchText && searchResults.length > 0"size="mini"@click="searchNext"></el-button></el-button-group></div></div><!-- 日志显示组件 --><log-viewerref="logViewerContainer"class="log-content":key="logviewViewerKey":log="searchText ? highlightedLogContent : logContent":loading="isFetchingLogs"/>
要在data()中定义几个参数用于搜索控件的使用:
searchText: '',searchResults: [],searchCurrentIndex: -1,searchOccurrences: [], // 存储搜索结果位置
随后定义匹配的搜索相关方法,搜索时会将搜索结果呈现黄色,点击操作按钮进行上下定位,定位到的结果显示为绿色
// 搜索相关方法onSearchInput() {if (this.searchText) {this.performSearch();} else {this.clearSearch();}},performSearch() {const content = this.logContent;if (!content || !this.searchText) {this.searchResults = [];this.searchCurrentIndex = -1;return;}// 查找所有匹配项const regex = new RegExp(this.escapeRegExp(this.searchText), 'gi');const matches = [];let match;while ((match = regex.exec(content)) !== null) {matches.push({index: match.index,text: match[0]});}this.searchResults = matches;if (matches.length > 0) {this.searchCurrentIndex = 0;this.scrollToSearchResult(0);} else {this.searchCurrentIndex = -1;}},searchNext() {if (this.searchResults.length === 0) return;this.searchCurrentIndex = (this.searchCurrentIndex + 1) % this.searchResults.length;this.scrollToSearchResult(this.searchCurrentIndex);},searchPrev() {if (this.searchResults.length === 0) return;this.searchCurrentIndex = (this.searchCurrentIndex - 1 + this.searchResults.length) % this.searchResults.length;this.scrollToSearchResult(this.searchCurrentIndex);},scrollToSearchResult(index) {if (this.searchResults.length === 0 || index < 0) return;// 更新当前索引以触发高亮更新this.searchCurrentIndex = index;// 获取当前显示的 log-viewer 组件const viewer = this.$refs.logViewerContainer;if (viewer && viewer.$refs && viewer.$refs.virturalList) {// 获取虚拟列表组件const virtualList = viewer.$refs.virturalList;// 获取内容并按行分割const content = this.logContent;const lines = content.split('\n');// 找到匹配项所在的行const matchPosition = this.searchResults[index].index;let lineNumber = 0;let charCount = 0;for (let i = 0; i < lines.length; i++) {const lineLength = lines[i].length + 1; // +1 for newline characterif (charCount + lineLength > matchPosition) {lineNumber = i;break;}charCount += lineLength;}// 滚动到指定行(添加偏移量使内容居中)this.$nextTick(() => {// 使用 start 属性设置滚动位置// 虚拟列表通过 start 属性控制显示的起始行if (virtualList && typeof virtualList.scrollToIndex === 'function') {// 直接滚动到目标行,不减去偏移量virtualList.scrollToIndex(lineNumber);} else if (virtualList && typeof virtualList.$el.scrollTo === 'function') {// 备选方案:使用原生滚动方法,修正计算方式const lineHeight = 20; // 假设每行高度为20px// 确保滚动到正确位置,减去一些像素使目标行在视图中央const targetScrollTop = Math.max(0, lineNumber * lineHeight - (virtualList.$el.clientHeight / 2));virtualList.$el.scrollTo({ top: targetScrollTop, behavior: 'smooth' });}});} else {// 如果无法直接控制滚动,至少显示提示信息this.$message.info(`已定位到第 ${index + 1} 个匹配项,共找到 ${this.searchResults.length} 个匹配项`);}},clearSearch() {this.searchText = '';this.searchResults = [];this.searchCurrentIndex = -1;},// 使用 ANSI 转义序列添加高亮(log-viewer组件只支持ANSI 转义)highlightText(content, searchText, currentIndex = -1) {if (!searchText) return content;// 分割成行以便处理const lines = content.split('\n');let globalIndex = 0; // 全局匹配索引// 使用正则匹配搜索词const regex = new RegExp(`(${this.escapeRegExp(searchText)})`, 'gi');const highlightedLines = lines.map(line => {return line.replace(regex, (match, ...args) => {const currentGlobalIndex = globalIndex;globalIndex++;// 如果是当前选中的搜索结果,使用不同的颜色if (currentGlobalIndex === currentIndex) {return `\x1b[42m${match}\x1b[0m`; // 当前结果使用绿色背景} else {return `\x1b[103m${match}\x1b[0m`; // 其余使用亮黄色背景}});});return highlightedLines.join('\n');},escapeRegExp(string) {return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');}},
};
随后在computed计算属性中增加对高亮文字的处理,将搜索框的内容状态与内容显示通过计算属性互相绑定起来
// 高亮显示的日志内容highlightedLogContent() {if (!this.searchText || this.searchResults.length === 0) {return this.logContent;}return this.highlightText(this.logContent, this.searchText, this.searchCurrentIndex);},