Vue自定义文章目录

一. 需求

根据string类型的字符串文章内容,提取H1-H6标签,生成文章目录结构展示,并且对应生成锚点,点击可跳转。

二. 效果图

三. 代码实现

1. Template代码

<a-card style="margin:10px 10px 0 20px;position: absolute;top:30%;width:15%;">   
<b>文章目录</b>   
<p></p>   
<p v-for="menuitem in menuTree" :key="menuitem"><a :href="getElementmaodian(menuitem)" :style="menustyle(menuitem)">{{getElementcontent(menuitem)}}</a></p></a-card>

2. Method代码

1)根据文章内容生成目录结构

/*设置文章目录结构*/  
getCenceptQuerySelect(){   
    const str_content = this.selectConcept.contents   
    const regex = /<h[1-6](.*?)>(.*?)<\/h[1-6]>/g;//正则表达式匹配提取所有H1-H6标签         
    var match    
    while((match = regex.exec(str_content)) !== null{     
        this.menuTree.push(match[0])    
    }  
},

2)目录样式

/*文章结构标题缩进*/  
menustyle(menuiten){ 
    var type = menuiten.slice(1,3)   
    var menutyle = ''   
    switch(type){ 
        case 'h1': menutyle = 'margin-left:10px;';break    
        case 'h2': menutyle = 'margin-left:20px;';break    
        case 'h3': menutyle = 'margin-left:30px;';break    
        case 'h4': menutyle = 'margin-left:40px;';break    
        case 'h5': menutyle = 'margin-left:50px;';break    
        case 'h6': menutyle = 'margin-left:60px;';break    
        default: break   
    }       
    return menutyle  
},

3)获取标签锚点

/*获取标签锚点*/  
getElementmaodian(item){   
    var placeholder = document.createElement('div')   
    placeholder.innerHTML = item       //返回id锚点   
    return '#' + placeholder.firstElementChild.id  
},

4)文章创建时添加锚点代码

由于使用quill富文本创建文章是生成的内容没有锚点,所以需要自定义:

const str_content = this.addConcept.contents    
const regex = /<h[1-6]>(.*?)<\/h[1-6]>/g;         
var match    var menuTree = []    
while((match = regex.exec(str_content)) !== null){        
    menuTree.push(match[0])    
}         
menuTree.forEach(item=>{     
    var placeholder = document.createElement('div')     
    placeholder.innerHTML = item

    //生成有锚点的元素,锚点value为显示的内容     
    var rep = item.replaceAll('>'+placeholder.firstElementChild.innerHTML+'<', ' id=\'' +     placeholder.firstElementChild.innerHTML + '\'>'+placeholder.firstElementChild.innerHTML+'<')      
    this.addConcept.contents = this.addConcept.contents.replaceAll(item, rep) //替换有锚点的元素 
})

四. 进阶 - 目录树

需求

目录树结构,可展开收缩。

效果图

 

代码实现

1. 生成目录树结构的Json

menuTree_json存放结构数据

 generateDirectoryTree_Json(content){
   const regex = /<h[1-6](.*?)>(.*?)<\/h[1-6]>/g;
   var match
   while ((match = regex.exec(content)) !== null) {
    var item = {
     level: this.menuItemLevel(match[0]),   //层级:H1-1/H2-2/H3-3/H4-4/H5-5/H6-6
     anchor: this.getElementmaodian(match[0]), //锚点
     title: this.getElementcontent(match[0]), //内容
     child: [],                //子节点
     isExpand:true               //默认该树节点展开
    }
      
    this.addMenuitem(this.menuTree_json,item) //从树根节点开始添加子节点
   }
  },
   
  /*递归插入*/
  addMenuitem(menu, item){
   if (menu.length === 0) {
    menu.push(item)
   }
   else {
    if (item.level > menu[menu.length - 1].level) {
     //递归添加子节点
     this.addMenuitem(menu[menu.length - 1].child, item)
    }
    else if (menu.length - 1 === 0) {
     menu.push(item)
    }
    else {
     menu.push(item)
    }
   }
  },

2.templace中根据目录树结构的Json递归生成目录

这里将这部分定义为一个组件,方便递归调用。

组件调用:

<ContentMenu :menu="menuTree_json"></ContentMenu>

组件定义:

<template>
 <div>
