xiazhengtongxue
2026-03-27 d24121c2267e892e4fea2fad777e42c51c805c88
ÏîÄ¿´úÂë/WMS/WIDESEA_WMSClient/src/views/system/txt_log.vue
@@ -2,16 +2,21 @@
    <div class="mian_log">
        <div class="log_tree">
            <div class="log_search">
                <el-input v-model.lazy="filterText" placeholder="请输入查询关键字" clearable />
                <el-input v-model.lazy="filterText" placeholder="请输入目录名称" clearable />
                <el-button type="success" @click="ReloadTree()">刷 æ–°</el-button>
            </div>
            <el-tree class="log_el_tree" ref="treeRef" :data="treeData" :props="treeProps" :filter-node-method="treeFilter">
            <!-- æ·»åŠ æ ‘èŠ‚ç‚¹æœç´¢ç»“æžœæç¤º -->
            <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>{{ node.label }}</div>
                        <!-- é«˜äº®åŒ¹é…çš„关键字 -->
                        <div v-html="highlightText(node.label)"></div>
                        <div style="padding-left: 10px;" v-if="isFile(data)">
                            <a href="javascript:;" @click="GetContent(data)"> æ‰“å¼€ </a>
                            <!-- ||<a href="javascript:;" @click="DonwLog(data)"> ä¸‹è½½ </a> -->
                        </div>
                    </div>
                </template>
@@ -27,11 +32,26 @@
                <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)" style="white-space: pre-wrap;word-break: break-all">{{
                content }}</div>
            <!-- ä¿®æ”¹å†…容显示区域,支持高亮 -->
            <div ref="showdata" @scroll="handleScroll($event)" class="content-display"
                v-html="highlightContent"></div>
        </div>
    </div>
</template>
@@ -56,12 +76,21 @@
            content: "",
            isClick: false,
            keepReadTimer: null
            keepReadTimer: null,
            // æ–°å¢žæœç´¢ç›¸å…³å±žæ€§
            contentSearchText: "",
            searchResults: [],
            currentMatchIndex: -1,
            searchHighlights: [],
            filteredCount: null,
            originalContent: "", // ä¿å­˜åŽŸå§‹å†…å®¹ç”¨äºŽæœç´¢
        }
    },
    watch: {
        filterText(val) {
            this.$refs.treeRef.filter(val)
            this.updateFilteredCount()
        },
        keepRead(val) {
            if (val) {
@@ -75,9 +104,14 @@
                        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();
                                }
                            })
                        })
                    }
@@ -85,13 +119,159 @@
            } 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.includes(value)
            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;
@@ -118,6 +298,7 @@
            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) {
@@ -140,8 +321,12 @@
            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 () {
@@ -170,8 +355,13 @@
                        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();
                        }
                    })
                })
            }
@@ -185,6 +375,7 @@
            this.http.post("/api/Sys_Log/GetLogList", {}, "加载目录中……")
                .then(res => {
                    this.treeData = res.data.dirs;
                    this.updateFilteredCount();
                })
        },
        GetSize(size) {
@@ -233,12 +424,17 @@
    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;
@@ -252,12 +448,10 @@
}
.treeItem::-webkit-scrollbar-thumb {
    /* å®šä¹‰æ»šåŠ¨æ¡æ»‘å—é¢œè‰² */
    background: #ccc;
}
.treeItem::-webkit-scrollbar-thumb:hover {
    /* å®šä¹‰æ»šåŠ¨æ¡æ»‘å—æ‚¬åœé¢œè‰² */
    background: orange;
}
@@ -283,15 +477,46 @@
    color: #bbb;
}
.log_content>div:last-child {
.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;
}
</style>
: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>