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
posted @ 2024-01-26 09:16  三教布衣  阅读(1113)  评论(0编辑  收藏  举报