106 lines
5.5 KiB
JavaScript
106 lines
5.5 KiB
JavaScript
import { useEffect } from "react";
|
||
import { Button, Tooltip } from "antd";
|
||
import { CaretDownOutlined, CloseOutlined, MenuOutlined } from "@ant-design/icons";
|
||
import classNames from "classnames";
|
||
import Style from './tocView.module.less';
|
||
export function TocView(props) {
|
||
const { toc, showToc, tocPosition, setShowToc, highlightBgColor, headerTop = 0, scrollId, fixed = false, closed = false } = props;
|
||
useEffect(() => {
|
||
document.documentElement.style.setProperty('--highlight-bg-color', highlightBgColor);
|
||
}, [highlightBgColor]);
|
||
const generateTocList = (items, currentLevel = 1, parentId = null) => {
|
||
//递归生成嵌套列表
|
||
const result = [];
|
||
let lastId = parentId;
|
||
while (items.length > 0) {
|
||
const item = items[0];
|
||
//有无子级标题,有则显示展开图标
|
||
const hasChildren = items.length > 1 && items[1].level > item.level;
|
||
if (item.level > currentLevel) {
|
||
// 递归生成子列表
|
||
const subList = generateTocList(items, item.level, item.id);
|
||
result.push(<li key={item.id} id={`ul-${lastId}`}>
|
||
<ul style={{ listStyleType: 'none', paddingInlineStart: '6px' }}>
|
||
{subList}
|
||
</ul>
|
||
</li>);
|
||
}
|
||
else if (item.level < currentLevel) {
|
||
// 结束当前层级
|
||
break;
|
||
}
|
||
else {
|
||
// 添加当前层级的 <li>
|
||
result.push(<li className={Style.listItem} key={item.id} id={`li-${item.id}`} style={{ paddingLeft: `${(item.level - 1) * 6}px`, }}>
|
||
<CaretDownOutlined id={`icon-${item.id}`} className={Style.icon} style={{ visibility: hasChildren && item.level < 5 ? 'visible' : 'hidden', color: '#A5A5A5' }} onClick={() => {
|
||
const iconElem = document.getElementById(`icon-${item.id}`);
|
||
const ulElem = document.getElementById(`ul-${item.id}`);
|
||
const isFolded = iconElem?.className.includes('iconFold') || ulElem?.className.includes('fold');
|
||
if (isFolded) {
|
||
iconElem?.classList.remove(Style.iconFold);
|
||
ulElem?.classList.remove(Style.fold);
|
||
}
|
||
else {
|
||
iconElem?.classList.add(Style.iconFold);
|
||
ulElem?.classList.add(Style.fold);
|
||
}
|
||
}}/>
|
||
<div style={{ fontSize: '1em', fontWeight: item.level === 1 ? 'bold' : 'normal' }} className={Style.tocItem} onClick={(event) => {
|
||
//页面滚动到对应元素
|
||
const elem = document.getElementById(item.id);
|
||
const elemTop = elem?.getBoundingClientRect().top;
|
||
if (scrollId) {
|
||
const scrollContainer = document.getElementById(scrollId);
|
||
const containerTop = scrollContainer?.getBoundingClientRect().top;
|
||
scrollContainer?.scrollBy({
|
||
top: elemTop - containerTop,
|
||
behavior: 'smooth',
|
||
});
|
||
}
|
||
else {
|
||
// const containerTop = document.body.getBoundingClientRect().top;
|
||
window.scrollBy({
|
||
top: elemTop - headerTop,
|
||
behavior: 'smooth',
|
||
});
|
||
}
|
||
//添加背景色
|
||
elem?.classList.add(Style.highlight);
|
||
//移除背景色类名
|
||
setTimeout(function () {
|
||
elem?.classList.remove(Style.highlight);
|
||
}, 1000);
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
}}>
|
||
{item.text}
|
||
</div>
|
||
</li>);
|
||
items.shift(); // 移除已处理的项
|
||
lastId = item.id;
|
||
}
|
||
}
|
||
return result;
|
||
};
|
||
return (<div className={classNames(Style.tocContainer, {
|
||
[Style.fixed]: fixed
|
||
})}>
|
||
{showToc ? (<>
|
||
<div className={Style.catalogTitle}>
|
||
<div style={{ color: '#A5A5A5' }}>大纲</div>
|
||
{closed ? (<CloseOutlined style={{ color: '#A5A5A5' }} onClick={() => setShowToc(false)}/>) : null}
|
||
</div>
|
||
|
||
{(toc && toc.length > 0) ? (<ul style={{ listStyleType: 'none', paddingInlineStart: '0px' }}>{generateTocList([...toc])}</ul>) : (<div style={{ display: 'flex', alignItems: 'center', color: '#B1B1B1', height: '200px' }}>
|
||
<div>
|
||
对文档内容应用“标题”样式,即可生成大纲
|
||
</div>
|
||
</div>)}
|
||
</>) : (<div className={classNames(Style.tocButton, { [Style.tocButtonRight]: tocPosition === 'right' })}>
|
||
<Tooltip title="显示大纲" placement={tocPosition === 'right' ? 'left' : 'right'}>
|
||
<Button size="small" icon={<MenuOutlined />} onClick={() => setShowToc(true)}/>
|
||
</Tooltip>
|
||
</div>)}
|
||
</div>);
|
||
}
|