零后端个人导航站实战:用JSON与静态托管构建信息地图式书签管理

每个人都有过这样的经历:收藏夹里躺着一百多个网址,平时根本不会打开,真到要找某个工具的时候,又得在浏览器历史记录里翻半天。我做了个项目,名字叫“lingbot-map”,简单说就是一个个人网络书签地图站,把散落在各处的链接、灵感、工具、资料按自己的方式重新组织成一张网。这个项目做完之后,我在产品设计、信息架构和前端性能几个方面积累了不少想法,这篇文章完整记录下来,希望对做同类个人站、导航站或者知识库的朋友有参考价值。

项目本身并不复杂,没有引入高大上的框架,也没有做后端服务,数据全放在前端,打开页面就能用。但正因为简单,反而腾出了很多精力去打磨信息组织和交互细节。适合想要搭建个人网址导航、书签管理、资源聚合页面的开发者,也适合正在犹豫“静态站到底该怎么做”的初级前端。

1. lingbot-map的项目由来:从一百多个书签到一张网

先交代一下背景。我做这个项目的原因,是因为浏览器原生书签实在撑不住我的使用习惯了。书签栏只有一排,收藏夹越来越大,网址存进去之后基本就是“已经收藏了,等于已经用过了”,之后再也没有点开过。最痛苦的是跨设备同步,工作电脑、家里电脑、手机三套环境,书签经常对不上,有的在收藏夹里躺了两年,我甚至不知道它还在。

一开始我想过用现成的书签工具,比如一些基于标签的导航站服务。但我很快意识到一个问题,我需要的其实不是“收藏工具”,而是“展示工具”。很多书签服务做得再复杂,使用的场景停留在“搜索一个地址然后跳转”,但我更在意的是:打开一个页面,一眼就能看到我领域内需要用到的全部资源分布,它们之间是什么关系,哪些是核心,哪些是配套工具。这听起来像地图,而不是列表。于是“lingbot-map”这个想法就出现了,把它做成一张以自己为圆心、按主题分层展开的信息地图。

这个项目的核心理念很简单:要么不做,要做就做成自己每天打开三遍的都首页。两次迭代测试之后,我把整个项目的定位固定下来,它的核心能力可以概括成三条:快速收录、直观展示、低维护成本。快速收录指的是我能用最少的操作把一个新链接加入系统;直观展示指的是页面结构对信息的组织度足够高;低维护成本指的是我不需要定时花精力去维护这个系统,它自己用静态托管躺着就行。

当然,项目取名“lingbot-map”也有一点私心。“lingbot”原本是我给另一个小工具起的代号,后来觉得这个代号很适合延续下来。“map”则直接点明了本质,网页上的卡片不是简单的网格排列,而是有位置、有关系的信息地图。

1.1 痛点:收藏夹变坟场

收藏夹变成坟场,这个过程几乎是静默发生的。刚开始用浏览器书签时,我还会把每个链接好好分类,工具一栏、文档一栏、灵感一栏。但几个月之后,新建的书签数量超过了整理次数,新收藏的网址找不到合适的定位,就直接丢进“其他书签”。再往后,书签多到我连分类都懒得看,只凭记忆搜索。

这个问题如果只靠“多分类”是解决不了的。因为书签列表本质上是线性结构,我看到的永远是“一行文字加一个小图标”,它对大脑的刺激非常有限。真正的效率在于把信息放到合适的位置上,页面一打开,视觉重心自然落在最重要的板块,而不是每次从头扫到尾。

所以在正式开发前,我统计了自己收藏夹里那一百多个网址,按使用频率和主题做了聚类。结果很有说服力:大概 70% 的资源日常根本用不到,但它们占据了书签栏大部分空间,反而把真正高频的工具挤到了视觉盲区。这个发现直接决定了 lingbot-map 的第一版设计原则——不要展示所有,只展示值得被看到的。

1.2 产品定位:不是又一个网址导航

