前言

欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/

项目开源地址:https://AtomGit.com/lqjmac/ele_lianjieleida

浏览器收藏夹能保存链接,但不擅长保存判断。

很多链接刚收藏时很重要,过几天再看,只剩下一串 URL 和一个模糊标题。到底为什么收藏、是否读完、能不能引用、下次什么时候复查,都很难说清。

所以这个工具叫 链接雷达

它不是替代浏览器收藏夹,而是给本地链接加上状态、摘要、复查点和导出能力。

适合这些场景:

  • 项目调研链接需要持续复查
  • 技术资料链接要整理引用说明
  • 文章素材链接要记录上下文
  • 本地收藏需要导出成 Markdown

链接管理的关键不是“存了多少”,而是“下一次打开时还能不能判断它有没有用”。

本文会按链接巡检思路拆解 Vue3 页面、状态模型、Electron 桥接、剪贴板复制、Markdown 导出和桌面端构建检查。

一、链接雷达先解决上下文丢失

1.1 收藏链接为什么还会丢

链接丢失不一定是 URL 不见了。

更多时候是上下文消失了。

丢失内容 具体表现 链接雷达的处理
收藏原因 不知道当时为什么保存 增加摘要和主题
阅读状态 不知道是否看完 增加待扫描、已确认、需复查
引用价值 不知道能不能写进文档 增加高亮和引用说明
复查时间 不知道何时再看 增加 nextReview

这也是为什么第一版重点不做浏览器同步。

先把链接判断记录做扎实更重要。

1.2 第一版的轻闭环

链接雷达先做五件事:

  1. 收进一个链接条目
  2. 标记链接类型和当前状态
  3. 写摘要、高亮和复查时间
  4. 复制链接说明
  5. 导出 Markdown 巡检记录

这条链路可以覆盖大部分个人资料整理场景。

二、文件分工围绕链接巡检

2.1 组件职责

文件 职责 链接场景里的作用
Home.vue 页面总装 组织雷达面板、列表和编辑区
LinkSidebar.vue 左侧筛选 搜索、类型、状态统计
LinkList.vue 链接列表 展示链接标题、状态、摘要
LinkEditor.vue 编辑区 URL、来源、主题、复查点
useLinks.ts 状态层 本地保存、筛选、排序
useNativeBridge.ts 桥接层 复制、保存、通知

链接工具的组件不需要复杂,但要把 URL、状态和摘要分开。

这样后续才方便做有效性检测。

2.2 页面不直接处理原生能力

const {
  copyText,
  exportMarkdown,
  notify,
  isNativeRuntime,
} = useNativeBridge();

页面只调用语义方法。

运行时差异由桥接层兜住。

三、页面结构图

3.1 链接雷达结构图

在这里插入图片描述

这张图表现的是链接从收集、扫描、确认到导出的过程。

3.2 为什么用雷达感界面

链接巡检像是在一堆信息源里扫描信号。

有些链接只是待读,有些需要复查,有些可以直接引用。

区域 作用 视觉重点
左侧 链接类型和搜索 暗色背景下保持清晰
中间 链接信号列表 状态边线要明显
右侧 链接判断 摘要和高亮优先
顶部 复制和导出 按钮不遮挡内容

暗色不是为了炫,而是让状态色更容易被识别。

四、数据模型要保存链接判断

4.1 字段设计

字段 含义 示例
title 链接标题 Electron 窗口文档
url 链接地址 https://www.electronjs.org
source 来源 官方文档、调研文章
topic 主题 桌面窗口控制
category 类型 待读链接、引用参考
state 状态 待扫描、已确认、需复查
summary 摘要 说明这个链接能解决什么
highlights 高亮 值得引用的结论
nextReview 下次复查 版本更新后

链接条目必须保存 URL,但列表层不能只显示 URL。

否则用户仍然要重新理解。

4.2 TypeScript 类型

export type LinkState = 'scanning' | 'confirmed' | 'reviewing';

export type LinkCategory =
  | 'reading'
  | 'signal'
  | 'followup'
  | 'reference';

