后端返回数据:
前端需展示:
1、正常解析源码,每行增加行号
2、匹配的行高亮显示
3、初始化定位到第一次高亮的行
4、垂直滚动时,行数和对应的代码同时滚动;水平滚动时,只滚动右侧代码部分
实现代码:
<template>
<div class="source-code" v-loading="isLoading">
<pre ref="sourceCode">{{ sourceCode }}</pre>
<div class="index">
<div ref="index">
<span v-for="item in list" :key="item.index">{{ item.index }}</span>
</div>
</div>
<div class="content" ref="content">
<div>
<span :class="`code${item.index}`" v-for="item in list" :key="item.index"><i :class="{ isMark: item.isMark }">{{ item.content }}</i></span>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash';
export default {
name: 'pre-html',
props: {
sourceCode: { // 源码
type: String,
default: '',
},
matchLinesJson: { // 匹配上的行
type: Array,
default: () => [],
},
coverage: { // 匹配程度(0-100)
type: Number,
default: -1,
}
},
data() {
return {
list: [],
isLoading: true,
firstMarkedLine: -1,
}
},
mounted() {
this.init();
this.$refs.content.addEventListener('scroll', this.onScroll);
},
beforeDestroy() {
this.$refs.content.removeEventListener('scroll', this.onScroll);
},
methods: {
init() {
const el = this.$refs.sourceCode;
const content = el.textContent;
// 13回车、10换行(经测试只用解析换行)
const linesArray = content.split(String.fromCharCode(10));
const matchLines = this.matchLinesJson.map(d => [d.startingMatchLine, d.endingMatchLine]);
this.list = linesArray.map((d, i) => {
const isMark = this.coverage === 100 ? true : matchLines.some(c => (i + 1) >= c[0] && (i + 1) <= c[1]);
if (isMark && this.firstMarkedLine < 0) {
this.firstMarkedLine = i + 1;
}
return {
content: d,
index: i + 1,
isMark,
};
});
this.isLoading = false;
if (this.firstMarkedLine <= 1) return;
this.$nextTick(() => {
const cel = document.querySelector(`.code${this.firstMarkedLine}`);
const pEl = this.$refs.content;
if (!cel || !pEl) return;
const t = cel.offsetTop - 10;
if (t > 10) {
pEl.scrollTop = t;
}
});
},
onScroll() {
return _.throttle(() => {
const st = (this.$refs.content && this.$refs.content.scrollTop) || 0;
if (!this.$refs.index) return;
this.$refs.index.style.marginTop = -st + 'px';
}, 50)();
},
},
}
</script>
<style lang="scss" scoped>
.source-code {
max-height: 300px;
overflow: hidden;
display: flex;
position: relative;
pre {
position: fixed;
visibility: hidden;
}
.index {
flex: none;
overflow: hidden;
> div {
min-width: 30px;
padding: 12px 8px;
border-right: 1px solid #eee;
}
span {
display: block;
line-height: 20px;
font-size: 12px;
height: 20px;
color: #999;
}
}
.content {
flex: 1;
width: 0;
overflow: auto;
> div {
padding: 12px 8px;
width: 100%;
}
span {
display: block;
padding: 0 4px;
white-space: pre;
line-height: 20px;
font-size: 12px;
height: 20px;
color: #333;
}
i {
font-style: normal;
min-width: 1px;
height: 100%;
display: inline-block;
vertical-align: bottom;
&.isMark {
background: #FFE5D1;
}
}
}
}
</style>