市面上的导航站,比如各种“好用网站合集”,通常做法是把一大堆网址按大分类平铺开,页面长得像信息瀑布。这种站的问题不在内容质量,而在结构上。它是以“网站”为中心,而不是以“使用场景”为中心。访客进入页面之后要自己判断“我是来干什么的”,然后再去找对应的分类。

我给 lingbot-map 设定的目标完全相反,以“使用场景”为中心。页面上的每个区块,对应的是我日常要做的一件事,比如“速记与灵感收集”“技术检索”“图像处理工具”“聊天与搜索”。当我需要记录一条临时想法时,我不会去记“速记工具在哪个分类下”,我只需要知道“去左上角找便签”。这种转变看着细小,实际体验差别巨大,它把页面从一个存放链接的仓库,变成了一张决策地图。

有人可能会问:这不就是一个带分组的书签页吗?区别在于分组方式。普通书签分组往往是“按内容类型”,比如“工具”“资源”“文章”,这种分法的毛病在于类型本身不代表用途。而 lingbot-map 的分组是“按任务”,把“做图时需要的一系列参考和工具”放一起,把“写文章时要用到的检索和素材”放一起,这样进入页面之后不需要二次思考。

2. 数据模型与分层设计:先想清楚怎么组织信息

很多个人项目一上来就写界面,把数据直接硬编码在模板里,做到后边改动一次想哭一次。lingbot-map 的第一版确实也踩了这个坑,但很快我做了重构。核心原则只有一条:界面和内容必须完全分离。界面上展示什么、卡片怎么排、显示文案是什么,都不是硬编码的问题,而是数据层应该决定的。

我最终采用的是纯前端架构:一个 JSON 文件负责全部数据,页面运行时加载这个数据,再根据数据结构渲染出地图区块。选择这种方案的理由很直白:不需要搭服务器,不需要数据库,GitHub 或者任何静态托管平台都能直接跑起来,内容更新只需要改数据文件后提交推送。

JSON 的数据结构我设计成三层:

  • 第一层是“板块”(section),对应页面上的每个大区域,比如“设计资源”。
  • 第二层是“分组”(group),板块下面再拆成的组,比如“配色工具”“字体工具”“图标素材”。
  • 第三层是“条目”(item),每个具体网址包含标题、链接、描述、图标。

层级并不是我拍脑袋定的,它直接映射了我的使用习惯。板块对应的是“当我打开页面时,我打算做哪类事情”,分组对应的是“做这件事时的具体子步骤”,条目就是那个具体工具本身。如果层级再少一层,信息会显得扁平,分组不够;如果层级再多一层,维护成本就会上升到不太需要的地步。

2.1 节点数据格式:JSON 还是 CMS

在数据格式选择上,我也认真比较过几类方案。第一种是直接用 JSON 文件,像上面说的这样;第二种是接一个无头 CMS;第三种是自己在本地维护一个 SQLite 数据库,再通过构建脚本生成静态数据。

无头 CMS 的优势在于可以通过网页端可视化编辑,甚至支持多人协作,但对单人项目来说太重了。我需要的只是快速加一个标题和链接,开个 CMS 管理面板反而多了一层心智负担。SQLite 方案适合数据量极大、查询条件复杂的场景,对一个几十上百条链接的导航站来说,用上了大炮打蚊子。

最终选 JSON 还有一个隐藏好处:Git 变成本能的版本管理器。我对每条链接的任何修改,在 git 历史里都有迹可循。某天某个链接失效了,想回退到原来的配置,一条 git checkout 就完事了。这种透明度和可回溯性,是数据库方案很难给的。

实际 JSON 文件的格式并不复杂,大致结构如下:

{ "sections": [ { "id": "design", "title": "设计资源", "subtitle": "做图之前先来这里逛一圈", "groups": [ { "name": "配色", "items": [ { "title": "Coolors", "url": "https://coolors.co", "desc": "快速生成配色方案", "icon": "https://coolors.co/favicon.ico", "tags": ["color", "quick"] }, { "title": "Color Hunt", "url": "https://colorhunt.co", "desc": "设计师分享的调色板集合", "icon": "https://colorhunt.co/favicon.ico", "tags": ["color", "inspire"] } ] } ] } ] }

