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

推荐订阅源

W
WeLiveSecurity
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Cloudbric
Cloudbric
The Register - Security
The Register - Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives
G
Google Developers Blog
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园_首页
Last Week in AI
Last Week in AI
A
Arctic Wolf
B
Blog RSS Feed
J
Java Code Geeks
C
Cybersecurity and Infrastructure Security Agency CISA
I
InfoQ
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
有赞技术团队
有赞技术团队
S
Schneier on Security
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Vercel News
Vercel News
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans

姓王者的博客

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永久链接迁移到Astro | 姓王者的博客 再见👋 LeanCloud | 姓王者的博客 2025年终总结 | 姓王者的博客 许可合规-fancybox | 姓王者的博客 博客主题的软著下来了 | 姓王者的博客 友链图谱 - 汇聚千丝万缕的联系 | 姓王者的博客 chen-er 专为Chen式ER图打造的npm包 | 姓王者的博客 为什么我推荐你使用GPG来加密你的邮件 | 姓王者的博客 2025第三方客户端登录东北大学邮箱 | 姓王者的博客 好久没更新了,过去与未来 | 姓王者的博客 1024 重要的日子 | 姓王者的博客 再也不见Windows10 | 姓王者的博客 偷梁换柱,解决Ubuntu24.04安装Packet Tracer缺失依赖问题 | 姓王者的博客 中秋-来试试Moonbit吧 | 姓王者的博客 Obsidian使用体验 | 姓王者的博客 猪猪侠·一只老猪的逆袭 | 姓王者的博客 国庆日纪念 | 姓王者的博客 GNU 42周年,AI时代的自由精神 | 姓王者的博客 解决Linux上启动游戏总是默认English的情况 | 姓王者的博客 7x24:运维使命 | 姓王者的博客 Tauri2.x实现系统菜单导航Vue路由 | 姓王者的博客 计算机图形学-基本图形生成算法 | 姓王者的博客 数据库原理-关系数据 | 姓王者的博客 数据库原理-设计技巧 | 姓王者的博客 数据库原理E-R模型 | 姓王者的博客 旧忆 - 我曾玩过的游戏 | 姓王者的博客 再谈自由软件 | 姓王者的博客 可能解决Tauri多窗口应用阻塞问题 | 姓王者的博客 Xingwangzhe! Z-Library We miss you and we need your help | 姓王者的博客 计算机组成原理第二章 - 定点数与浮点数 | 姓王者的博客 计算机组成原理第一章 | 姓王者的博客 不小心写死循环窗口弹出了 | 姓王者的博客 美化Grub界面 | 姓王者的博客 计算机图形学-图形的表示与数据结构 | 姓王者的博客 计算机图形学绪论 | 姓王者的博客 为什么说,大学教育与社会脱节 | 姓王者的博客 VSCode Remote 远程连接服务器记录 | 姓王者的博客 解决Tauri2.x拖拽事件问题 | 姓王者的博客 新学期第一课《计算机图形学》报告 | 姓王者的博客 Tauri在GNOME46+上通知无效的临时解决方法 | 姓王者的博客 窃文者:未经授权转载我文章 | 姓王者的博客 GPG公钥分享文化 | 姓王者的博客 解决在ubuntu上,打包vscode插件问题 | 姓王者的博客 伪造squaremap的玩家显示 | 姓王者的博客 爆,沉浸式翻译泄露敏感信息 | 姓王者的博客 读书:《Free as in Freedom》——若为自由故 | 姓王者的博客 首页文章列表懒加载优化 | 姓王者的博客 Ubuntu 24.04 安装 Vivado 2018.3 | 姓王者的博客 腾讯Edgeone免费版体验 | 姓王者的博客 在 Ubuntu 上实现 Thetis FIDO U2F 密钥登录 | 姓王者的博客 Thetis物理密钥,为什么我们应该使用物理密钥 | 姓王者的博客 高考生过来看!教你精准转换录取位次! | 姓王者的博客 ubuntu无法访问windows磁盘问题 | 姓王者的博客 收信有感,防范钓鱼邮件 | 姓王者的博客 自由不止软件-记录一次zlib上传书籍 | 姓王者的博客 时隔两年,通关夺命邮差2 | 姓王者的博客 博客一周年了,竟然坚持了下来 | 姓王者的博客 Minecraft大电影:不建不散! | 姓王者的博客 是时候了解docker了! | 姓王者的博客 编译原理:LL(1)文法 | 姓王者的博客 编译原理:文法转换 | 姓王者的博客 离散数学:子群的陪集及拉格朗日定理 | 姓王者的博客 离散数学:半群,独异点 | 姓王者的博客 《人工智能生成合成内容标识办法》与个人博客--我们应该做什么? | 姓王者的博客 通识学习:形式语言与自动机,布尔代数与数进制 | 姓王者的博客 Webmapview:一个我的世界内置网页地图浏览Fabric模组 | 姓王者的博客 海岛机器人农场试玩 | 姓王者的博客 正则表达式学习 | 姓王者的博客 抓取个人博客文章目录到github主页 | 姓王者的博客 制作github贪吃蛇贡献图 | 姓王者的博客
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>

`;

}