重构后前台详细页的文章导航部分出现了错误提示,这个主要是我们选择的markdown-navbar模块的坑,这里为大家推荐一个这方面的插件叫 tocify.tsx,比较好用,所以我们直接拿来使用。
tocify.tsx 文件简介
tocify.tsx文件,用这个扩展名的含义是他使用了typeScript语法来编写jsx的部分,为了更好的和普通的是jsx文件区分,所以这里使用了tsx的扩展名。
使用这个文件的两个必要条件
- 你的程序员中使用了
Ant DesignUI 库,因为它里边的导航部分,使用了antd的Anchor组件 - 安装
lodash模块,这个可以直接使用yarn add lodash来安装或者使用npm install --save lodash
上面两个条件满足后,你可以把文件tocify.tsx拷贝到你的项目里了,我这里放到了/blog/components文件夹下了,把它视为一种自定义组件。
tocify.tsx 文件
import React from 'react';
import { Anchor } from 'antd';
import { last } from 'lodash';
const { Link } = Anchor;
export interface TocItem {
anchor: string;
level: number;
text: string;
children?: TocItem[];
}
export type TocItems = TocItem[]; // TOC 目录树结构
export default class Tocify {
tocItems: TocItems = [];
index: number = 0;
constructor() {
this.tocItems = [];
this.index = 0;
}
add(text: string, level: number) {
const anchor = `toc${level}${++this.index}`;
const item = { anchor, level, text };
const items = this.tocItems;
if (items.length === 0) { // 第一个 item 直接 push
items.push(item);
} else {
let lastItem = last(items) as TocItem; // 最后一个 item
if (item.level > lastItem.level) { // item 是 lastItem 的 children
for (let i = lastItem.level + 1; i <= 2; i++) {
const { children } = lastItem;
if (!children) { // 如果 children 不存在
lastItem.children = [item];
break;
}
lastItem = last(children) as TocItem; // 重置 lastItem 为 children 的最后一个 item
if (item.level <= lastItem.level) { // item level 小于或等于 lastItem level 都视为与 children 同级
children.push(item);
break;
}
}
} else { // 置于最顶级
items.push(item);
}
}
return anchor;
}
reset = () => {
this.tocItems = [];
this.index = 0;
};
renderToc(items: TocItem[]) { // 递归 render
return items.map(item => (
<Link key={item.anchor} href={`#${item.anchor}`} title={item.text}>
{item.children && this.renderToc(item.children)}
</Link>
));
}
render() {
return (
<Anchor affix showInkInFixed>
{this.renderToc(this.tocItems)}
</Anchor>
);
}
}
其实这个文件也很简单,如果 JavaScript 基础没问题是完全可以看明白的。
使用tocify.tsx生成文章目录
接下来就可以利用tocify.tsx文件生成目录了,在/blog/pages/detailed.js引入。
import Tocify from '../components/tocify.tsx'
引入后,需要对marked的渲染进行自定义,这时候需要设置renderer.heading,就是写一个方法们重新定义对#这种标签的解析。代码如下:
renderer.heading = function(text, level, raw) {
const anchor = tocify.add(text, level);
return `<a id="${anchor}" href="#${anchor}" ><h${level}>${text}</h${level}></a>n`;
};
最后在需要显示文章导航的地方,写下面的代码:
<div className="toc-list">
{tocify && tocify.render()}
</div>
为了方便大家学习,给出detailed.js全部代码。
import React,{useState} from 'react'
import Head from 'next/head'
import {Row, Col, Space, Breadcrumb, Affix} from 'antd'
import {ClockCircleOutlined,VideoCameraOutlined,FireOutlined} from '@ant-design/icons'
import Header from '../components/Header'
import Author from '../components/Author'
import Advert from '../components/Advert'
import Footer from '../components/Footer'
import MarkNav from 'markdown-navbar';
import 'markdown-navbar/dist/navbar.css';
import axios from 'axios'
import marked from 'marked'
import hljs from "highlight.js";
import 'highlight.js/styles/monokai-sublime.css';
import Tocify from '../components/tocify.tsx'
const Detailed = (props) => {
const IconText = ({ icon, text }) => (
<Space>
{React.createElement(icon)}
{text}
</Space>
);
const tocify = new Tocify()
renderer.heading = function(text, level, raw) {
const anchor = tocify.add(text, level);
return `<a id="${anchor}" href="#${anchor}" ><h${level}>${text}</h${level}></a>n`;
};
const renderer = new marked.Renderer();
marked.setOptions({
renderer: renderer,
gfm: true,
pedantic: false,
sanitize: false,
tables: true,
breaks: false,
smartLists: true,
smartypants: false,
highlight: function (code) {
return hljs.highlightAuto(code).value;
}
});
let html = marked(props.article_content);
return (
<>
<Head>
<title>博客详细页</title>
</Head>
<Header />
<Row className="comm-main" type="flex" justify="center">
<Col className="comm-left" xs={24} sm={24} md={16} lg={18} xl={14} >
<div>
<div className="bread-div">
<Breadcrumb>
<Breadcrumb.Item><a href="/">首页</a></Breadcrumb.Item>
<Breadcrumb.Item>教程列表</Breadcrumb.Item>
<Breadcrumb.Item>xxxx</Breadcrumb.Item>
</Breadcrumb>
</div>
<div>
<div className="detailed-title">
React 实战教程笔记-开发
</div>
<div className="list-icon center">
<IconText icon={ClockCircleOutlined} text="2020-12-01" key="list-vertical-star-o" />
<IconText icon={VideoCameraOutlined} text="前端教程" key="list-vertical-like-o" />
<IconText icon={FireOutlined} text="5498 人" key="list-vertical-message" />
</div>
<div className="detailed-content"
dangerouslySetInnerHTML={{__html:html}}
>
</div>
</div>
</div>
</Col>
<Col className="comm-right" xs={0} sm={0} md={7} lg={5} xl={4}>
<Author />
<Advert />
<Affix offsetTop={5}>
<div className="detailed-nav comm-box">
<div className="nav-title">文章目录</div>
<div className="toc-list">
{tocify && tocify.render()}
</div>
</div>
</Affix>
</Col>
</Row>
<Footer/>
</>
)
}
Detailed.getInitialProps = async(context) => {
let id = context.query.id;
const promise = new Promise((resolve)=>{
axios('http://127.0.0.1:7001/default/getArticleById/' + id).then((res) => {
console.log(res)
resolve(res.data.data[0])
})
})
return await promise
}
export default Detailed
这样就完成了前端详细文章页面的文章导航,可以预览一下效果。