每个条目我都存了一个 icon 字段,用于在卡片上显示站点的小图标。其实如果省事的话,也可以让前端根据域名自动拼接 favicon,比如https://域名/favicon.ico,但这种方式经常遇到部分站点不提供默认入口图标的情况。所以我更倾向显式存储图标字段,虽然维护时多写一行,但展示效果稳定得多。

2.2 分类粒度与标签体系

分类粒度是我反复调整最久的地方。最初分成 4 个板块,每个板块下面只有两三个分组,结果打开页面后发现,视觉上很空,而且部分板块之间有明显的重叠。比如“灵感参考”和“设计资源”里都放了 Behance,这让我意识到一个问题:一个链接可以归属多个场景,但卡片只能放在一个物理位置。

为了解决这种场景重叠,我在数据模型里增加了“tags”字段。每个条目除了归属在主分类下,还能打上多个标签。页面上的主体布局仍然按板块和分组排列,但顶部增加了一个全局标签筛选条。点击某个标签后,页面上所有非对称卡片会做模糊过滤,只展示包含此标签的卡片。这样一来,同一个链接可以同时出现在“设计资源 - 灵感参考”和“图片素材 - 案例收集”两条检索路径下,不会丢失信息。

标签体系不宜过于复杂。我定义了一批有限的标签,比如quick(高频工具)、free(免费)、inspire(灵感型)、tutorial(教程型),这样筛选时心智负担小,也不会出现一个链接打十个标签的情况。实践下来,一条链接平均打 1-2 个标签最合适,多于 2 个就说明分组本身可能需要调整。

3. 前端实现的关键决策:地图化呈现的几种路径

数据模型定好之后,真正的挑战在前端。让它看起来不只是一个普通网格导航站,而是有“地图感”,这个目标说起来容易,做起来并不简单。我尝试过几种路径,最终确定的方案是几轮取舍的结果。

第一种思路是使用真正的可视化地图布局,比如 Canvas 或 SVG,把每个节点变成平面上的坐标点,支持拖拽和缩放。这种方案视觉效果最炫,但问题也最多:节点之间的间距需要手动调,数据一多就容易交错,移动端适配困难。而且这种方案对于“扫码找到一个链接”这种任务来说,效率可能反而比传统网格低。

第二种思路是使用网格流式布局,配合锚点和分组标题。这个方案稳定可靠,也是很多导航站采用的。问题在于立体感不足,滚动操作太多,一眼看不完整个板块之间的关系。

第三种思路,也是我最终选定的方案:网格布局 + 分组卡片 + 可视层级。页面保持基于网格的自然流动布局,但每个分组本身是一张卡片,卡片有明确的标题和底色,板块之间用大号标题和分隔线拉开距离。视觉上,进入页面后能明显感觉到“色彩模糊的小地图块”,每个板块就像地图上的片区。

3.1 前端框架与渲染逻辑的选择

考虑到项目规模,我没有引入 React 或 Vue,而是用原生 JavaScript 加一个小型状态管理对象搞定。这不是为了刻意追求极简,而是我发现:对于“一次加载,渲染完毕,之后不频繁交互”的页面,框架带来的虚拟 DOM 更新优势完全用不上。

渲染逻辑分成三步:首先用fetch加载 JSON 数据,然后按板块顺序生成对应的 DOM 容器,最后遍历分组和条目,填充卡片。整个渲染过程一次性完成,后续交互主要依赖事件委托。

事件委托是这里的一个小技巧。每个卡片按钮不单独绑定点击事件,而是在整个容器上绑定一个 click 监听,通过>const state = { rawData: null, activeTag: null, searchQuery: "" }; document.querySelector(".map-container").addEventListener("click", (e) => { const card = e.target.closest(".map-card"); if (!card) return; window.open(card.dataset.url, "_blank"); });

这种简洁逻辑还有一个好处:对于后来想看我代码的人几乎没有学习成本。

3.2 卡片交互与地图感的营造

