fix 日志组件采用虚拟滚动渲染,避免日志过多浏览器卡死

This commit is contained in:
bwcx_jzy 2023-06-09 13:35:35 +08:00
parent cb6503511a
commit 693f184426
No known key found for this signature in database
GPG Key ID: E187D6E9DDDE8C53
5 changed files with 183 additions and 40 deletions

View File

@ -9,6 +9,7 @@
1. 【server】修复 页面关闭 docker 终端未主动关闭后台终端进程问题
2. 【server】优化 docker 终端页面缓冲区大小自动适应
3. 【server】优化 项目列表可以查看项目日志(避免控制台卡顿无法操作下载日志)(感谢@阿超)
4. 【server】优化 日志组件采用虚拟滚动渲染,避免日志过多浏览器卡死
------

View File

@ -26,11 +26,12 @@
"vue-codemirror": "^4.0.6",
"vue-json-viewer": "^2.2.20",
"vue-router": "^3.5.2",
"vue-virtual-scroller": "^1.1.2",
"vuedraggable": "^2.24.3",
"vuex": "^3.5.1",
"xterm": "^4.13.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.5.0",
"vuedraggable": "^2.24.3"
"xterm-addon-fit": "^0.5.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.13",
@ -43,9 +44,9 @@
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.15.1",
"prettier": "^2.3.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"prettier": "^2.3.2",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {

View File

@ -10,11 +10,11 @@
<a-col v-if="this.extendBar" style="padding-left: 10px">
<a-space>
<a-tooltip title="清空当前缓冲区内容">
<a-button type="link" style="padding: 0" @click="clearLogCache" icon="delete"><span style="margin-left: 2px">清空</span></a-button>
<a-button type="primary" size="small" @click="clearLogCache" icon="delete">清空</a-button>
</a-tooltip>
<a-tooltip title="内容超过边界自动换行">
<!-- <a-tooltip title="内容超过边界自动换行">
<a-switch v-model="temp.wordBreak" checked-children="自动换行" un-checked-children="不换行" @change="onChange" />
</a-tooltip>
</a-tooltip> -->
<a-tooltip title="有新内容后是否自动滚动到底部">
<a-switch v-model="temp.logScroll" checked-children="自动滚动" un-checked-children="不滚动" @change="onChange" />
</a-tooltip>

View File

