vue实现将word转换为HTML页面,并实现类似word的目录导航和关键字搜索跳转
<template>
<div class="content">
<div class="header">
<div class="title">
XXXXXX
</div>
<div class="search">
<a-button class="btn" @click="previous">
上一个
</a-button>
<a-input-search
v-model:value="keyword"
placeholder="请输入关键字"
class="search-area"
@search="onSearch()"
/>
<div v-if="matchNum > 0" style="height: 50px;line-height: 50px;padding: 4px;">{{ searchIndex + '/'+ matchNum}}</div>
<a-button class="btn" @click="next">
下一个
</a-button>
</div>
</div>
<div class="content-area">
<div class="left-Nav" id="left-Nav">
<!-- <div v-for="item in tocData">
<div style="font-size: 16px;cursor: pointer" @click="handleNodeClick(item.id,$event)" class="log-item">{{item.label}}</div>
<div v-for="a in item.children">
<div style="font-size: 16px;margin-left: 1em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(a.id,$event)" class="log-item">{{a.label}}</div>
<div v-for="b in a.children">
<div style="font-size: 16px;margin-left: 2em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(b.id,$event)" class="log-item">{{b.label}}</div>
</div>
</div>
</div> -->
<div style="width: 100%" class="nav">
<a-menu
:default-selected-keys="['1']"
:default-open-keys="['2']"
mode="inline"
:theme="theme"
:inline-collapsed="collapsed"
:selectedKeys="selectedKeys"
>
<template v-for="item in tocData">
<a-menu-item v-if="!item.children" :key="item.key">
<a-icon type="pie-chart" />
<span>{{ item.title }}</span>
</a-menu-item>
<sub-menu v-else :key="item.key + '1'" :menu-info="item" />
</template>
</a-menu>
</div>
</div>
<div class="right-content">
<div class="word-wrap">
<div id="wordView" ref = "aContent" v-html="wordText" v-if="wordText" />
</div>
</div>
</div>
</div>
</template>
<script>
import mammoth from "mammoth";
import { Menu } from "ant-design-vue";
const SubMenu = {
template: `
<a-sub-menu :key="menuInfo.key" v-bind="$props" v-on="$listeners">
<span slot="title">
<a-icon type="mail" /><span>{{ menuInfo.title }}</span>
</span>
<template v-for="item in menuInfo.children">
<a-menu-item v-if="!item.children" :key="item.key" @click=handleNodeClick(item.id)>
<a-icon type="pie-chart" />
<span>{{ item.title }}</span>
</a-menu-item>
<sub-menu v-else :key="item.key" :menu-info="item" />
</template>
</a-sub-menu>
`,
name: "SubMenu",
// must add isSubMenu: true
isSubMenu: true,
props: {
...Menu.SubMenu.props,
// Cannot overlap with properties within Menu.SubMenu.props
menuInfo: {
type: Object,
default: () => ({}),
},
},
methods:{
handleNodeClick(data) {
console.log("点击目录")
// 实现跳转锚点
let id = data
let tag = 'h-'+id
let anchorH = document.getElementById(tag).offsetTop // 得到h标签的位置
console.log("anchorH",anchorH)
this.changeHeight(anchorH)
// this.$emit('changeHeight', anchorH); // 我的页面滚动通过父组件控制,所以发射出去让父组件操作
},
changeHeight(record){
console.log("--------")
// let el = this.$refs.scroll
let el = document.getElementById("wordView")
// console.log("el",el)
// let cur = el.scrollTop // el-scrollbar获取页面当前位置的属性,定位也是通过改变这个
let cur = el.offsetTop
// console.log("cur",cur)
let flag = false
let step = 20
// 由于有可能是向上滑,有可能向下滑,所以需要考虑移动的正负
if (record < cur) {
step = -step;
flag = true
}else if (record == cur) {
return
}
// 设置动画
// 具体含义就是按照每8毫秒执行一次向上移动指定的步长,这样就是一个动画
var timer = setInterval(()=>{
if (flag) {
// 说明小于0
if (cur + step < record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
// el.scrollTop = record
window.scrollTo({ top: record, behavior: "smooth" });
clearInterval(timer)
}else {
// el.scrollTop = cur + step
window.scrollTo({ top: cur + step, behavior: "smooth" });
}
}else{
if (cur + step > record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
// el.scrollTop = record
window.scrollTo({ top: record, behavior: "smooth" });
clearInterval(timer)
}else {
// el.scrollTop = cur + step
window.scrollTo({ top: cur + step, behavior: "smooth" });
}
}
cur += step
},8)
},
}
};
export default{
components: {
"sub-menu": SubMenu,
},
data(){
return{
wordText: "",
wordURL: '/pro/XXX.docx',//文件地址
flag:true,
scrollTag:[],
selectedKeys:['0'],
maxum:0,
keyword:'',
highlightedText:'',
searchIndex:1,
anchorHlist:[], //搜索的元素所在的位置
matchNum:0, //匹配数
curIndex:0, //当前的index
}
},
created(){
this.getWordText();
setTimeout(()=>{
this.makeToc()
this.$forceUpdate()
},2000)
},
mounted(){
window.addEventListener('scroll',this.handleScroll,true)
setTimeout(()=>{
let values = []
console.log("this.maxum",this.maxum)
// let hTags = document.getElementsByTagName('h');
for (let i = 1;i <=this.maxum;i++) {
let tag = 'h-'+i
// console.log("tag",tag)
let anchorH = document.getElementById(tag).offsetTop
// console.log(anchorH)
values.push({id:i,height:anchorH})
}
this.scrollTag = values
// console.log("scrollTag",this.scrollTag)
},2100)
},
computed: {
// processedText() {
// const regex = new RegExp(this.keyword, 'gi'); // g表示全局匹配,i表示不区分大小写
// this.highlightedText = this.wordText.replace(regex, `<span class="highlight">${this.keyword}</span>`);
// return this.highlightedText;
// }
matchNumText(){
let num = this.matchNum
return num
}
},
updated() {
// 需要在data里面加上flag,只触发一次里面内容
// if (this.flag) {
// this.makeToc()
// this.flag = false
// }
// this.$emit('refreshScroll',values)
},
methods:{
getWordText() {
const xhr = new XMLHttpRequest();
xhr.open("get", this.wordURL, true);
console.log("======",xhr)
xhr.responseType = "arraybuffer";
xhr.onload = () => {
if (xhr.status == 200) {
mammoth.convertToHtml({ arrayBuffer: new Uint8Array(xhr.response) }).then((resultObject) => {
this.$nextTick(() => {
this.wordText = resultObject.value;
console.log("this.$refs",this.$refs)
// this.makeToc()
});
});
}
};
xhr.send();
},
onSearch(){
const regex1 = new RegExp(`<span class="highlight" style="background:yellow">`, 'gi') //删除上一次的样式
this.wordText = this.wordText.replace(regex1,"<span>")
this.searchIndex = 1
const regex = new RegExp(this.keyword, 'gi'); // g表示全局匹配,i表示不区分大小写
let wordText = this.wordText
let matches = this.wordText.match(regex)
// this.matchNum = matches !== null ? matches.length : 0
this.wordText = wordText.replace(regex, `<span class="highlight" style="background:yellow">${this.keyword}</span>`);
// 根据关键字查找匹配的元素
let mainContainer =document.querySelector("#wordView").querySelectorAll("p");
console.log("mainContainer.length",mainContainer.length,mainContainer[1].textContent)
// let anchorHlist = []
this.anchorHlist = []
for(let i =0;i<=mainContainer.length-1;i++){
if(mainContainer[i].textContent.match(regex) !== null){
let anchorH = mainContainer[i].offsetTop
this.anchorHlist.push(anchorH)
}
}
this.matchNum = this.anchorHlist.length
this.scrollToLocation(this.checkIndex);
console.log("mainContainer",mainContainer)
console.log('matchNum',this.matchNum,matches,this.anchorHlist)
},
scrollToLocation(index){
let height = this.anchorHlist[index]
console.log("下一个-------",height)
window.scrollTo({ top: height - 110, behavior: "smooth" });
// window.scrollTo({ top: cur + step, behavior: "smooth" });
},
next() {
this.searchIndex++;
if(this.searchIndex > this.matchNum) this.searchIndex = 1
this.scrollToLocation(this.searchIndex-1);
},
previous() {
if(this.searchIndex > 1){
this.searchIndex--;
}else{
this.searchIndex = 1
}
this.scrollToLocation(this.searchIndex-1);
},
// 将一个集合的数据变成一个树形的数据结构
toTree(data){
// 删除 所有 children,以防止多次调用
data.forEach(function (item) {
delete item.children;
});
// 将数据存储为 以 id 为 KEY 的 map 索引数据列
var map = {};
data.forEach(function (item) {
map[item.id] = item;
});
var val = [];
data.forEach(function (item) {
// 以当前遍历项的pid,去map对象中找到索引的id
var parent = map[item.p_id];
// 好绕啊,如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
//如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级
val.push(item);
}
});
return val;
},
/**
* 生成目录
* */
makeToc(){
// 获取所有的h标签,给他们加上id,同时创建符合toTree方法要求的对象
//{
// id:'',// 抛出id
// tag:'',// 抛出标签名称
// label:'',// 抛出标题
// p_id:'',// 抛出父级id
// }
// 定义参与目录生成的标签
const tocTags = ["H1","H2","H3","H4","H5","H6"];
// 目录树结果
const tocArr = [];
// 获取所有标题标签
const headDoms = Array.from(this.$refs.aContent.childNodes).filter(item => tocTags.includes(item.tagName));
// 遍历标题标签
headDoms.forEach((item,index,arr) => {
// 给标题添加id
item.id = `h-${index + 1}`;
// 获取当前节点前面的节点
let prevs = arr.filter((i,j) => j < index);
// 过滤前面的节点为合理节点
// 如 h3节点前 只能为 h1 h2 h3
prevs = prevs.filter(i => tocTags.filter((i,j) => j <= tocTags.findIndex(i => i == item.tagName)).includes(i.tagName));
// 对前面的节点进行排序,距离自身节点近的排在前面
// 如 div > p > span > img 当前为img
// 常规获取节点为 [div,p,span,img]
// 排序后获取节点为 [img,span,p,div]
prevs = prevs.sort((a,b) => -(a.id.replace('h-','')) - b.id.replace('h-',''));
// 查询距离自身节点最近的不同于当前标签的节点
const prev = prevs.find(i => i.tagName != item.tagName);
this.maxum = Math.max(this.maxum,index + 1)
tocArr.push({
id:index + 1,// 抛出id
key:index,
title:item.innerText,
tag:item.tagName,// 抛出标签名称
label:item.innerText,// 抛出标题
p_id:item.tagName == "H1" || prev == null ? 0 : Number(prev.id.replace("h-",'')),// 抛出父级id
})
})
// 使用上述方法生成树 最后在el-tree的data中使用 tocData即可
this.tocData = this.toTree(tocArr);
console.log(this.tocData)
},
changeHeight(record){
console("--------")
let el = this.$refs.scroll
let cur = el.scrollTop // el-scrollbar获取页面当前位置的属性,定位也是通过改变这个
let flag = false
let step = 20
// 由于有可能是向上滑,有可能向下滑,所以需要考虑移动的正负
if (record < cur) {
step = -step;
flag = true
}else if (record == cur) {
return
}
// 设置动画
// 具体含义就是按照每8毫秒执行一次向上移动指定的步长,这样就是一个动画
var timer = setInterval(()=>{
if (flag) {
// 说明小于0
if (cur + step < record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
el.scrollTop = record
clearInterval(timer)
}else {
el.scrollTop = cur + step
}
}else{
if (cur + step > record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
el.scrollTop = record
clearInterval(timer)
}else {
el.scrollTop = cur + step
}
}
cur += step
},8)
},
// 父组件
handleScroll(){
// let top = this.$refs.scroll.wrap.scrollTop
let top = document.getElementById("wordView").offsetTop
let len = this.scrollTag.length -1
for (let i = 0;i < len;i ++) {
// 判断当前页面的位置 应该属于哪个标签,我这里是标签下的内容没有结束,目录都是这个标签高亮
if (top >= this.scrollTag[i].height - 1 && top < this.scrollTag[i + 1].height) {
// -1 是我发现点击目录滑动的时候,会有<1 的误差,导致目录高亮不改变
// 让之变成
// this.$refs.detail.changeItem(i) // 通过这个方法,改变目录标签高亮
this.selectedKeys = [i]
}
}
// 特殊处理 第一个和最后一个
if (top< this.scrollTag[0].height ) this.selectedKeys = [i]
if (top >= this.scrollTag[len].height - 1 ) this.selectedKeys = [this.selectedKeys.length -1]
console.log("this.selectedKeys",this.selectedKeys)
},
}
}
</script>
<style lang="less">
.content {
width: 95%;
margin:0px auto;
border: 1px solid black;
font-size: 20px;
}
.header{
position: fixed;
top:0px;
left: 2.5%;
width: 95%;
height:100px;
border-bottom: 1px solid black;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background-color:aliceblue;
.title{
font-size: 40px;
font-weight: bold;
}
.search{
/* width: 500px; */
height: 50px;
display: flex;
justify-content: space-around;
flex-direction: row;
.btn{
width: 20%;
height: 50px;
border-radius: 15%;
background-color: #1890ff;
color: #fff;
}
.search-area{
width: 70%;
height: 50px;
margin: 0 10px;
}
}
}
.content-area{
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 100px;
.left-Nav{
position: fixed;
top:100px;
left:2.5%;
width: 19%;
padding:10px 30px;
.nav{
/* height: 100%;
position: relative; */
}
}
.right-content{
margin-left:20%;
width: 85%;
padding:10px 30px;
border-left: 1px solid black;
.word-wrap {
padding: 15px;
img {
width: 80%;
}
}
}
}
h1{
font-size:36px;
height: 40px;
line-height: 40px;
}
h2{
font-size:28px;
height: 40px;
line-height: 40px;
}
img{
width: 80% !important;
}
.ant-menu{
font-size: 20px !important;
}
/* .word-wrap {
padding: 15px;
img {
width: 100%;
}
} */
</style>
<template>
<div class="content">
<div class="header">
<div class="title">
指标管理平台
</div>
<div class="search">
<a-button class="btn" @click="previous">
上一个
</a-button>
<a-input-search
v-model:value="keyword"
placeholder="请输入关键字"
class="search-area"
@search="onSearch()"
/>
<div v-if="matchNum > 0" style="height: 50px;line-height: 50px;padding: 4px;">{{ searchIndex + '/'+ matchNum}}</div>
<a-button class="btn" @click="next">
下一个
</a-button>
</div>
</div>
<div class="content-area">
<div class="left-Nav" id="left-Nav">
<!-- <div v-for="item in tocData">
<div style="font-size: 16px;cursor: pointer" @click="handleNodeClick(item.id,$event)" class="log-item">{{item.label}}</div>
<div v-for="a in item.children">
<div style="font-size: 16px;margin-left: 1em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(a.id,$event)" class="log-item">{{a.label}}</div>
<div v-for="b in a.children">
<div style="font-size: 16px;margin-left: 2em;margin-top: 1.2em;cursor: pointer" @click="handleNodeClick(b.id,$event)" class="log-item">{{b.label}}</div>
</div>
</div>
</div> -->
<div style="width: 100%" class="nav">
<a-menu
:default-selected-keys="['1']"
:default-open-keys="['2']"
mode="inline"
:theme="theme"
:inline-collapsed="collapsed"
:selectedKeys="selectedKeys"
>
<template v-for="item in tocData">
<a-menu-item v-if="!item.children" :key="item.key">
<a-icon type="pie-chart" />
<span>{{ item.title }}</span>
</a-menu-item>
<sub-menu v-else :key="item.key + '1'" :menu-info="item" />
</template>
</a-menu>
</div>
</div>
<div class="right-content">
<div class="word-wrap">
<div id="wordView" ref = "aContent" v-html="wordText" v-if="wordText" />
</div>
</div>
</div>
</div>
</template>
<script>
import mammoth from "mammoth";
import { Menu } from "ant-design-vue";
const SubMenu = {
template: `
<a-sub-menu :key="menuInfo.key" v-bind="$props" v-on="$listeners">
<span slot="title">
<a-icon type="mail" /><span>{{ menuInfo.title }}</span>
</span>
<template v-for="item in menuInfo.children">
<a-menu-item v-if="!item.children" :key="item.key" @click=handleNodeClick(item.id)>
<a-icon type="pie-chart" />
<span>{{ item.title }}</span>
</a-menu-item>
<sub-menu v-else :key="item.key" :menu-info="item" />
</template>
</a-sub-menu>
`,
name: "SubMenu",
// must add isSubMenu: true
isSubMenu: true,
props: {
...Menu.SubMenu.props,
// Cannot overlap with properties within Menu.SubMenu.props
menuInfo: {
type: Object,
default: () => ({}),
},
},
methods:{
handleNodeClick(data) {
console.log("点击目录")
// 实现跳转锚点
let id = data
let tag = 'h-'+id
let anchorH = document.getElementById(tag).offsetTop // 得到h标签的位置
console.log("anchorH",anchorH)
this.changeHeight(anchorH)
// this.$emit('changeHeight', anchorH); // 我的页面滚动通过父组件控制,所以发射出去让父组件操作
},
changeHeight(record){
console.log("--------")
// let el = this.$refs.scroll
let el = document.getElementById("wordView")
// console.log("el",el)
// let cur = el.scrollTop // el-scrollbar获取页面当前位置的属性,定位也是通过改变这个
let cur = el.offsetTop
// console.log("cur",cur)
let flag = false
let step = 20
// 由于有可能是向上滑,有可能向下滑,所以需要考虑移动的正负
if (record < cur) {
step = -step;
flag = true
}else if (record == cur) {
return
}
// 设置动画
// 具体含义就是按照每8毫秒执行一次向上移动指定的步长,这样就是一个动画
var timer = setInterval(()=>{
if (flag) {
// 说明小于0
if (cur + step < record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
// el.scrollTop = record
window.scrollTo({ top: record, behavior: "smooth" });
clearInterval(timer)
}else {
// el.scrollTop = cur + step
window.scrollTo({ top: cur + step, behavior: "smooth" });
}
}else{
if (cur + step > record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
// el.scrollTop = record
window.scrollTo({ top: record, behavior: "smooth" });
clearInterval(timer)
}else {
// el.scrollTop = cur + step
window.scrollTo({ top: cur + step, behavior: "smooth" });
}
}
cur += step
},8)
},
}
};
export default{
components: {
"sub-menu": SubMenu,
},
data(){
return{
wordText: "",
wordURL: '/pro/指标管理平台操作指南2.docx',//文件地址
flag:true,
scrollTag:[],
selectedKeys:['0'],
maxum:0,
keyword:'',
highlightedText:'',
searchIndex:1,
anchorHlist:[], //搜索的元素所在的位置
matchNum:0, //匹配数
curIndex:0, //当前的index
}
},
created(){
this.getWordText();
setTimeout(()=>{
this.makeToc()
this.$forceUpdate()
},2000)
},
mounted(){
window.addEventListener('scroll',this.handleScroll,true)
setTimeout(()=>{
let values = []
console.log("this.maxum",this.maxum)
// let hTags = document.getElementsByTagName('h');
for (let i = 1;i <=this.maxum;i++) {
let tag = 'h-'+i
// console.log("tag",tag)
let anchorH = document.getElementById(tag).offsetTop
// console.log(anchorH)
values.push({id:i,height:anchorH})
}
this.scrollTag = values
// console.log("scrollTag",this.scrollTag)
},2100)
},
computed: {
// processedText() {
// const regex = new RegExp(this.keyword, 'gi'); // g表示全局匹配,i表示不区分大小写
// this.highlightedText = this.wordText.replace(regex, `<span class="highlight">${this.keyword}</span>`);
// return this.highlightedText;
// }
matchNumText(){
let num = this.matchNum
return num
}
},
updated() {
// 需要在data里面加上flag,只触发一次里面内容
// if (this.flag) {
// this.makeToc()
// this.flag = false
// }
// this.$emit('refreshScroll',values)
},
methods:{
getWordText() {
const xhr = new XMLHttpRequest();
xhr.open("get", this.wordURL, true);
console.log("======",xhr)
xhr.responseType = "arraybuffer";
xhr.onload = () => {
if (xhr.status == 200) {
mammoth.convertToHtml({ arrayBuffer: new Uint8Array(xhr.response) }).then((resultObject) => {
this.$nextTick(() => {
this.wordText = resultObject.value;
console.log("this.$refs",this.$refs)
// this.makeToc()
});
});
}
};
xhr.send();
},
onSearch(){
const regex1 = new RegExp(`<span class="highlight" style="background:yellow">`, 'gi') //删除上一次的样式
this.wordText = this.wordText.replace(regex1,"<span>")
this.searchIndex = 1
const regex = new RegExp(this.keyword, 'gi'); // g表示全局匹配,i表示不区分大小写
let wordText = this.wordText
let matches = this.wordText.match(regex)
// this.matchNum = matches !== null ? matches.length : 0
this.wordText = wordText.replace(regex, `<span class="highlight" style="background:yellow">${this.keyword}</span>`);
// 根据关键字查找匹配的元素
let mainContainer =document.querySelector("#wordView").querySelectorAll("p");
console.log("mainContainer.length",mainContainer.length,mainContainer[1].textContent)
// let anchorHlist = []
this.anchorHlist = []
for(let i =0;i<=mainContainer.length-1;i++){
if(mainContainer[i].textContent.match(regex) !== null){
let anchorH = mainContainer[i].offsetTop
this.anchorHlist.push(anchorH)
}
}
this.matchNum = this.anchorHlist.length
this.scrollToLocation(this.checkIndex);
console.log("mainContainer",mainContainer)
console.log('matchNum',this.matchNum,matches,this.anchorHlist)
},
scrollToLocation(index){
let height = this.anchorHlist[index]
console.log("下一个-------",height)
window.scrollTo({ top: height - 110, behavior: "smooth" });
// window.scrollTo({ top: cur + step, behavior: "smooth" });
},
next() {
this.searchIndex++;
if(this.searchIndex > this.matchNum) this.searchIndex = 1
this.scrollToLocation(this.searchIndex-1);
},
previous() {
if(this.searchIndex > 1){
this.searchIndex--;
}else{
this.searchIndex = 1
}
this.scrollToLocation(this.searchIndex-1);
},
// 将一个集合的数据变成一个树形的数据结构
toTree(data){
// 删除 所有 children,以防止多次调用
data.forEach(function (item) {
delete item.children;
});
// 将数据存储为 以 id 为 KEY 的 map 索引数据列
var map = {};
data.forEach(function (item) {
map[item.id] = item;
});
var val = [];
data.forEach(function (item) {
// 以当前遍历项的pid,去map对象中找到索引的id
var parent = map[item.p_id];
// 好绕啊,如果找到索引,那么说明此项不在顶级当中,那么需要把此项添加到,他对应的父级中
if (parent) {
(parent.children || (parent.children = [])).push(item);
} else {
//如果没有在map中找到对应的索引ID,那么直接把 当前的item添加到 val结果集中,作为顶级
val.push(item);
}
});
return val;
},
/**
* 生成目录
* */
makeToc(){
// 获取所有的h标签,给他们加上id,同时创建符合toTree方法要求的对象
//{
// id:'',// 抛出id
// tag:'',// 抛出标签名称
// label:'',// 抛出标题
// p_id:'',// 抛出父级id
// }
// 定义参与目录生成的标签
const tocTags = ["H1","H2","H3","H4","H5","H6"];
// 目录树结果
const tocArr = [];
// 获取所有标题标签
const headDoms = Array.from(this.$refs.aContent.childNodes).filter(item => tocTags.includes(item.tagName));
// 遍历标题标签
headDoms.forEach((item,index,arr) => {
// 给标题添加id
item.id = `h-${index + 1}`;
// 获取当前节点前面的节点
let prevs = arr.filter((i,j) => j < index);
// 过滤前面的节点为合理节点
// 如 h3节点前 只能为 h1 h2 h3
prevs = prevs.filter(i => tocTags.filter((i,j) => j <= tocTags.findIndex(i => i == item.tagName)).includes(i.tagName));
// 对前面的节点进行排序,距离自身节点近的排在前面
// 如 div > p > span > img 当前为img
// 常规获取节点为 [div,p,span,img]
// 排序后获取节点为 [img,span,p,div]
prevs = prevs.sort((a,b) => -(a.id.replace('h-','')) - b.id.replace('h-',''));
// 查询距离自身节点最近的不同于当前标签的节点
const prev = prevs.find(i => i.tagName != item.tagName);
this.maxum = Math.max(this.maxum,index + 1)
tocArr.push({
id:index + 1,// 抛出id
key:index,
title:item.innerText,
tag:item.tagName,// 抛出标签名称
label:item.innerText,// 抛出标题
p_id:item.tagName == "H1" || prev == null ? 0 : Number(prev.id.replace("h-",'')),// 抛出父级id
})
})
// 使用上述方法生成树 最后在el-tree的data中使用 tocData即可
this.tocData = this.toTree(tocArr);
console.log(this.tocData)
},
changeHeight(record){
console("--------")
let el = this.$refs.scroll
let cur = el.scrollTop // el-scrollbar获取页面当前位置的属性,定位也是通过改变这个
let flag = false
let step = 20
// 由于有可能是向上滑,有可能向下滑,所以需要考虑移动的正负
if (record < cur) {
step = -step;
flag = true
}else if (record == cur) {
return
}
// 设置动画
// 具体含义就是按照每8毫秒执行一次向上移动指定的步长,这样就是一个动画
var timer = setInterval(()=>{
if (flag) {
// 说明小于0
if (cur + step < record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
el.scrollTop = record
clearInterval(timer)
}else {
el.scrollTop = cur + step
}
}else{
if (cur + step > record) {
// 说明移动多了,这时候我们改变移动距离,直接移动到指定位置即可
el.scrollTop = record
clearInterval(timer)
}else {
el.scrollTop = cur + step
}
}
cur += step
},8)
},
// 父组件
handleScroll(){
// let top = this.$refs.scroll.wrap.scrollTop
let top = document.getElementById("wordView").offsetTop
let len = this.scrollTag.length -1
for (let i = 0;i < len;i ++) {
// 判断当前页面的位置 应该属于哪个标签,我这里是标签下的内容没有结束,目录都是这个标签高亮
if (top >= this.scrollTag[i].height - 1 && top < this.scrollTag[i + 1].height) {
// -1 是我发现点击目录滑动的时候,会有<1 的误差,导致目录高亮不改变
// 让之变成
// this.$refs.detail.changeItem(i) // 通过这个方法,改变目录标签高亮
this.selectedKeys = [i]
}
}
// 特殊处理 第一个和最后一个
if (top< this.scrollTag[0].height ) this.selectedKeys = [i]
if (top >= this.scrollTag[len].height - 1 ) this.selectedKeys = [this.selectedKeys.length -1]
console.log("this.selectedKeys",this.selectedKeys)
},
}
}
</script>
<style lang="less">
.content {
width: 95%;
margin:0px auto;
border: 1px solid black;
font-size: 20px;
}
.header{
position: fixed;
top:0px;
left: 2.5%;
width: 95%;
height:100px;
border-bottom: 1px solid black;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background-color:aliceblue;
.title{
font-size: 40px;
font-weight: bold;
}
.search{
/* width: 500px; */
height: 50px;
display: flex;
justify-content: space-around;
flex-direction: row;
.btn{
width: 20%;
height: 50px;
border-radius: 15%;
background-color: #1890ff;
color: #fff;
}
.search-area{
width: 70%;
height: 50px;
margin: 0 10px;
}
}
}
.content-area{
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 100px;
.left-Nav{
position: fixed;
top:100px;
left:2.5%;
width: 19%;
padding:10px 30px;
.nav{
/* height: 100%;
position: relative; */
}
}
.right-content{
margin-left:20%;
width: 85%;
padding:10px 30px;
border-left: 1px solid black;
.word-wrap {
padding: 15px;
img {
width: 80%;
}
}
}
}
h1{
font-size:36px;
height: 40px;
line-height: 40px;
}
h2{
font-size:28px;
height: 40px;
line-height: 40px;
}
img{
width: 80% !important;
}
.ant-menu{
font-size: 20px !important;
}
/* .word-wrap {
padding: 15px;
img {
width: 100%;
}
} */
</styl