要让网格布局产生“地图感”,核心在间距、层次和动效三个要素。

间距方面,分组之间的留白必须明显大于卡片之间的留白。我实验下来,卡片之间 8px、分组卡片内边距 16px、板块间隔 32px 是比较舒服的比例。视觉上,同组的卡片有明显的“聚合感”,而不同板块之间不会混作一团。

层次方面,每组卡片背景使用近似于页面底色但略深一点的色块,板块标题用更大的字号和图标作为起始引导。卡片本身不添加边框,只用极浅的投影来区分层级。整个页面颜色控制在一个低饱和度的体系内,既不会信息过载,也避免把导航页做得像广告牌。

动效方面,我没有做花哨的入场动画,只保留了一个 150ms 的悬浮反馈:卡片在鼠标悬停时上浮 2px,颜色轻微变深。这个反馈时间不长不短,恰好让用户感知到“这里可以点”。对于移动端的触摸,用hover不够自然,所以我还加了一个a:active状态,按下时卡片会轻微下沉。

3.3 搜索与筛选的实现思路

页面再好看,如果信息多了找不到链接,也是白搭。所以我在顶部预留了一个搜索框。搜索逻辑不复杂,但有一个点值得说:搜索之前先做数据归一化。也就是将所有标题、描述、标签统一转成小写,再做子串匹配。这样搜索“figma”时,即使数据里存的是“Figma”,也能正确命中。

筛选逻辑分两种模式叠加:标签筛选与关键词搜索。两者是“与”的关系,也就是说同时满足标签和搜索条件才显示。实现时,我维护一个visibleSections对象,在每次状态变化后重新计算当前哪些分组、哪些卡片应该保留。所有隐藏元素使用hidden属性,而不是动态删除 DOM 节点,这样再次切换筛选条件时,恢复显示不需要重新渲染,效率更高。

这里分享一个我实测中很有用的做法:搜索框的输入事件做个 200ms 的防抖。不是所有输入都需要立即触发筛选,比如用户打字速度很快,中间停顿的那几个中间状态一点儿意义都没有。防抖之后,筛选函数只在用户停顿超过 200ms 才执行,体感上反而是“更跟手”的。

4. 部署、数据备份与更新流程

lingbot-map 的部署非常简单,因为它是标准静态网站。构建产物只有 HTML、CSS、JS、JSON 和少量图片资源,没有任何服务端依赖。我可以非常确定地说,这套方案在部署层面是“零运维”的,只要托管平台不挂,服务就不会挂。

我选择的是将其托管在支持静态页面的平台,利用 Git 仓库作为触发源。每次往主分支推送变更,平台自动构建并发布。部署流程被压缩到极致:改完数据文件,执行一次git push,十几秒后线上就是最新版本。

但这不意味着不需要考虑数据安全。因为整个站点的内容全部来自 JSON 数据文件,它是一个人的知识地图,丢了会很可惜。所以我把数据备份提到了第一优先级。

4.1 静态部署步骤与环境配置

如果你也想快速搭一个类似站点,环境准备其实非常少:

  1. 安装 Node.js(用于本地启动开发预览)。
  2. 下载或克隆 lingbot-map 项目骨架,目录结构大致为:
lingbot-map/ ├── docs/ │ ├── index.html │ ├── assets/ │ │ ├── style.css │ │ ├── app.js │ │ └── data/ │ │ └── map.json ├── tools/ │ └── check-links.js └── README.md
  1. 本地使用npx serve docs启动端口预览。
  2. 修改map.json,加入自己的网址与板块。
  3. 推送到远程仓库,由静态托管平台自动执行部署。

这些步骤看起来多,其实实际操作的熟练以后,从“拿到一个新域名”到“上线一个自己的导航站”,半小时内就能完成。关键在做项目骨架时把目录和数据层分离清楚,后续添加链接就只是“编辑 JSON + 推送”。

4.2 图片资源与 favicon 的离线缓存

