惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
爱范儿
爱范儿
GbyAI
GbyAI
博客园 - 叶小钗
Last Week in AI
Last Week in AI
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
云风的 BLOG
云风的 BLOG
C
Check Point Blog
H
Help Net Security
P
Proofpoint News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
大猫的无限游戏
大猫的无限游戏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog RSS Feed
Y
Y Combinator Blog
U
Unit 42
T
Tailwind CSS Blog
MyScale Blog
MyScale Blog
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

姓王者的博客

Linux用户Secure Boot自主维护指南 | 姓王者的博客 MAD Bugs 已经开始——关于信息安全的军备竞赛 | 姓王者的博客 解决钉钉Dingtalk无法在Linux新版内核上启动问题-修复可执行栈错误 | 姓王者的博客 突发:GitHub 正遭受大规模 Issue 赌博广告轰炸 | 姓王者的博客 Ubuntu26.04-beta体验:坚毅浣熊! | 姓王者的博客 fakeclaw装作龙虾发贴吧 | 姓王者的博客 找回12年前的QQ记忆 | 姓王者的博客 在Linux上玩Flash网页游戏-洛克王国 | 姓王者的博客 Copilot将使用交互数据来训练 | 姓王者的博客 重要通知-请更新我的GPG公钥 | 姓王者的博客 为了自由Android | 姓王者的博客 GPL"2,3"事 | 姓王者的博客 短文-对VitePlus的一点🤏小贡献 | 姓王者的博客 Bing收录没了?亲测有效的快速恢复指南 | 姓王者的博客 解决桌面设备二维码快速识别的工具-ClipQR | 姓王者的博客 解决 Nautilus 自定义终端插件安装依赖问题 | 姓王者的博客 OpenClaw 该熄火了 | 姓王者的博客 Vite8 - 统一的基建开始 | 姓王者的博客 Astro 6 推出啦 | 姓王者的博客 ubuntu的openvpn异常暂停推送更新 | 姓王者的博客 Ubuntu 24.04 安装 Win10 虚拟机 | 姓王者的博客 ESA-后记:热爱阿里云 | 姓王者的博客 Moonbit 0.8.0 重大发布,我也要改一下我的包 | 姓王者的博客 ESA Pages 边缘开发大赛获奖 | 姓王者的博客 Astro: 优化katex,mermaid和灯箱使用 | 姓王者的博客 从edgeone迁移到esa | 姓王者的博客 出租人类:AI时代的荒诞与真实 | 姓王者的博客 Astro 5.17构建性能优化实践:从18s到13s | 姓王者的博客 Moonbit License Checker 开发使用 | 姓王者的博客 Stalux Astro博客主题自荐 | 姓王者的博客
hexo-graph:新增分类树状图 | 姓王者的博客
作者:xingwangzhe · 2025-01-02 · via 姓王者的博客

🕒 阅读时间:2 分钟 📝 字数:739 👀 阅读量: Loading...

应该算是技术债:(

起因

issuse:分类跳转错误

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

问题代码出现在这一行

categoriesChart.on("click", function (params) {

window.location.href = "/categories/" + params.name;

});

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

过程

验证分类嵌套结构

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

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

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

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

建个对象

const categoryTree = {

name: hexo.config.title || "Categories",

children: [],

count: 0,

path: "",

};

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

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

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

`

....

${generateCategoriesTreeChart(categoryTree, darkMode, colorPalette)}

`;

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

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>

`;

}