export interface LinkItem {
  id: string;
  title: string;
  url: string;
  source: string;
  topic: string;
  category: LinkCategory;
  state: LinkState;
  keywords: string;
  summary: string;
  highlights: string;
  nextReview: string;
  content: string;
  pinned: boolean;
  archived: boolean;
  createdAt: number;
  updatedAt: number;
}

这份类型为后续做链接有效性检测预留了空间。

五、状态和类型要像巡检

5.1 文案映射

const categoryLabelMap: Record<LinkCategory, string> = {
  reading: '待读链接',
  signal: '线索页面',
  followup: '跟进流程',
  reference: '引用参考',
};

const stateLabelMap: Record<LinkState, string> = {
  scanning: '待扫描',
  confirmed: '已确认',
  reviewing: '需复查',
};

这些文案决定用户如何理解列表。

不要把链接工具写成普通笔记工具。

5.2 状态解释

状态 含义 下一步动作
待扫描 刚收进来,还没读 打开并补摘要
已确认 内容可引用或可继续用 复制说明或导出
需复查 时效、来源或结论待确认 设置复查点

状态越清晰,链接越不会堆成信息噪声。

六、默认链接要像真实调研

6.1 示例数据

export const seedLinks: LinkItem[] = [
  {
    id: 'link-electron-window',
    title: 'Electron BrowserWindow 配置',
    url: 'https://www.electronjs.org/docs/latest/api/browser-window',
    source: 'Electron 官方文档',
    topic: '桌面窗口控制',
    category: 'reference',
    state: 'confirmed',
    keywords: 'Electron,BrowserWindow,preload,contextIsolation',
    summary: '用于确认窗口尺寸、预加载脚本和安全隔离配置。',
    highlights: 'BrowserWindow 的 webPreferences 决定页面和主进程能力边界。',
    nextReview: 'Electron 版本升级后',
    content: '这条链接适合放在窗口初始化和桥接能力说明附近。',
    pinned: true,
    archived: false,
    createdAt: Date.now() - 3600_000,
    updatedAt: Date.now(),
  },
];

这条默认链接带有明确用途。

读者打开工具就能理解它为什么被收藏。

6.2 默认数据覆盖

默认数据至少覆盖:

  • 官方文档链接
  • 调研文章链接
  • 待扫描链接
  • 需复查链接
  • 可引用链接

这样才能测试不同状态的显示效果。

七、本地保存链接资料

7.1 读取数据

const STORAGE_KEY = 'lianjie-leida-links:v1';

function loadLinks(): LinkItem[] {
  if (typeof window === 'undefined') return seedLinks;

  try {
    const raw = window.localStorage.getItem(STORAGE_KEY);
    if (!raw) return seedLinks;

    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed : seedLinks;
  } catch {
    return seedLinks;
  }
}

链接工具也要防止本地缓存损坏。

不能因为一条坏数据让整个应用打不开。

7.2 新建链接

function createLink() {
  const now = Date.now();
  const link: LinkItem = {
    id: `link-${now}-${Math.random().toString(16).slice(2)}`,
    title: '新的链接',
    url: '',
    source: '',
    topic: '',
    category: 'reading',
    state: 'scanning',
    keywords: '',
    summary: '',
    highlights: '',
    nextReview: '',
    content: '',
    pinned: false,
    archived: false,
    createdAt: now,
    updatedAt: now,
  };

  links.value = [link, ...links.value];
  currentLinkId.value = link.id;
  schedulePersist();
}

新建时默认放到待读链接。

符合“先收进来,再判断”的工作流。

八、筛选要支持 URL 和主题

8.1 可见链接

const searchTerm = ref('');
const activeCategory = ref<'all' | LinkCategory>('all');

const visibleLinks = computed(() => {
  const keyword = searchTerm.value.trim().toLowerCase();

  return links.value
    .filter(link => {
      if (link.archived) return false;
      if (activeCategory.value !== 'all' && link.category !== activeCategory.value) {
        return false;
      }
      if (!keyword) return true;

      return [
        link.title,
        link.url,
        link.source,
        link.topic,
        link.keywords,
        link.summary,
      ].join(' ').toLowerCase().includes(keyword);
    })
    .sort(sortLinks);
});

搜索 URL 很重要。

有时候用户只记得域名,不记得标题。

8.2 排序规则