导航站虽然内容以链接为主,但图标和缩略图还是一笔不小的外部资源。我最早直接使用站点自己的 favicon 链接,后来发现部分网站的图标服务不稳定,有的图片加载很慢,有的直接断开。这个问题在网速较慢时尤其明显,可能整页卡片有一排小叉号,观感很差。

我的解决方案是:将常用站点的 favicon 统一下载,缩放到 32x32 或 64x64,放到本地assets/icons/目录下。数据字段中的 icon 值改为相对路径。只有极少数不常用或不方便下载图标的站点,才保留外链。这样首屏加载速度快很多,也彻底摆脱了外部图片服务的波动。

需要注意,favicon 尺寸不必贪大。导航卡片上的图标展示区域通常只有 16 或者 24 像素,下载 512 像素的图标完全是在浪费资源。官方 favicon 一般都有 32 或 64 的版本,抓下来之后反而比大图标更适合展示。

如果有站点没有提供公开的 favicon,可以用一种取巧的办法,直接从页面 HTML 的link标签里解析。写一段简单的脚本遍历已知 URL,抓取页头中有rel="icon"的地址,能省去不少手动找图标的功夫。

4.3 链接有效性检查

纯静态站的另一个隐患是链接失效。链接存了三年之后,原来的站点关闭或改版了,用户点过去看到的是 404。这种问题不能靠人工定期检查,所以我写了一个小的 Node 脚本,放在tools/check-links.js中,用来自动化检查数据文件中的所有外链。

脚本原理非常简单:

  1. 读取map.json。
  2. 遍历所有条目的url。
  3. 用 Node 的http/https模块发起HEAD请求,捕捉状态码。
  4. 如果请求失败或状态码大于 400,把对应的条目输出到一个待检列表。

考虑到部分站点会屏蔽非浏览器客户端,脚本里设置了一个常规浏览器的 User-Agent,超时时间设为 10 秒,防止某个卡住的请求拖慢整个检查流程。这个脚本我设置成每两周手动跑一次,偶尔也会配合错误日志做一次全量修复。

5. 踩过的坑与实测调优手记

说句实在话,这个项目功能不复杂,但越简单的东西越容易栽进各种意想不到的坑里。下面这几个坑,有的是我自己慢慢排查出来的,有的则是走了很大弯路之后才发现的。记录在这里,希望能帮后来者少花点时间。

5.1 绝对路径与相对路径的坑

第一个坑与路径有关。最开始我把 JSON 数据中的图标路径写成相对路径assets/icons/xxx.png,本地测试一切正常。但部署之后,部分页面刷新就出现图标丢失的问题。排查发现:当前页面 URL 如果是https://site.com/assets/icons/xxx.png,相对路径还能解析;但如果通过子路径访问,比如个人站挂在https://username.github.io/lingbot-map/下,相对路径的解析基准就完全不同了,导致图标全部 404。

解决办法是在 HTML 里写一个<base>标签,或是在 JavaScript 中通过一个定位函数拼接完整路径。我采用的是第二种方式:在app.js顶部声明const ICON_BASE = window.location.origin + window.location.pathname,渲染时用ICON_BASE + iconPath拼出完整路径。这样不管站点部署在哪个层级,资源都能正确加载。

5.2 搜索输入法导致的丢失问题

第二个坑出在搜索框。中文输入法场景下,如果直接监听input事件,用户在拼音组合阶段就已经触发了多次筛选,导致每次键入字母时,界面卡片瞬间消失又恢复,非常飘。这个问题的本质是:拼音输入法产生的文本是“中间态”,不应该作为过滤依据。

解决思路很巧妙:监听compositionstart和compositionend事件。当compositionstart触发时,设置一个isComposing标志;只有在该标志为false时才执行筛选逻辑。更新后的代码如下:

let isComposing = false; searchInput.addEventListener("compositionstart", () => { isComposing = true; }); searchInput.addEventListener("compositionend", (e) => { isComposing = false; handleSearch(e.target.value); }); searchInput.addEventListener("input", (e) => { if (!isComposing) handleSearch(e.target.value); });

这个小改进对日常使用感受提升非常明显,尤其是大量的时间是在过滤中文关键词的场景,几乎完全消除了“筛选抖动”。