@ -1,21 +1,43 @@
<template>
<div>
<code-editor
ref="codemirror"
:style="`height:${this.height}`"
v-model="showContext"
:options="{ theme: 'panda-syntax', mode: 'verilog', cursorBlinkRate: -1, tabSize: 2, readOnly: true, styleActiveLine: true, lineWrapping: this.config.wordBreak }"
></code-editor>
<div class="wrapper">
<RecycleScroller class="scroller" :id="uniqueId" :style="`min-height:${height};height:${height}`" key-field="id" :items="showList" :item-size="itemHeight" :emitUpdate="false">
<template v-slot="{ index, item }">
<div class="item">
<template v-if="!item.warp">
<span class="linenumber">{{ index + 1 }}</span> {{ item.text }} &nbsp;&nbsp;
</template>
</div>
<!-- <code-editor
ref="codemirror"
v-model="item.text"
:options="{
theme: 'panda-syntax',
mode: 'verilog',
// maxHighlightLength: 5,
viewportMargin: 1,
cursorBlinkRate: -1,
tabSize: 2,
readOnly: true,
styleActiveLine: true,
lineNumbers: true,
firstLineNumber: index + 1,
lineWrapping: config.wordBreak,
}"
></code-editor> -->
</template>
</RecycleScroller>
</div>
</template>
<script>
import ansiparse from "@/utils/parse-ansi";
import codeEditor from "@/components/codeEditor";
// import codeEditor from "@/components/codeEditor";
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
export default {
components: {
codeEditor,
// codeEditor,
RecycleScroller,
},
props: {
height: {
@ -33,29 +55,92 @@ export default {
return {
defText: "loading context...",
logContext: "",
dataArray: [],
idInc: 0,
visibleStartIndex: -1,
itemHeight: 24,
uniqueId: `component_${Math.random().toString(36).substring(2, 15)}`,
};
},
computed: {
wordBreak() {
this.changeBuffer();
// this.changeBuffer();
return this.config.wordBreak || false;
},
showContext: {
get() {
return this.logContext || this.defText;
},
set() {},
showList() {
const element = document.querySelector(`#${this.uniqueId}`);
let result = [...this.dataArray];
let warp = false;
if (element) {
const min = Math.ceil(element.clientHeight / this.itemHeight);
const le = min - result.length;
for (let i = 0; i < le; i++) {
result.push({
id: "system-warp-empty:" + i,
warp: true,
});
warp = true;
}
}
if (!warp) {
result = result.concat([
{
id: "system-warp-end:1",
warp: true,
},
{
id: "system-warp-end:2",
text: "",
warp: true,
},
]);
}
return result;
},
// showContext: {
// get() {
// return this.logContext || this.defText;
// },
// set() {},
// },
},
mounted() {},
methods: {
//
appendLine(data) {
if (!data) {
return;
scrollToBottom() {
const element = document.querySelector(`#${this.uniqueId}`);
if (element) {
// console.log(element, element.scrollHeight);
element.scrollTop = element.scrollHeight - element.clientHeight;
// this.scrollTo(element, element.scrollHeight - element.clientHeight, 500);
// element.scrollIntoView(false);
}
const dataArray = Array.isArray(data) ? data : [data];
this.logContext += dataArray
},
scrollTo(element, position) {
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function (cb) {
return setTimeout(cb, 10);
};
}
var scrollTop = element.scrollTop;
var step = function () {
var distance = position - scrollTop;
scrollTop = scrollTop + distance / 5;
if (Math.abs(distance) < 1) {
element.scrollTop = position;
} else {
element.scrollTop = scrollTop;
requestAnimationFrame(step);
}
};
step();
},
onUpdate(viewStartIndex, viewEndIndex, visibleStartIndex, visibleEndIndex) {
const tempArray = this.dataArray.slice(visibleStartIndex, visibleEndIndex);
this.logContext = tempArray
.map((item) => {
return item.text;
})
.map((item) => {
return (
// gitee isuess I657JR
@ -67,23 +152,79 @@ export default {
);
})
.join("");
this.visibleStartIndex = visibleStartIndex;
// console.log(this.dataArray.length, tempArray.length, visibleStartIndex, visibleEndIndex);
// console.log(this.logContext);
},
//
appendLine(data) {
if (!data) {
return;
}
const tempArray = (Array.isArray(data) ? data : [data]).map((item) => {
return {
text: ansiparse(item)
.map((ansiItem) => {
return ansiItem.text;
})
.join(""),
id: this.idInc++,
};
});
this.dataArray = [...this.dataArray, ...tempArray];
// console.log(this.dataArray);
if (this.config.logScroll) {
setTimeout(() => {
//
this.$nextTick(() => {
this.$refs?.codemirror?.scrollToBottom();
this.scrollToBottom(".scroller");
});
}, 500);
}
},
clearLogCache() {
this.logContext = "";
this.dataArray = [];
},
},
};
</script>
<style scoped></style>
<style scoped>
.wrapper {
/* overflow: hidden; */
/* position: absolute; */
/* top: 0; */
/* bottom: 0; */
/* left: 0; */
/* right: 0; */
}
.scroller {
height: 100%;
width: 100%;
font-family: Operator Mono, Source Code Pro, Menlo, Monaco, Consolas, Courier New, monospace;
position: relative;
<style></style>
overflow-y: scroll;
}
/deep/ .vue-recycle-scroller__item-wrapper {
background: #292a2b;
color: #ffb86c;
white-space: nowrap;
overflow-x: scroll;
overflow-y: hidden;
padding-bottom: 10px;
}
.item {
padding: 0px 6px;
}
.linenumber {
color: #e6e6e6;
padding: 0px 4px;
opacity: 0.6;
/* overflow: auto; */
/* white-space: nowrap; */
}
</style>

View File

@ -25,8 +25,8 @@
<a-dropdown>
<!-- <a type="link" class="ant-dropdown-link"> 更多<a-icon type="down" /> </a> -->
<a
class="ant-dropdown-link"
<a-button
size="small"
@click="
(e) => {
e.preventDefault();
@ -34,12 +34,12 @@
}
"
>
<a-tag>
文件大小: {{ project.logSize || "-" }}
<!-- 更多 -->
<a-icon type="fullscreen" />
</a-tag>
</a>
<!-- <a-tag> -->
日志大小: {{ project.logSize || "-" }}
<!-- 更多 -->
<a-icon type="fullscreen" />
<!-- </a-tag> -->
</a-button>
<!-- <a-menu slot="overlay">
<a-menu-item>
<a-button type="primary" size="small" :disabled="!project.logSize" @click="handleDownload">导出日志</a-button>