function sortLinks(a: LinkItem, b: LinkItem) {
  if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;

  const order: Record<LinkState, number> = {
    reviewing: 0,
    scanning: 1,
    confirmed: 2,
  };

  if (a.state !== b.state) {
    return order[a.state] - order[b.state];
  }

  return b.updatedAt - a.updatedAt;
}

需复查链接优先出现。

链接失效或结论过期的风险比普通待读更高。

九、列表卡片不要只放 URL

9.1 链接卡片

<template>
  <button class="link-card" :class="link.state" @click="$emit('select', link.id)">
    <span class="status">{{ stateLabelMap[link.state] }}</span>
    <strong>{{ link.title || '未命名链接' }}</strong>
    <span class="domain">{{ domain }}</span>
    <p>{{ excerpt(link.summary || link.content) }}</p>
  </button>
</template>

URL 可以显示域名,不必在卡片里完整铺开。

完整 URL 放进编辑区更合适。

9.2 解析域名

function getDomain(url: string) {
  try {
    return new URL(url).hostname;
  } catch {
    return '未设置链接';
  }
}

域名比完整 URL 更适合快速扫描。

如果 URL 不合法,也要给出稳定兜底。

十、编辑器补足链接上下文

10.1 编辑字段

<template>
  <article class="link-editor">
    <input :value="link.title" placeholder="链接标题" @input="updateField('title', $event)" />
    <input :value="link.url" placeholder="https://..." @input="updateField('url', $event)" />
    <input :value="link.source" placeholder="来源" @input="updateField('source', $event)" />
    <input :value="link.topic" placeholder="主题" @input="updateField('topic', $event)" />

    <textarea
      :value="link.summary"
      placeholder="写清这个链接解决什么问题"
      @input="updateField('summary', $event)"
    />
  </article>
</template>

链接编辑器的核心是补上下文。

URL 本身只是入口。

10.2 URL 校验提示

const isValidUrl = computed(() => {
  if (!currentLink.value?.url.trim()) return false;
  try {
    new URL(currentLink.value.url);
    return true;
  } catch {
    return false;
  }
});

初始版本不一定要阻止保存。

但可以提示 URL 是否规范。

十一、复制动作要区分目标

11.1 摘要复制

async function copyLinkSummary() {
  if (!currentLink.value) return;

  const text =
    currentLink.value.summary ||
    currentLink.value.highlights ||
    currentLink.value.url ||
    currentLink.value.title;

  const ok = await copyText(text);

  if (ok) {
    showFeedback('链接摘要已复制');
    await notify('链接雷达', '当前链接摘要已经复制到剪贴板');
  }
}

这个顺序适合资料流转。

如果用户只想复制 URL,后续可以再加单独按钮。

11.2 可扩展复制菜单

后续可以扩展三个复制入口:

  1. 复制 URL
  2. 复制引用说明
  3. 复制 Markdown 卡片

第一版保持一个按钮,先降低复杂度。

十二、导出 Markdown 像巡检记录

12.1 导出结构

function buildLinkMarkdown(link: LinkItem) {
  return [
    `# ${link.title || '未命名链接'}`,
    '',
    `- URL:${link.url || '未设置'}`,
    `- 类型:${categoryLabelMap[link.category]}`,
    `- 状态:${stateLabelMap[link.state]}`,
    `- 来源:${link.source || '未设置'}`,
    `- 主题:${link.topic || '未设置'}`,
    `- 关键词:${link.keywords || '未设置'}`,
    `- 下次复查:${link.nextReview || '未设置'}`,
    '',
    '## 摘要',
    '',
    link.summary || '暂无摘要',
    '',
    '## 关键高亮',
    '',
    link.highlights || '暂无高亮',
    '',
    '## 备注',
    '',
    link.content || '暂无正文',
  ].join('\n');
}

导出的链接记录可以直接进入调研文档。

12.2 安全文件名