5.3 移动端适配与触摸延迟

第三个坑在移动端。导航站在手机上的使用比例不低,但手机屏幕窄、手指粗,对小卡片的点击精准度要求很高。第一版设计没有做特别的移动端处理,卡片的高度和间距沿用桌面端数值,结果实测在手机上经常点到旁边卡片。

经过几轮调试,最终确定移动端阈值:屏幕宽度小于 640px 时,卡片最小高度增大到 48px,卡片内边距也整体上调,保证每个链接都有明确的“热区”。同时关闭了卡片上的部分 hover 效果,因为移动端不存在鼠标悬停,hover 效果反而会影响触摸响应的视觉反馈。

相对复杂的问题在于触摸延迟,移动端浏览器在触摸之后通常会等待 300ms 再触发点击事件,用来判断用户是否要双击缩放。现代浏览器在设置了正确viewport并且使用width=device-width之后会移除这个延迟,但仍有些旧版本浏览器会保留。保险起见,我在 CSS 中加入了:

html { touch-action: manipulation; }

这行代码告诉浏览器:这个元素上不需要双击缩放,触摸时可以立即触发点击。对导航类页面来说,每次点击节省 300ms,累计起来的体验差异非常明显。

5.4 数据更新与缓存策略

静态站虽然部署简单,但更新之后有个常见的隐患:浏览器缓存。JSON 数据文件和 JS 文件一样,会被浏览器缓存,导致页面部署成功后,用户端看到的仍是旧数据。这个问题在 Safari 里尤其顽固。

我的处理方法是为静态资源增加带 hash 的版本号:每次构建时生成一个随机版本号,附加到 JS 和 JSON 资源的请求地址上。由于整个页面是静态托管,没有构建步骤,实现方式就简单粗暴一些——在 HTML 中写一个固定的版本号,每次手动更新数据文件时顺手改一下版本号,资源请求 URL 会变化,自然绕过缓存。

<script src="app.js?v=3"></script>

这个办法虽然原始,但极其实用。版本号不需要自动化,因为内容更新频率本身就低,每次改动记住 +1 就行。

6. lingbot-map的边界探索与扩展方向

项目做到这个阶段,基本功能已经稳定,剩下的问题不再是“还能加什么”,而是“什么不应该加”。个人导航站最容易犯的毛病就是功能无限膨胀:今天想加备忘录,明天想加 RSS 阅读器,后天想加日程管理。每加一个新模块,维护成本和界面复杂度都会上升,最终背离“快速找到链接”这个初衷。

所以我在功能边界上做了很明确的取舍。凡是“需要频繁交互、涉及多步操作”的功能,通通不做,交给更专业的在线工具;凡是“静态展示、信息组织”层面的能力,才会考虑继续优化。按照这个原则,下面几个方向是我认为比较有价值且不破坏架构的扩展。

6.1 分组折叠与个性化排序

现在所有板块默认全部展开,板块多了之后页面会变得很长。一个比较自然的升级是增加分组的折叠能力:每个板块标题可以点击折叠,折叠状态持久化到 localStorage。这样用户进入页面后,首先看到的是一级板块列表,需要哪个板块就展开哪个板块,信息架构更接近真正的“地图”。

折叠状态只需要用一个对象记录在 localStorage 里,形如:

{ "design": "collapsed", "code": "expanded", "reading": "collapsed" }

逻辑上并不复杂,但对页面观感的控制力会提升很多。而且折叠状态还能为用户提供“分区关注”的能力,比如今天聚焦写文案,就把写作相关板块展开,其他全部收纳起来。

6.2 链接评分与历史访问记录

另一个我一直在犹豫要不要做的方向,是给每个链接记录访问次数和评分。理论上,如果我能知道哪些卡片被频繁点击,就可以把最高频的链接自动提升到每个板块的开头位置,实现“使用时自动优化排序”。这个功能对纯前端项目也能实现,只需要在每次点击时向 localStorage 写入时间戳和计数。