<div v-for="menuitem in menu" :key="menuitem" style="overflow: auto;margin-top:10px;">
   <svg @click="menuitem.isExpand=!menuitem.isExpand" v-if="menuitem.child.length != 0 && !menuitem.isExpand" t="1682300013635" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3229" width="16" height="16"><path d="M946.33106 697.353498 541.30749 284.093337c-15.690354-16.009625-41.469484-16.009625-57.160861 0l-405.024593 413.260162c-24.819269 25.323758-6.877641 68.028373 28.579919 68.028373l810.048163 0C953.209724 765.381871 971.150328 722.677257 946.33106 697.353498z" fill="#1296db" p-id="3230"></path></svg>
   <svg @click="menuitem.isExpand=!menuitem.isExpand" v-if="menuitem.child.length != 0 && menuitem.isExpand" t="1682300082130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3505" width="16" height="16"><path d="M79.123059 327.850933l405.024593 413.260162c15.690354 16.009625 41.469484 16.009625 57.160861 0l405.02357-413.260162c24.819269-25.323758 6.877641-68.028373-28.579919-68.028373L107.704001 259.82256C72.245418 259.82256 54.30379 302.527175 79.123059 327.850933z" fill="#1296db" p-id="3506"></path></svg>
   <a href="javascript:void(0)" @click="goAnchor(menuitem.anchor)" style="margin-left:5px;margin-top:3px;">
    <span v-html="menuitem.title"></span>
</a>
   <div v-if="menuitem.isExpand" style="margin-left:30px;margin-top:10px;">
    <!--递归生成目录树,递归自己-->
    <contentMenu :menu="menuitem.child"></contentMenu>
   </div>
  </div>
 </div>
</template>
<script>
import { mixins_menutree } from '../../mixin/menutree'
export default {
 name:'contentMenu', //定义name才能组件自己递归调用自己
 mixins:[mixins_menutree],
 props:{
menu:[]
 }
}
</script>

五. 目录搜索

需求

当目录条目过多时,需要进行搜索过滤

效果

 

代码实现

<a-input-search    
    size="small"    
    placeholder="文章目录"    
    v-model = "searchMenu"   
    @search="onSearchMenu"
></a-input-search>

/*onSearchMenu 
  搜索目录,重新生成目录
  */
  onSearchMenu() {
   this.menuTree_json = []
    
   this.menu_init.forEach(item => {
    if (item.title.toLowerCase().indexOf(this.searchMenu.toLowerCase()) != -1) {
item.child = [] //孩子节点直接去掉
     this.addMenuitem(this.menuTree_json, item)
    }
   })
  },

六. 遇到问题

1. 锚点中文跳转控制台报错问题

 中文锚点 #锚点1 被转义为 #%E9%94%9A%E7%82%B91 导致控制台报错,但是能work,正确跳转。。。

2. a标签href跳转发布到IIS后跳转出错

<a :href="getElementmaodian(menuitem)"

解决:

<a href="javascript:void(0)" @click="goAnchor(getElementmaodian(menuitem))"

goAnchor(selector){ //锚点跳转    document.querySelector(selector).scrollIntoView({  behavior:'smooth'    })},

3. 锚点是数字开头会认为不合法

解决方法一:标题字符串处理后再生成锚点id,保证锚点正确。

还存在问题:

1、btoa加密后的字符串后两位是‘==’,也会包锚点不合法错误,临时解决:只提取前20位

2、只提取前20位的话有可能存在相同锚点

//标题字符串处理,保证锚点正确

var str64 = window.btoa(window.encodeURIComponent(placeholder.firstElementChild.innerHTML)).slice(0,20)

var rep = item.replaceAll('>'+placeholder.firstElementChild.innerHTML+'<', ' id=\'' + str64 + '\'>'+placeholder.firstElementChild.innerHTML+'<') this.addConcept.contents = this.addConcept.contents.replaceAll(item, rep)

解决方法二

随机数拼接字符串

//标题字符串处理,保证锚点正确var str64 = window.btoa(window.encodeURIComponent(placeholder.firstElementChild.innerHTML))
//移除特殊字符
str64 = str64.replaceAll('=', '')
//生成随机数转成36进制,再截取后8位
var str_random = Math.random().toString(36).slice(-8)

str64 = str64 + str_random
var rep = item.replaceAll('>'+placeholder.firstElementChild.innerHTML+'<', ' id=\'' + str64 + '\'>'+placeholder.firstElementChild.innerHTML+'<') 
this.addConcept.contents = this.addConcept.contents.replace(item, rep)//使用replace而不是replaceAll,否则可能替换所有

解决方法三:

直接生成三个随机数转成36进制,分别截取后8位,再拼接

​
var str_random1 = Math.random().toString(36).slice(-8)
var str_random2 = Math.random().toString(36).slice(-8)
var str_random3 = Math.random().toString(36).slice(-8)var str_random = str_random1 + str_random2 + str_random3

​

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值