function safeLinkFileName(value: string) {
  return (value.trim() || '链接记录')
    .replace(/[\\/:*?"<>|]/g, '-')
    .slice(0, 80);
}

URL 不适合直接作为文件名。

用标题更稳。

十三、桥接层处理剪贴板和保存

13.1 统一接口

export function useNativeBridge() {
  async function copyText(text: string) {
    if (!text.trim()) return false;

    if (window.desktopBridge?.copyText) {
      return window.desktopBridge.copyText(text);
    }

    await navigator.clipboard.writeText(text);
    return true;
  }

  return {
    copyText,
    exportMarkdown,
    notify,
  };
}

这层可以复用到其他桌面工具。

13.2 预加载能力

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('desktopBridge', {
  copyText: text => ipcRenderer.invoke('copy-text', text),
  saveMarkdown: data => ipcRenderer.invoke('save-markdown', data),
  showNotification: data => ipcRenderer.invoke('show-notification', data),
});

预加载脚本是页面和原生能力之间的边界。

相关概念可以参考 Electron contextBridge

十四、主进程加载本地资源

14.1 窗口配置

const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: 1225,
    height: 850,
    minWidth: 980,
    minHeight: 720,
    title: '链接雷达',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false,
    },
  });

  win.loadFile(path.join(__dirname, '..', 'dist', 'index.html'));
}

app.whenReady().then(createWindow);

窗口标题要和工具一致。

这能减少调试时的混乱。

14.2 构建命令

npm run build
test -f dist/index.html
find dist/assets -type f | sort

资源存在是运行的前提。

不要只看开发环境能不能打开。

十五、雷达视觉要克制

15.1 暗色主题

.link-radar {
  min-height: 100%;
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr) 380px;
  background: #0c131d;
  color: #edf9f7;
}

.link-card {
  border: 1px solid rgba(127, 255, 212, 0.22);
  border-left: 4px solid rgba(127, 255, 212, 0.65);
  background: #111b27;
  border-radius: 8px;
  padding: 14px;
}

.link-card.reviewing {
  border-left-color: #ffcc66;
}

暗色主题要注意对比度。

不要为了氛围牺牲可读性。

15.2 URL 显示

.domain {
  display: inline-block;
  max-width: 100%;
  color: #7fffd4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

长 URL 很容易撑破布局。

列表里只显示域名更安全。

十六、滚动和窗口体验

16.1 布局滚动

html,
body,
#app {
  width: 100%;
  height: 100%;
  margin: 0;
}

.link-radar {
  height: 100vh;
  min-height: 0;
  overflow: hidden;
}

.link-list,
.link-editor {
  min-height: 0;
  overflow: auto;
}

链接列表可能很长,编辑区内容也可能很多。

两边都要能滚动。

16.2 原生窗口栏

链接雷达不建议在页面里自绘窗口按钮。

原生窗口栏可以保证最小化、最大化、关闭行为稳定。

业务页面专注链接内容即可。

十七、发布前检查

17.1 功能检查

发布前检查:

  1. 标题和窗口都显示链接雷达
  2. 默认链接包含真实 URL
  3. 搜索标题、域名、关键词都能命中
  4. URL 不合法时页面不崩溃
  5. 复制摘要可以写入剪贴板
  6. 导出 Markdown 包含 URL 和状态
  7. 长链接不会撑破卡片

这些点覆盖链接工具的主要风险。

17.2 文章检查表

检查项 结果 说明
图片 通过 结构图可显示
表格 通过 覆盖对比、字段、检查
代码块 通过 覆盖类型、组件、桥接、样式
链接 通过 正文和资源区包含有效链接
投票引导 通过 文末保留

链接类工具最怕列表看起来很多,实际没有任何可复用判断。

十八、后续扩展方向

18.1 链接有效性检测

后续可以补:

  • 定时检查链接是否可访问
  • 记录 HTTP 状态码
  • 标记重定向
  • 保存网页标题快照
  • 给过期链接加复查提醒

这些能力可以逐步做。

18.2 引用管理

还可以增加:

  1. 复制 Markdown 引用
  2. 复制 HTML 链接
  3. 按项目导出链接包
  4. 给链接添加可信度评分
  5. 按主题生成调研报告

这会让链接雷达从收藏工具变成调研辅助工具。

总结

链接雷达解决的不是“保存 URL”,而是保存链接背后的判断。

通过状态、主题、摘要、复查点、复制和导出,它把本地收藏变成可巡检、可流转的桌面资料面板。

后续如果继续增强,我会优先做链接有效性检测和 Markdown 引用复制。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