但说实话,目前我的使用场景里数据量还不够大,几十个链接手动排序也没问题,自动排序反而让我对页面结构失去控制感。这个功能我暂时只做了一个最简单的“最近常用”标记,放在每个板块的开头,展示点击次数最多的三个条目。等将来收录更多链接之后,再决定要不要升级成完整的个性化排序算法。

6.3 多主题与自定义外观

外观层面,我准备做成“主题包”的机制。界面颜色不是硬编码,而是通过 CSS 变量定义,换主题只需要切换一套变量值就行。这算是对未来可能的“发一个小产品给别人用”的提前准备。毕竟每个人对导航站的审美需求差别很大,有人喜欢极简白,有人喜欢深色模式,还有人可能想要一点毛玻璃质感。通过 CSS 变量来切换主题,不影响逻辑层,维护成本也低。

实现方式其实非常简单:

:root { --bg-color: #fdfbf7; --card-bg: #ffffff; --text-main: #1f2937; --accent: #4f46e5; } .theme-dark { --bg-color: #111827; --card-bg: #1f2937; --text-main: #f9fafb; --accent: #818cf8; }

页面所有具体颜色只引用这些变量,主题切换脚本只需切换document.body的 class。我还打算把主题选择同样存到 localStorage,这样访问者自己也可以切,而不用动代码。

6.4 输入体验的优化:快速添加模式

现在往 lingbot-map 里加链接,流程是手动编辑 JSON。虽然技术上是常规操作,但对非开发人员,或者偶尔在手机上想添加链接的时候,这个过程还是太别扭。我设想过一个“快速添加”入口:在页面右上角放一个加号按钮,点击弹出小面板,只需要填标题和 URL,数据会自动暂存到一个pending.json列表里。

由于没有后端,“自动写入文件”这件事纯前端做不到。所以我做了一点变通:快速添加面板生成一段 JSON 代码,复制下来然后粘贴进map.json中即可。虽然还是手动操作,但至少省去了打开代码编辑器、寻找正确分类的麻烦。

如果未来这个功能用得频繁,我可能会再加一个脚本:读取一个专门存放临时条目的 JSON 文件,执行时自动去重并合并进主数据。这样就真的做到了“添加链接只需要推一次代码”的水平。

写在最后

做 lingbot-map 这个项目,最大的收获不是学会某个具体技术,而是理顺了“个人工具类产品”的设计思路。信息组织这件事,很多时候比视觉精美更重要。它需要你认真思考自己的使用场景,提炼出真正的核心需求,再围绕这个需求做大量减法。

我现在每天打开这个页面,已经不再把它当作一个“需要维护的项目”,而是当作一个真正在使用的效率工具。随着我工作方向的变化,板块内容还在持续调整:新增了 AI 工具板块,弱化了一些低频设计参考站,顺便把高频的搜索工具往页面中心挪了挪。这就是自己的信息地图,它有生命,会随着我的使用习惯一起进化。

如果你也想做类似的个人导航页,我的建议只有一条:先从整理自己的收藏夹开始,扔掉那些两年都没打开过的链接,只留下真正高频和必要的资源,然后再开始写代码。页面能不能感动自己,往往在技术之外就已经决定了。

相关新闻

用矩阵思维管理技能:从清单到可调用能力单元的系统方法

用矩阵思维管理技能:从清单到可调用能力单元的系统方法

1. 从“skills”这个词说起&#xff1a;它到底指什么“skills”这个词最近频繁出现在各种讨论里&#xff0c;很多人第一反应是“技能”这个翻译&#xff0c;但如果你只把它理解成简历上那一栏“掌握技能”&#xff0c;那就把它的价值看窄了。我做了十多年项目&#xff0c;带过团…

2026/10/21 5:45:17 阅读更多 →
HDFS性能优化10个实战技巧:从块大小到NameNode调优

HDFS性能优化10个实战技巧:从块大小到NameNode调优

HDFS 跑得慢&#xff0c;很多时候不是硬件不行&#xff0c;而是默认参数和你的数据特征不匹配。我在一线维护过不少大数据集群&#xff0c;被问到最多的问题基本集中在几类&#xff1a;写入吞吐上不去、小文件太多把 NameNode 内存吃光、读大文件时明明网络带宽很足却还是慢吞吞…

2026/10/21 5:45:17 阅读更多 →
大模型微调实操指南:从数据准备到训练评估避坑

大模型微调实操指南:从数据准备到训练评估避坑

大模型微调这个话题&#xff0c;这两年都快被聊烂了&#xff0c;但每次看到有人说“简单得像打开浏览器”&#xff0c;我还是会忍不住想接一句&#xff1a;话是没错&#xff0c;但前提是你知道浏览器地址栏在哪、要输什么网址&#xff0c;以及打开之后点哪里。微调的门槛确实降…

2026/10/21 5:45:17 阅读更多 →

最新新闻

WebDAV Android客户端开发:Kotlin协程实现协议级兼容

WebDAV Android客户端开发:Kotlin协程实现协议级兼容

简介&#xff1a;WebDAVClient是一款基于Kotlin开发的轻量级Android端WebDAV协议客户端&#xff0c;面向Android开发者及需要在移动设备上管理远程WebDAV服务器文件的技术人员&#xff0c;解决跨平台文件同步、远程编辑与协作访问等实际需求。资源包共149个文件&#xff0c;涵盖…

2026/10/21 6:26:35 阅读更多 →
Agentory:面向AI Native时代的工作操作系统

Agentory:面向AI Native时代的工作操作系统

1. 项目概述&#xff1a;这不是又一个“AI工具集合”&#xff0c;而是一套可生长的工作操作系统“Agentory&#xff1a;个人与团队的AI Native工作台”——光看这个名字&#xff0c;很多人第一反应是&#xff1a;“哦&#xff0c;又一个把ChatGPT、Claude、Perplexity打包进网页…

2026/10/21 6:26:35 阅读更多 →
SwiftUI Agent-Skill架构:从零搭建模块化App骨架

SwiftUI Agent-Skill架构:从零搭建模块化App骨架

很多人拿到新项目标题的第一反应是上网搜教程、看别人的轮子&#xff0c;但我习惯先拆名字。SwiftUI-Agent-Skill 这三个词放在一起&#xff0c;其实已经把一套 App 的核心架构讲完了&#xff1a;SwiftUI 管界面层&#xff0c;Agent 管决策和调度&#xff0c;Skill 是一堆可以被…

2026/10/21 6:26:35 阅读更多 →
gcc离线安装完整指南:从RPM收集到本地yum源批量部署

gcc离线安装完整指南:从RPM收集到本地yum源批量部署

简介&#xff1a;在无法连接互联网的服务器、学校机房或边缘设备中&#xff0c;搭建编译器工具链常因缺少依赖而失败。这份面向红帽系列Linux 6系统的GCC离线安装包共包含十二个文件&#xff1a;十一份软件安装包与一份安装脚本。其中十一份安装包覆盖基础编译器、C编译器、预处…

2026/10/21 6:26:35 阅读更多 →
2026年09月总结及随笔之十一年

2026年09月总结及随笔之十一年

1. 回头看日更坚持了1369天。读《数据可视化》更新完成读《数据架构知识体系指南》开更并持续更新中2023年至2025年12月底累计码字3054280字&#xff0c;累计日均码字2786字。2023年至2026年09月底累计码字3916512字&#xff0c;累计日均码字2860字。2026年09月码字90370字&…

2026/10/21 6:26:35 阅读更多 →
分布式电源选址定容的多目标优化:从帕累托前沿到最优折中解

分布式电源选址定容的多目标优化:从帕累托前沿到最优折中解

在分布式能源&#xff08;分布式光伏、小型风机、储能、燃气轮机等&#xff09;接入配电网的项目里&#xff0c;说白了大家最关心的就两件事&#xff1a;在哪建、建多大。也就是选址和定容。这两个问题一旦定下来&#xff0c;直接决定了电网改造投资、运行损耗、电压质量和供电…

2026/10/21 6:25:34 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →