效果

需求
因為項目最後是對外開放的,是以在項目中有個文檔中心,裡面有一些使用者手冊、開發文檔等展示需求。同時支援文章目錄點選。
分析
第一時間想到的就是解析
md
檔案了,文法簡單,滿足大部分寫作要求,市面上也很有很多成熟的解析方案。
依賴項
這裡是使用了
react-markdown
和
github-markdown-css
這兩個庫,一個是解析
md
檔案,一個是github的
md
文檔樣式。
具體
解析md檔案(大概)
import React, { useState, useEffect } from 'react';
import ReactMarkdown from 'react-markdown/with-html';
import 'github-markdown-css/github-markdown.css'
const articles = {
'1': '/developer_guide.md',
'2': '/user_manual.md'
}
const NormalTest: React.FC<any> = () => {
const [currentArticle, setCurrentArticle] = useState<{ url: string, content: any }>({ url: '', content: '' });
// 初始為開發文檔
useEffect(() => {
changeCurrentArticle(articles['1'])
}, [])
// 更改目前文檔
const changeCurrentArticle = async (url: string) => {
const res = await fetch(url);
const content = await res.text();
setCurrentArticle({ ...currentArticle, content, url })
}
return (
<>
<ReactMarkdown
className="markdown-body"
source={currentArticle.content}
escapeHtml={false}
/>
</>
)
};
如果頁面正确顯示,說明是解析成功
單獨解析目錄
這裡要用到
react-markdown
元件的
renderers
API,給
title
加上錨點
HeadBlock.tsx
import React from 'react';
const HeadBlock: React.FC<any> = (props) => {
const { level, children } = props;
const { nodeKey } = children[0].props;
return (
<>
{React.createElement(`h${level}`, { className: 'article-nav', id: nodeKey, 'data-level': level }, children)}
</>
);
}
export default HeadBlock;
index.tsx
import HeadBlock from './HeadBlock';
<ReactMarkdown
className="markdown-body"
source={currentArticle.content}
escapeHtml={false}
renderers={{
heading: HeadBlock
}}
/>
完整版
import React, { useState, useEffect } from 'react';
import { Row, Col, Menu, Affix, Anchor } from 'antd';
import ReactMarkdown from 'react-markdown/with-html';
import { isEmpty } from "lodash";
import HeadBlock from './HeadBlock';
import 'github-markdown-css/github-markdown.css'
import './index.less';
const { Link } = Anchor;
const articles = {
'1': '/developer_guide.md',
'2': '/user_manual.md'
}
/**
*
* @param lists
* 這裡隻做兩級處理
*/
export const navsToTree = (lists: any[]) => {
if (isEmpty(lists)) return [];
// 提取第一個level為最大level 後續比他大的一律為同級
const maxLevel = lists[0].level;
const newLists: any[] = [];
lists.forEach((item: any) => {
// 一級 同級
if (item.level <= maxLevel) {
newLists.push(item)
} else {
// 非同級
if (newLists[newLists.length - 1].children) {
newLists[newLists.length - 1].children.push(item)
} else {
newLists[newLists.length - 1].children = [item]
}
}
})
return newLists;
}
const NormalTest: React.FC<any> = () => {
const [currentArticle, setCurrentArticle] = useState<{ url: string, content: any }>({ url: '', content: '' });
const [treeNavs, setTreeNavs] = useState<any[]>([])
// 初始為開發文檔
useEffect(() => {
// console.log(1);
changeCurrentArticle(articles['1'])
}, [])
// 這裡是根據文檔修改進行擷取目錄
useEffect(() => {
/**
* 擷取所有的文章标題
*/
// console.log(currentArticle);
const markdownNavs = document.querySelectorAll('.article-nav')
const navs: any[] = [];
markdownNavs.forEach((item: any) => {
const level = item.getAttribute('data-level');
const value = item.textContent;
const nodeKey = item.id;
navs.push({ level, value, nodeKey })
})
transArticleNavs(navs)
}, [currentArticle.content])
// 更改目前文檔
const changeCurrentArticle = async (url: string) => {
const res = await fetch(url);
const content = await res.text();
setCurrentArticle({ ...currentArticle, content, url })
}
// 書籍導航點選
const menuOnClick = (e: any) => {
const url = articles[e.key]
changeCurrentArticle(url)
}
// 轉換為文章右側目錄
const transArticleNavs = (navs: any) => {
// 轉換為二級導航
const treedevelopDocs = navsToTree(navs);
setTreeNavs(treedevelopDocs)
}
return (
<>
<Row className='articles'>
<Col flex='200px' className="articles-list">
<Affix offsetTop={24}>
<Menu defaultSelectedKeys={['1']} onClick={menuOnClick} theme='light'>
<Menu.Item key="1">開發文檔</Menu.Item>
<Menu.Item key="2">使用文檔</Menu.Item>
</Menu>
</Affix>
</Col>
<Col flex='1' className='articles-content'>
<div className='articles-content_wrpper'>
<ReactMarkdown
className="markdown-body"
source={currentArticle.content}
escapeHtml={false}
renderers={{
heading: HeadBlock
}}
/>
</div>
</Col>
<Col flex='200px' className="articles-menu">
<Affix offsetTop={20} >
<Anchor style={{ width: 160 }}>
{
treeNavs.map((item: any) => {
if (item.children) {
return (
<Link href={`#${item.nodeKey}`} title={item.value} key={item.nodeKey}>
{
item.children.map((childItem: any) => (
<Link href={`#${childItem.nodeKey}`} title={childItem.value} key={childItem.nodeKey} />
))
}
</Link>
)
} else {
return (
<Link href={`#${item.nodeKey}`} title={item.value} key={item.nodeKey} />
)
}
})
}
</Anchor>
</Affix>
</Col>
</Row>
</>
);
};
export default NormalTest;