前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >hexo-graph:新增分类树状图

hexo-graph:新增分类树状图

作者头像
姓王者
发布2025-02-28 17:46:57
发布2025-02-28 17:46:57
5500
代码可运行
举报
文章被收录于专栏:姓王者的博客姓王者的博客
运行总次数:0
代码可运行

起因

issuse:分类跳转错误

[bug]Category图表,如果点击的对象并不是最高级Category,而是次级Category,跳转链接的路径拼接就有问题。 · Issue #7 · codepzj/hexo-graph

问题代码出现在这一行

代码语言:javascript
代码运行次数:0
复制
categoriesChart.on('click', function (params) {
    window.location.href = '/categories/' + params.name;
});

很显然,当时我考虑的链接拼接有点草率了,忘记分类具有嵌套结构了。 这就会导致默认每个分类都是顶级分类,但实际上却不存在对应链接,这会导致404.于是我着手准备fix bugs

过程

验证分类嵌套结构

我好奇究竟具体是什么样的嵌套,于是用console.log()试了一下

2025-01-02-140650
2025-01-02-140650

虽然链接表现出来的是嵌套结构,但是实际上的对象是数组,欸我去,关键点在于_idparent两个属性,显然parent对应的值是父级分类_id属性,脑海里想到哈希,但是,我想到一个问题 a与a->b与b这三个分类中,最中间的嵌套和最后一个的分类会出现明显混淆,在柱状图显示的话,根本不能确定是跳转到/categories/a/b/还是categories/b/,尽管两者表现名称是一样的。

放弃原有,但保留结构,开辟新图

很明显,这种嵌套结构,非常适合树状结构。但实际上的一篇文章的分类是数组结构(我认为是平铺展开),看来得动手改一下了

建个对象

代码语言:javascript
代码运行次数:0
复制
const categoryTree = { 
    name: hexo.config.title || 'Categories', 
    children: [],
    count: 0,
    path: ''
};

根节点肯定是博客名,或者分类(应该没人不起博客名字吧:)

遍历文章的时候,遍历分类

代码语言:javascript
代码运行次数:0
复制

if(post.categories.length > 0) {
    let current = categoryTree;
    let path = '';
    post.categories.data.forEach((category, index) => {
        path = path ? `${path}/${category.name}` : `${category.name}`;
        let found = false;
        if (!current.children) {
            current.children = [];
        }
        // 找到现有的分类
        for (let child of current.children) {
            if (child.name === category.name) {
                child.count += 1;
                current = child;
                found = true;
                break;
            }
        }
        // 创建新的分类
        if (!found) {
            let newNode = {
                name: category.name,
                children: [],
                count: 1,
                path: path
            };
            current.children.push(newNode);
            current = newNode;
        }
    });
}

添加

先主函数添加一下要返回的html

代码语言:javascript
代码运行次数:0
复制
`
....
${generateCategoriesTreeChart(categoryTree, darkMode, colorPalette)}
`

接着就是Echarts树状图的具体实现,可以在Echarts官网找到示例,照抄,然后具体写一下功能样式就行:)

代码语言:javascript
代码运行次数:0
复制
function generateCategoriesTreeChart(categoryTree, darkMode, colors){
const data = JSON.stringify([categoryTree]); //只有一个根节点,就是博客标题
return `
    <script>
        const treeChart = echarts.init(document.getElementById('categoriesTreeChart'), '${darkMode}');
        treeChart.setOption({
            title: {
                text: '操作提示:单击展开分类,双击进入具体分类页面',
                textStyle: {
                    fontSize: 12,
                    color: '#999',
                    fontWeight: 'normal'
                },
                bottom: 0,
                left: 'center'
            },
            tooltip: {
                trigger: 'item',
                triggerOn: 'mousemove'
            },
            series: [{
                type: 'tree',
                data: ${data},
                initialTreeDepth: -1,    // 默认展开所有节点
                top: '5%',           // 调整上边距
                bottom: '5%',       // 调整下边距,为提示文字留出更多空间
                left: '0%',          // 调整左边距
                right: '0%',        // 调整右边距
                symbolSize: 15,      // 增大节点大小
                layout: 'orthogonal',// 使用正交布局
                orient: 'TB',        // 从左到右布局
                itemStyle: {
                    color: '${colors.categoryColors[0]}',
                    borderColor: '${colors.categoryColors[1]}'
                },
                label: {
                    position: 'bottom',
                    verticalAlign: 'middle',
                    align: 'center',
                    fontSize: 14,    // 增大字体
                    distance: 28,    // 标签与节点的距离
                    formatter: function(params) {
                        return params.data.name + (params.data.count ? ' (' + params.data.count + ')' : '');
                    }
                },
                leaves: {
                    label: {
                        position: 'top',
                        verticalAlign: 'middle',
                        align: 'center'
                    }
                },
                emphasis: {
                    focus: 'descendant'
                },
                expandAndCollapse: true,
                animationDuration: 550,
                animationDurationUpdate: 750,
                lineStyle: {
                    width: 1.5,      // 增加线条宽度
                    curveness: 0.5
                },
                nodeAlign: 'justify',// 节点对齐方式
                levelStep: 200       // 增加层级间距
            }]
        });
        let lastClickTime = 0;
        let timer = null;
        treeChart.on('click', function (params) {
            const currentTime = new Date().getTime();
            const timeDiff = currentTime - lastClickTime;
            
            // 清除之前的定时器
            if (timer) {
                clearTimeout(timer);
            }
            // 如果两次点击间隔小于300ms,认为是双击
            if (timeDiff < 300) {
                // 双击事件 - 跳转链接
                if (params.data.path) {
                    window.location.href = '/categories/' + params.data.path;
                }
            } else {
                // 单击事件 - 设置延时以区分双击
                timer = setTimeout(() => {
                    // 获取当前节点的展开状态
                    const expandedNodes = treeChart.getOption().series[0].data[0];
                    // 使用路径查找节点
                    const currentNode = findNodeByPath(expandedNodes, params.data.path || '');
                    if (currentNode) {
                        // 切换展开/收起状态
                        currentNode.collapsed = !currentNode.collapsed;
                        // 更新图表
                        treeChart.setOption({
                            series: [{
                                data: [expandedNodes]
                            }]
                        });
                    }
                }, 300);
            }
            
            lastClickTime = currentTime;
        });
        // 使用路径查找节点的新函数
        function findNodeByPath(tree, targetPath) {
            if (!targetPath) return null;
            
            // 如果是根节点
            if (tree.path === targetPath) {
                return tree;
            }
            // 递归查找子节点
            if (tree.children) {
                for (let child of tree.children) {
                    const found = findNodeByPath(child, targetPath);
                    if (found) return found;
                }
            }
            return null;
        }
    </script>
`;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起因
  • issuse:分类跳转错误
  • 过程
  • 验证分类嵌套结构
  • 放弃原有,但保留结构,开辟新图
    • 建个对象
    • 遍历文章的时候,遍历分类
    • 添加
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档