You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

56 KiB

万物收藏 — 产品需求文档 & 开发计划

版本: v1.0.0
技术栈: Nuxt 4 · SQLite · Drizzle ORM
文档日期: 2026-05-17
状态: 待评审


目录

  1. 产品概述
  2. 用户角色与场景
  3. 功能需求详述
  4. 数据库设计
  5. API 接口设计
  6. 前端页面与交互
  7. 技术架构
  8. 开发计划
  9. 非功能需求
  10. 验收标准

1. 产品概述

1.1 产品定位

万物收藏是一款面向个人用户的知识收藏与管理工具,核心理念是打通「收藏 → 整理 → 深读 → 创作」的完整闭环,解决以下核心问题:

  • 收藏内容分散在浏览器书签、手机备忘录、各类 App,无法统一管理
  • 收藏了但忘了,找不到,形成信息黑洞
  • 收藏只是「消费」,缺乏将其转化为个人输出的工具支持

1.2 产品愿景

让每一条收藏都能产生价值,成为用户的第二大脑

1.3 核心功能概览

模块 功能
收藏录入 支持网页/文本/图片/文件/视频等多种类型,多入口采集
分类体系 树状分类(文件夹)+ 扁平标签,双轨管理
检索系统 全文搜索 + 多维过滤 + AI 语义搜索
批注系统 文字高亮、段落批注、整体备注、评分
文章创作 Markdown 编辑器,引用收藏,导出与分享
AI 辅助 摘要生成、标签推荐、相关推荐、对话检索

2. 用户角色与场景

2.1 目标用户

主要用户: 知识工作者(设计师、工程师、研究员、内容创作者)
次要用户: 有阅读习惯的普通用户

2.2 典型使用场景

场景 A — 碎片化收藏
用户在浏览器中看到好文章,通过浏览器插件一键保存,添加标签,收藏进入收件箱,稍后整理。

场景 B — 深度整理
用户每周花 20 分钟整理收件箱,给每条收藏补充分类、标签,读一遍后在重要段落高亮批注。

场景 C — 创作输出
用户要写一篇关于「AI 设计工具」的文章,搜索相关收藏,将多条内容引用到编辑器,形成文章,导出 Markdown 或分享链接。

场景 D — 快速检索
用户想找上周看到的一篇关于「设计系统」的文章,在搜索框输入关键词,1 秒内找到目标。


3. 功能需求详述

3.1 收件箱(Inbox)

功能描述: 所有新收藏的默认落点,未整理的收藏集中于此。

需求明细:

ID 需求 优先级
INB-01 收件箱显示所有未分配分类的收藏,按时间倒序排列 P0
INB-02 顶部显示待处理数量(红色角标) P0
INB-03 支持在收件箱直接操作:分配分类、打标签、删除 P0
INB-04 支持批量选择并批量操作(批量移动分类、批量删除) P1
INB-05 收件箱内容可以「稍后处理」标记,区别于已整理内容 P2

交互逻辑:

  • 收藏录入时,若未指定分类,自动归入收件箱
  • 分配分类后,从收件箱消失,移入对应分类
  • 收件箱数量 = items WHERE category_id IS NULL

3.2 收藏录入

3.2.1 支持的内容类型

类型 标识 说明
网页 web URL 链接,自动抓取标题、描述、OG 图片、favicon
文本 text 任意文字、语录、代码片段、想法
图片 image 本地上传,支持 jpg/png/webp/gif,最大 10MB
文件 file PDF、Word、PPT 等文档,最大 50MB
视频 video YouTube/B站等外链,抓取封面和标题

3.2.2 录入入口

ID 需求 优先级
ADD-01 网页端「新增收藏」按钮,弹出 Modal 表单 P0
ADD-02 浏览器扩展(Chrome/Edge),一键保存当前页面 P1
ADD-03 系统分享菜单(iOS/Android App,后续计划) P3
ADD-04 剪贴板监听:复制 URL 后,页面弹出快捷保存提示 P2
ADD-05 快捷键 Cmd/Ctrl + K 打开快速收藏输入框 P1

3.2.3 网页类型自动抓取

当用户输入 URL 后,后端自动:

  1. 抓取页面 <title><meta name="description">og:imagefavicon
  2. 提取正文内容(使用 @extractus/article-extractor
  3. 如为视频链接(YouTube/bilibili),调用 oEmbed API 获取元数据
  4. AI 自动生成摘要(异步,完成后更新记录)
ID 需求 优先级
FETCH-01 URL 输入后,2 秒内自动填充标题、描述、封面图预览 P0
FETCH-02 抓取失败时,给出友好提示,允许手动填写标题 P0
FETCH-03 支持 User-Agent 伪装,绕过简单的反爬机制 P1
FETCH-04 抓取结果缓存 7 天,同一 URL 不重复抓取 P1
FETCH-05 抓取正文内容并存储,支持全文搜索 P1

3.2.4 录入表单字段

字段 类型 是否必填 说明
type 枚举 web/text/image/file/video
url string web/video 必填 原始链接
title string 标题,自动抓取可覆盖
description text 描述或摘录内容
cover_url string 封面图 URL
category_id integer 分类 ID,空则入收件箱
tags string[] 标签名数组
note text 个人备注
rating integer 1-5 评分,默认不评

3.3 分类管理

功能描述: 树状文件夹结构,最多 3 级嵌套。

ID 需求 优先级
CAT-01 创建分类,设置名称和图标(emoji) P0
CAT-02 编辑分类名称、图标 P0
CAT-03 删除分类(提示:其下收藏将移入收件箱) P0
CAT-04 拖拽排序分类 P1
CAT-05 分类支持颜色主题(6 种预设色) P2
CAT-06 分类支持移动(改变父级) P1
CAT-07 侧边栏显示每个分类的收藏数量 P0
CAT-08 最多支持 3 级嵌套,第 4 级创建时提示限制 P0

分类图标:从预设 Emoji 集合选择,或直接输入 Emoji 字符。


3.4 标签管理

功能描述: 扁平化多维标记,一条内容可关联多个标签。

ID 需求 优先级
TAG-01 录入时直接输入标签名,自动创建 P0
TAG-02 标签输入支持 autocomplete(已有标签下拉提示) P0
TAG-03 标签管理页:列出所有标签及数量,支持重命名、删除、合并 P1
TAG-04 标签支持自定义颜色(从 8 种预设色中选) P1
TAG-05 AI 智能推荐标签(基于内容分析,收藏时弹出建议) P2
TAG-06 侧边栏显示常用标签(按使用频率,最多显示 10 个) P0
TAG-07 点击标签名,过滤显示所有含该标签的收藏 P0

3.5 检索系统

3.5.1 全文搜索

ID 需求 优先级
SEARCH-01 支持对 titledescriptionnotecontent(正文)的全文搜索 P0
SEARCH-02 搜索结果高亮匹配关键词 P0
SEARCH-03 搜索响应时间 < 500ms(本地 SQLite FTS5) P0
SEARCH-04 支持短语搜索(加引号)和排除搜索(-keyword) P1
SEARCH-05 搜索历史记录,最近 20 条,可清除 P2

实现方案: SQLite FTS5 全文索引,建立 items_fts 虚拟表,对 title + description + content + note 建联合索引。

3.5.2 多维过滤

支持以下维度组合过滤,多条件为 AND 关系:

过滤维度 可选值
内容类型 web / text / image / file / video
分类 选中分类(含子分类)
标签 选中标签(多选 AND/OR 切换)
时间范围 今天 / 本周 / 本月 / 自定义范围
星标 是 / 否
评分 ≥ N 星
排序 收藏时间倒序 / 更新时间倒序 / 评分倒序
ID 需求 优先级
FILTER-01 顶部过滤栏支持快速按类型切换 P0
FILTER-02 高级过滤面板(抽屉),组合多维度 P1
FILTER-03 过滤条件 URL 参数化,支持分享过滤状态 P2
FILTER-04 过滤条件组合可保存为「智能分类」 P3

3.6 批注系统

功能描述: 在收藏内容上进行个人标注,沉淀思考。

ID 需求 优先级
ANN-01 整体备注:在收藏详情页写自由文本备注(Markdown 支持) P0
ANN-02 评分:1-5 星,可修改 P0
ANN-03 文字高亮:选中网页正文中的文字,标记颜色高亮(4 种色) P1
ANN-04 段落批注:在高亮文字处添加批注评论 P2
ANN-05 批注列表:详情页显示所有高亮和批注的汇总 P2
ANN-06 高亮内容支持导出(复制为 Markdown 格式) P2

高亮存储格式:

{
  "highlights": [
    {
      "id": "uuid",
      "text": "被高亮的原文",
      "color": "yellow",
      "start_offset": 120,
      "end_offset": 180,
      "note": "批注内容(可为空)",
      "created_at": "2026-05-17T10:00:00Z"
    }
  ]
}

3.7 文章创作

功能描述: 以收藏为素材,进行深度写作创作的工具。

3.7.1 编辑器功能

ID 需求 优先级
ART-01 Markdown 编辑器,支持实时预览(左右分栏) P0
ART-02 编辑器工具栏:标题/加粗/斜体/链接/图片/代码块/引用 P0
ART-03 自动保存(debounce 1 秒)并显示保存状态 P0
ART-04 支持插入收藏引用:输入 [[ 触发收藏搜索面板 P0
ART-05 引用块显示:收藏卡片形式(标题 + 来源 + 摘录) P1
ART-06 文章封面图设置 P1
ART-07 文章字数统计、阅读时间预估 P1
ART-08 版本历史:每次保存自动记录快照,可恢复历史版本 P2

3.7.2 文章关联

ID 需求 优先级
ART-REL-01 文章与收藏的关联关系:一篇文章可关联多条收藏 P0
ART-REL-02 收藏详情页显示「被 N 篇文章引用」,点击可跳转 P1
ART-REL-03 关联关系在文章发布时自动建立(解析 [[ 引用块) P0

3.7.3 发布与导出

ID 需求 优先级
ART-PUB-01 导出为 Markdown 文件(.md) P0
ART-PUB-02 导出为 PDF P1
ART-PUB-03 生成只读分享链接(公开访问,无需登录) P1
ART-PUB-04 分享链接可设置有效期(7 天 / 30 天 / 永久) P2
ART-PUB-05 分享页支持评论(访客可留言) P3

3.8 AI 辅助功能

ID 功能 触发方式 优先级
AI-01 收藏摘要生成 收藏录入后异步生成 P1
AI-02 标签智能推荐 录入时提示,可一键采纳 P1
AI-03 相关收藏推荐 详情页侧栏展示 P2
AI-04 对话式检索「问我的收藏」 底部 AI 对话框 P2
AI-05 写作辅助(续写/润色) 文章编辑器内 AI 按钮 P3
AI-06 每周回顾生成 定时任务,每周一 P3

AI 接入方案: 通过 API Key 方式接入,优先支持 OpenAI / Anthropic / 本地 Ollama,在设置页配置。摘要生成使用流式输出,避免等待时间过长。


3.9 用户与账户

初版为单用户本地应用,不需要注册登录。后续版本规划云同步和多账户。

ID 需求 优先级
USER-01 设置页:修改显示名称和头像 P1
USER-02 设置页:配置 AI API Key 和 Provider P1
USER-03 设置页:导出全量数据(JSON + 附件 ZIP) P1
USER-04 设置页:导入数据(从 JSON 恢复) P2
USER-05 设置页:配置浏览器插件 Token P1

4. 数据库设计

数据库: SQLite(文件路径:./data/wanwu.db
ORM: Drizzle ORM
迁移管理: Drizzle Kit

4.1 完整 Schema 定义

// db/schema.ts
import { sqliteTable, text, integer, real, blob } from 'drizzle-orm/sqlite-core'
import { sql } from 'drizzle-orm'

// ─── 分类表 ───────────────────────────────────────────
export const categories = sqliteTable('categories', {
  id:          integer('id').primaryKey({ autoIncrement: true }),
  name:        text('name').notNull(),
  icon:        text('icon').notNull().default('📁'),      // emoji
  color:       text('color').default('#6B7280'),           // hex 颜色
  parentId:    integer('parent_id').references(() => categories.id, { onDelete: 'set null' }),
  sortOrder:   integer('sort_order').notNull().default(0),
  createdAt:   text('created_at').notNull().default(sql`(datetime('now'))`),
  updatedAt:   text('updated_at').notNull().default(sql`(datetime('now'))`),
})

// ─── 标签表 ───────────────────────────────────────────
export const tags = sqliteTable('tags', {
  id:          integer('id').primaryKey({ autoIncrement: true }),
  name:        text('name').notNull().unique(),
  color:       text('color').default('#6B7280'),
  createdAt:   text('created_at').notNull().default(sql`(datetime('now'))`),
})

// ─── 收藏主表 ─────────────────────────────────────────
export const items = sqliteTable('items', {
  id:          integer('id').primaryKey({ autoIncrement: true }),
  type:        text('type', { enum: ['web', 'text', 'image', 'file', 'video'] }).notNull(),
  title:       text('title').notNull(),
  description: text('description'),                        // 简短描述/摘录
  content:     text('content'),                            // 完整正文(用于 FTS)
  url:         text('url'),                                // 原始链接
  coverUrl:    text('cover_url'),                          // 封面图 URL
  faviconUrl:  text('favicon_url'),                        // 网站图标
  sourceHost:  text('source_host'),                        // 来源域名
  filePath:    text('file_path'),                          // 本地文件路径(image/file 类型)
  fileSize:    integer('file_size'),                       // 文件大小 bytes
  fileMime:    text('file_mime'),                          // MIME type
  
  // 组织
  categoryId:  integer('category_id').references(() => categories.id, { onDelete: 'set null' }),
  
  // 个人标记
  note:        text('note'),                               // 个人备注(支持 Markdown)
  rating:      integer('rating'),                          // 1-5,null 表示未评
  starred:     integer('starred', { mode: 'boolean' }).notNull().default(false),
  highlights:  text('highlights', { mode: 'json' }),       // 高亮批注 JSON
  
  // AI 生成
  aiSummary:   text('ai_summary'),                         // AI 摘要
  aiKeywords:  text('ai_keywords', { mode: 'json' }),      // AI 关键词数组
  
  // 状态
  isArchived:  integer('is_archived', { mode: 'boolean' }).notNull().default(false),
  
  // 时间戳
  publishedAt: text('published_at'),                       // 原文发布时间
  createdAt:   text('created_at').notNull().default(sql`(datetime('now'))`),
  updatedAt:   text('updated_at').notNull().default(sql`(datetime('now'))`),
})

// ─── 收藏-标签 多对多关联表 ───────────────────────────
export const itemTags = sqliteTable('item_tags', {
  itemId:      integer('item_id').notNull().references(() => items.id, { onDelete: 'cascade' }),
  tagId:       integer('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
})

// ─── 文章表 ───────────────────────────────────────────
export const articles = sqliteTable('articles', {
  id:          integer('id').primaryKey({ autoIncrement: true }),
  title:       text('title').notNull().default('无标题文章'),
  content:     text('content').notNull().default(''),      // Markdown 原文
  coverUrl:    text('cover_url'),
  status:      text('status', { enum: ['draft', 'published'] }).notNull().default('draft'),
  wordCount:   integer('word_count').notNull().default(0),
  
  // 分享
  shareToken:  text('share_token').unique(),               // 公开分享 token
  shareExpiresAt: text('share_expires_at'),
  
  createdAt:   text('created_at').notNull().default(sql`(datetime('now'))`),
  updatedAt:   text('updated_at').notNull().default(sql`(datetime('now'))`),
})

// ─── 文章-收藏 关联表 ─────────────────────────────────
export const articleItems = sqliteTable('article_items', {
  articleId:   integer('article_id').notNull().references(() => articles.id, { onDelete: 'cascade' }),
  itemId:      integer('item_id').notNull().references(() => items.id, { onDelete: 'cascade' }),
  sortOrder:   integer('sort_order').notNull().default(0),
})

// ─── 文章版本历史表 ───────────────────────────────────
export const articleVersions = sqliteTable('article_versions', {
  id:          integer('id').primaryKey({ autoIncrement: true }),
  articleId:   integer('article_id').notNull().references(() => articles.id, { onDelete: 'cascade' }),
  content:     text('content').notNull(),
  wordCount:   integer('word_count').notNull().default(0),
  createdAt:   text('created_at').notNull().default(sql`(datetime('now'))`),
})

// ─── FTS 虚拟表(全文搜索)────────────────────────────
// 使用 SQLite FTS5,通过迁移脚本手动创建:
// CREATE VIRTUAL TABLE items_fts USING fts5(
//   id UNINDEXED,
//   title,
//   description,
//   content,
//   note,
//   content=items,
//   content_rowid=id,
//   tokenize='unicode61 remove_diacritics 1'
// );
//
// 同步触发器:
// CREATE TRIGGER items_ai AFTER INSERT ON items BEGIN
//   INSERT INTO items_fts(rowid, title, description, content, note)
//   VALUES (new.id, new.title, new.description, new.content, new.note);
// END;
// CREATE TRIGGER items_au AFTER UPDATE ON items BEGIN
//   INSERT INTO items_fts(items_fts, rowid, title, description, content, note)
//   VALUES ('delete', old.id, old.title, old.description, old.content, old.note);
//   INSERT INTO items_fts(rowid, title, description, content, note)
//   VALUES (new.id, new.title, new.description, new.content, new.note);
// END;
// CREATE TRIGGER items_ad AFTER DELETE ON items BEGIN
//   INSERT INTO items_fts(items_fts, rowid, title, description, content, note)
//   VALUES ('delete', old.id, old.title, old.description, old.content, old.note);
// END;

// ─── 用户设置表 ───────────────────────────────────────
export const settings = sqliteTable('settings', {
  key:         text('key').primaryKey(),
  value:       text('value', { mode: 'json' }),
  updatedAt:   text('updated_at').notNull().default(sql`(datetime('now'))`),
})

// 预设 settings key:
// display_name    : string
// avatar_url      : string
// ai_provider     : 'openai' | 'anthropic' | 'ollama'
// ai_api_key      : string (加密存储)
// ai_model        : string
// ai_base_url     : string (Ollama 自定义地址)
// plugin_token    : string (浏览器插件认证 token)
// theme           : 'dark' | 'light' | 'system'

4.2 索引设计

-- 高频查询索引
CREATE INDEX idx_items_category ON items(category_id);
CREATE INDEX idx_items_created  ON items(created_at DESC);
CREATE INDEX idx_items_starred  ON items(starred) WHERE starred = 1;
CREATE INDEX idx_items_type     ON items(type);
CREATE INDEX idx_items_rating   ON items(rating);
CREATE INDEX idx_item_tags_item ON item_tags(item_id);
CREATE INDEX idx_item_tags_tag  ON item_tags(tag_id);
CREATE INDEX idx_article_items  ON article_items(article_id);
CREATE UNIQUE INDEX idx_item_tags_unique ON item_tags(item_id, tag_id);

4.3 Drizzle 配置

// drizzle.config.ts
import { defineConfig } from 'drizzle-kit'

export default defineConfig({
  schema: './db/schema.ts',
  out: './db/migrations',
  dialect: 'sqlite',
  dbCredentials: {
    url: process.env.DATABASE_URL ?? './data/wanwu.db',
  },
  verbose: true,
  strict: true,
})

5. API 接口设计

所有接口基于 Nuxt 4 的 server/api/ 目录,使用 H3 框架,格式遵循 REST 约定。

通用响应格式:

// 成功
{ data: T, total?: number, page?: number }

// 失败
{ error: { code: string, message: string } }

通用错误码:

Code HTTP Status 说明
VALIDATION_ERROR 400 参数校验失败
NOT_FOUND 404 资源不存在
CONFLICT 409 资源冲突(如标签名重复)
INTERNAL_ERROR 500 服务器内部错误

5.1 收藏接口

GET /api/items

获取收藏列表,支持分页和过滤。

Query 参数:

参数 类型 说明
page number 页码,默认 1
limit number 每页条数,默认 20,最大 100
q string 搜索关键词(全文搜索)
type string 内容类型过滤
categoryId number | 'inbox' 分类 ID,inbox 表示无分类
tagIds string 标签 ID 逗号分隔
tagMode 'and' | 'or' 多标签关系,默认 and
starred boolean 星标过滤
ratingGte number 评分下限
dateFrom string 开始时间 ISO 8601
dateTo string 结束时间 ISO 8601
sort string created_at_desc / updated_at_desc / rating_desc
archived boolean 是否已归档,默认 false

响应:

{
  data: Item[],
  total: number,
  page: number,
  limit: number,
  hasMore: boolean
}

POST /api/items

创建新收藏。

Request Body:

{
  type: 'web' | 'text' | 'image' | 'file' | 'video'
  url?: string
  title: string
  description?: string
  categoryId?: number
  tagNames?: string[]   // 标签名数组,自动创建不存在的标签
  note?: string
  rating?: number
  starred?: boolean
}

响应: { data: Item }

副作用:

  • 若 type=web,触发异步 URL 抓取任务(Server-Sent Events 通知进度)
  • 触发异步 AI 摘要生成

GET /api/items/:id

获取单条收藏详情,包含完整字段(含 content、highlights)。


PATCH /api/items/:id

更新收藏。支持部分更新,仅传入需要修改的字段。

可更新字段: title description note rating starred categoryId isArchived highlights coverUrl


DELETE /api/items/:id

删除收藏。若有关联文章引用,保留关联记录但收藏标记为已删除。


POST /api/items/:id/tags

为收藏添加标签。

Body: { tagName: string }
响应: { data: { itemId, tagId, tagName } }


DELETE /api/items/:id/tags/:tagId

移除收藏的某个标签。


POST /api/items/fetch-url

预抓取 URL 元数据(用于录入表单实时预览)。

Body: { url: string }
响应:

{
  data: {
    title: string
    description: string
    coverUrl: string
    faviconUrl: string
    sourceHost: string
    type: 'web' | 'video'
  }
}

POST /api/items/batch

批量操作。

Body:

{
  ids: number[]
  action: 'move' | 'delete' | 'star' | 'unstar' | 'archive'
  categoryId?: number   // action=move 时必填
}

5.2 分类接口

方法 路径 说明
GET /api/categories 获取分类树(嵌套结构)
POST /api/categories 创建分类
PATCH /api/categories/:id 更新分类(名称/图标/颜色/父级/排序)
DELETE /api/categories/:id 删除分类
POST /api/categories/reorder 批量更新排序 { ids: number[] }

GET /api/categories 响应:

{
  data: Array<{
    id: number
    name: string
    icon: string
    color: string
    itemCount: number
    children: Category[]  // 递归
  }>
}

5.3 标签接口

方法 路径 说明
GET /api/tags 获取所有标签及使用数量
POST /api/tags 创建标签
PATCH /api/tags/:id 更新标签名称/颜色
DELETE /api/tags/:id 删除标签(解除所有关联)
POST /api/tags/:id/merge 合并标签 { targetTagId: number }

5.4 文章接口

方法 路径 说明
GET /api/articles 获取文章列表(分页)
POST /api/articles 创建新文章
GET /api/articles/:id 获取文章详情(含关联收藏)
PATCH /api/articles/:id 更新文章内容
DELETE /api/articles/:id 删除文章
POST /api/articles/:id/publish 发布文章
POST /api/articles/:id/share 生成分享链接
DELETE /api/articles/:id/share 撤销分享
GET /api/articles/:id/versions 获取版本历史
POST /api/articles/:id/restore/:versionId 恢复历史版本
GET /api/articles/:id/export 导出文章(query: format=md/pdf)
GET /api/share/:token 公开分享页数据(无需认证)

5.5 搜索接口

Query: q (必填), limit (默认 10)

响应:

{
  data: {
    items: Array<Item & { highlight: string }>   // highlight 为带 <mark> 标签的匹配片段
    articles: Array<Article & { highlight: string }>
  }
}

GET /api/search/suggest

搜索建议(用于输入框 autocomplete)。
Query: q, limit (默认 5)
响应: { data: string[] } — 建议关键词数组


5.6 AI 接口

POST /api/ai/summarize

手动触发 AI 摘要(流式响应)。

Body: { itemId: number }
响应: text/event-stream,逐 token 返回

POST /api/ai/suggest-tags

获取 AI 标签建议。

Body: { title: string, description?: string, content?: string }
响应: { data: string[] } — 建议标签名数组

POST /api/ai/chat

对话式检索「问我的收藏」。

Body:

{
  message: string
  history?: Array<{ role: 'user' | 'assistant', content: string }>
}

响应: text/event-stream


5.7 设置接口

方法 路径 说明
GET /api/settings 获取所有设置
PATCH /api/settings 批量更新设置 { [key]: value }
GET /api/settings/export 导出全量数据 (ZIP)
POST /api/settings/import 导入数据

6. 前端页面与交互

6.1 页面路由结构

/                   → 重定向到 /inbox
/inbox              → 收件箱
/items              → 全部收藏
/items/starred      → 星标收藏
/items?categoryId=N → 分类视图
/items?tagId=N      → 标签视图
/articles           → 文章列表
/articles/new       → 新建文章
/articles/:id       → 文章编辑器
/share/:token       → 公开分享页(无需认证,独立 layout)
/settings           → 设置页
/settings/tags      → 标签管理

6.2 全局布局(Layout)

三栏结构:

┌──────────────────────────────────────────────────────────────┐
│  Topbar(高度 48px):Logo | 搜索框 | 新增按钮 | 头像         │
├───────────┬──────────────────────────────────┬───────────────┤
│           │                                  │               │
│  Sidebar  │    主内容区(卡片/列表)           │  详情面板     │
│  220px    │                                  │  380px        │
│           │                                  │  (可收起)     │
│           │    AI 对话条(固定底部)           │               │
└───────────┴──────────────────────────────────┴───────────────┘

Sidebar 区块:

  1. 视图导航(收件箱、全部、最近、星标、文章)
  2. 分类树(可展开/折叠,带拖拽排序)
  3. 常用标签(最多 10 个)
  4. 底部:设置入口

详情面板(抽屉):

  • 点击卡片时从右侧滑入(400px 宽)
  • 不影响主列表区域(overlay 模式在移动端,push 模式在桌面端)
  • 内容:封面/预览、标题、来源、AI 摘要、标签、备注编辑、评分、相关推荐、操作按钮

6.3 主内容区

视图模式

模式 说明
卡片(Grid) 2-3 列瀑布流,每卡含封面缩略图
列表(List) 单列,含标题/来源/标签/时间,密度更高

卡片组件规范

网页类型卡片:

┌─────────────────────────────┐
│  [封面图 / 120px 高]         │
│  ┌类型badge┐   ⭐收藏按钮    │
├─────────────────────────────┤
│  favicon  来源域名           │
│  标题文字(最多 2 行)        │
│  描述(最多 2 行,灰色)      │
│  #标签1 #标签2       05-16  │
└─────────────────────────────┘

文本类型卡片(无封面):

  • 顶部区域显示文本前 3 行内容(背景渐变)

排序与分组

  • 支持按时间/评分/标题排序
  • 支持按日期分组(今天 / 本周 / 更早)

6.4 搜索体验

  1. 聚焦搜索框,展示最近搜索记录
  2. 输入时实时 autocomplete(debounce 150ms)
  3. 回车执行全文搜索,结果页显示匹配高亮
  4. 搜索词在结果列表中用 <mark> 高亮
  5. 搜索结果分区:收藏 / 文章

6.5 新增收藏 Modal

┌──────────────────────────────────────────┐
│  新增收藏                              ✕ │
├──────────────────────────────────────────┤
│  类型:[🌐 网页] [📝 文本] [🖼️ 图片]    │
│        [🎬 视频] [📄 文件]               │
│                                          │
│  链接或内容                              │
│  ┌────────────────────────────────────┐ │
│  │ https://...                        │ │
│  └────────────────────────────────────┘ │
│                                          │
│  [正在抓取元数据...] ← URL 输入后自动触发 │
│                                          │
│  标题(已自动填充)                      │
│  ┌────────────────────────────────────┐ │
│  │ Vercel 设计系统 2024 更新           │ │
│  └────────────────────────────────────┘ │
│                                          │
│  分类                    标签            │
│  ┌──────────────┐  ┌──────────────────┐│
│  │ 📁 设计  ▾   │  │ #设计 #参考... +  ││
│  └──────────────┘  └──────────────────┘│
│                                          │
│  [AI 建议标签:设计系统 Vercel 工具 +]   │
│                                          │
│  备注(可选)                            │
│  ┌────────────────────────────────────┐ │
│  │                                    │ │
│  └────────────────────────────────────┘ │
│                                          │
│                    [取消]  [保存收藏 →] │
└──────────────────────────────────────────┘

6.6 文章编辑器页面

布局:

┌─────────────────────────────────────────────────────────────┐
│  ← 返回  |  文章标题(inline 编辑)  |  草稿已保存  发布 ▾  │
├──────────────────────────┬──────────────────────────────────┤
│                          │                                  │
│   Markdown 编辑区        │   实时预览区                     │
│   (左半)               │   (右半)                       │
│                          │                                  │
│   输入 [[ 触发           │                                  │
│   收藏搜索面板           │                                  │
│                          │                                  │
└──────────────────────────┴──────────────────────────────────┘
│  字数:1,234  阅读时间:约 5 分钟  |  版本历史  导出  分享  │
└─────────────────────────────────────────────────────────────┘

[[ 收藏引用面板:

  • 弹出 Popover,输入搜索词实时过滤收藏
  • 选中后插入引用块:
    > [!cite] Vercel 设计系统 2024 更新
    > 来源:vercel.com · 2026-05-16
    > Vercel 团队分享了最新的设计系统更新...
    

7. 技术架构

7.1 技术栈详情

层级 技术 版本 用途
框架 Nuxt 4 4.x 全栈框架,SSR/SPA
运行时 Node.js 20 LTS 服务端运行时
数据库 SQLite 3.x 本地持久化存储
ORM Drizzle ORM latest 类型安全数据库操作
数据库驱动 better-sqlite3 latest SQLite Node.js 绑定
UI 组件 Nuxt UI v3 3.x 基础 UI 组件库
CSS Tailwind CSS v4 样式
状态管理 Pinia latest 客户端状态
编辑器 CodeMirror 6 6.x Markdown 编辑器
MD 渲染 markdown-it latest Markdown → HTML
图标 Nuxt Icon / Lucide latest 图标库
验证 Zod latest API 参数校验
测试 Vitest latest 单元/集成测试

7.2 项目目录结构

wanwu/
├── app/
│   ├── components/
│   │   ├── item/
│   │   │   ├── ItemCard.vue          # 收藏卡片
│   │   │   ├── ItemCardList.vue      # 列表视图行
│   │   │   ├── ItemDetail.vue        # 详情面板
│   │   │   ├── ItemAddModal.vue      # 新增 Modal
│   │   │   └── ItemHighlight.vue     # 高亮批注组件
│   │   ├── category/
│   │   │   ├── CategoryTree.vue      # 分类树
│   │   │   └── CategoryModal.vue     # 分类编辑
│   │   ├── article/
│   │   │   ├── ArticleEditor.vue     # 文章编辑器
│   │   │   ├── ArticleCard.vue       # 文章卡片
│   │   │   └── ArticleCitePanel.vue  # 引用收藏面板
│   │   ├── search/
│   │   │   ├── SearchBar.vue
│   │   │   └── SearchResults.vue
│   │   ├── common/
│   │   │   ├── TagBadge.vue
│   │   │   ├── StarRating.vue
│   │   │   ├── FilterBar.vue
│   │   │   └── AiChatStrip.vue
│   │   └── layout/
│   │       ├── AppSidebar.vue
│   │       └── AppTopbar.vue
│   ├── composables/
│   │   ├── useItems.ts               # 收藏数据操作
│   │   ├── useCategories.ts
│   │   ├── useTags.ts
│   │   ├── useArticles.ts
│   │   ├── useSearch.ts
│   │   ├── useAi.ts
│   │   └── useInfiniteScroll.ts
│   ├── layouts/
│   │   ├── default.vue               # 主布局(三栏)
│   │   └── share.vue                 # 分享页布局
│   ├── pages/
│   │   ├── index.vue                 # → redirect /inbox
│   │   ├── inbox.vue
│   │   ├── items.vue                 # 全部收藏 / 分类 / 标签视图
│   │   ├── articles/
│   │   │   ├── index.vue
│   │   │   ├── new.vue
│   │   │   └── [id].vue
│   │   ├── share/
│   │   │   └── [token].vue
│   │   └── settings/
│   │       ├── index.vue
│   │       └── tags.vue
│   └── stores/
│       ├── ui.ts                     # UI 状态(侧栏收起、详情面板等)
│       ├── items.ts
│       ├── categories.ts
│       └── search.ts
├── server/
│   ├── api/
│   │   ├── items/
│   │   │   ├── index.get.ts
│   │   │   ├── index.post.ts
│   │   │   ├── [id].get.ts
│   │   │   ├── [id].patch.ts
│   │   │   ├── [id].delete.ts
│   │   │   ├── [id]/tags.post.ts
│   │   │   ├── [id]/tags/[tagId].delete.ts
│   │   │   ├── fetch-url.post.ts
│   │   │   └── batch.post.ts
│   │   ├── categories/
│   │   │   ├── index.get.ts
│   │   │   ├── index.post.ts
│   │   │   ├── [id].patch.ts
│   │   │   ├── [id].delete.ts
│   │   │   └── reorder.post.ts
│   │   ├── tags/
│   │   │   ├── index.get.ts
│   │   │   ├── index.post.ts
│   │   │   ├── [id].patch.ts
│   │   │   ├── [id].delete.ts
│   │   │   └── [id]/merge.post.ts
│   │   ├── articles/
│   │   │   ├── index.get.ts
│   │   │   ├── index.post.ts
│   │   │   ├── [id].get.ts
│   │   │   ├── [id].patch.ts
│   │   │   ├── [id].delete.ts
│   │   │   ├── [id]/publish.post.ts
│   │   │   ├── [id]/share.post.ts
│   │   │   ├── [id]/export.get.ts
│   │   │   └── [id]/versions/
│   │   ├── search/
│   │   │   ├── index.get.ts
│   │   │   └── suggest.get.ts
│   │   ├── ai/
│   │   │   ├── summarize.post.ts
│   │   │   ├── suggest-tags.post.ts
│   │   │   └── chat.post.ts
│   │   ├── settings/
│   │   │   ├── index.get.ts
│   │   │   ├── index.patch.ts
│   │   │   ├── export.get.ts
│   │   │   └── import.post.ts
│   │   └── share/
│   │       └── [token].get.ts
│   ├── services/
│   │   ├── fetcher.ts                # URL 抓取服务
│   │   ├── ai.ts                     # AI 接入层
│   │   ├── search.ts                 # FTS 搜索逻辑
│   │   └── exporter.ts               # 导出服务
│   ├── utils/
│   │   ├── db.ts                     # Drizzle 实例(单例)
│   │   └── validate.ts               # Zod schema 集合
│   └── plugins/
│       └── db-init.ts                # 数据库初始化(运行迁移)
├── db/
│   ├── schema.ts
│   ├── migrations/                   # Drizzle Kit 生成
│   └── seed.ts                       # 测试数据
├── public/
│   └── uploads/                      # 本地上传文件(.gitignore)
├── data/                             # SQLite 数据库文件(.gitignore)
├── drizzle.config.ts
├── nuxt.config.ts
├── package.json
└── .env.example

7.3 关键实现细节

数据库连接(单例模式)

// server/utils/db.ts
import { drizzle } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import * as schema from '~/db/schema'

let db: ReturnType<typeof drizzle>

export function useDB() {
  if (!db) {
    const sqlite = new Database(process.env.DATABASE_URL ?? './data/wanwu.db')
    sqlite.pragma('journal_mode = WAL')          // 提高并发性能
    sqlite.pragma('foreign_keys = ON')           // 启用外键约束
    sqlite.pragma('synchronous = NORMAL')        // 平衡安全与性能
    db = drizzle(sqlite, { schema })
  }
  return db
}

URL 抓取服务

// server/services/fetcher.ts
import { extract } from '@extractus/article-extractor'
import { load } from 'cheerio'

export async function fetchUrlMeta(url: string) {
  const res = await fetch(url, {
    headers: { 'User-Agent': 'Mozilla/5.0 ...' },
    signal: AbortSignal.timeout(8000),
  })
  const html = await res.text()
  const $ = load(html)
  
  const meta = {
    title: $('meta[property="og:title"]').attr('content') || $('title').text(),
    description: $('meta[property="og:description"]').attr('content')
      || $('meta[name="description"]').attr('content'),
    coverUrl: $('meta[property="og:image"]').attr('content'),
    faviconUrl: resolveFavicon(url, $),
    sourceHost: new URL(url).hostname,
  }
  
  // 提取正文
  const article = await extract(url)
  
  return { ...meta, content: article?.content }
}

FTS5 搜索

// server/services/search.ts
export async function fullTextSearch(db: DB, query: string, limit = 20) {
  const sanitized = query.replace(/['"*]/g, ' ').trim()
  const ftsQuery = sanitized.split(/\s+/).map(t => `"${t}"`).join(' AND ')
  
  return db.all(sql`
    SELECT
      i.*,
      snippet(items_fts, 1, '<mark>', '</mark>', '...', 20) AS highlight
    FROM items_fts
    JOIN items i ON i.id = items_fts.rowid
    WHERE items_fts MATCH ${ftsQuery}
      AND i.is_archived = 0
    ORDER BY rank
    LIMIT ${limit}
  `)
}

7.4 nuxt.config.ts

export default defineNuxtConfig({
  compatibilityDate: '2025-01-01',
  future: { compatibilityVersion: 4 },
  
  modules: [
    '@nuxt/ui',
    '@pinia/nuxt',
    '@nuxt/icon',
    'nuxt-file-storage',
  ],
  
  nitro: {
    experimental: { asyncContext: true },
    storage: {
      uploads: { driver: 'fs', base: './public/uploads' }
    }
  },
  
  css: ['~/assets/css/main.css'],
  
  runtimeConfig: {
    databaseUrl: './data/wanwu.db',
    aiProvider: '',
    aiApiKey: '',
    aiModel: '',
    public: {}
  }
})

8. 开发计划

8.1 里程碑总览

里程碑 内容 工期 目标完成日
M0 项目初始化与基础架构 3 天 Week 1
M1 数据库 + 核心 API 5 天 Week 2
M2 收藏管理前端(增删改查) 5 天 Week 3
M3 分类 + 标签系统 3 天 Week 3-4
M4 搜索与过滤系统 3 天 Week 4
M5 详情面板与批注 4 天 Week 5
M6 文章编辑器 5 天 Week 6
M7 AI 功能接入 4 天 Week 7
M8 设置页 + 导入导出 3 天 Week 8
M9 浏览器扩展 4 天 Week 9
M10 测试 + 打磨 + 发布 5 天 Week 10

总工期: 约 10 周(单人全职开发)


8.2 M0 — 项目初始化(Week 1,第 1-3 天)

任务清单:

  • npx nuxi init wanwu --template ui 初始化项目
  • 配置 Nuxt 4 兼容模式(future.compatibilityVersion: 4
  • 安装依赖:better-sqlite3, drizzle-orm, drizzle-kit, zod, @extractus/article-extractor, cheerio
  • 创建 db/schema.ts,定义所有表结构
  • 运行 drizzle-kit generate 生成迁移文件
  • 实现 server/utils/db.ts 单例 + 数据库初始化插件
  • 配置 .env.example,列出所有环境变量
  • 设置 ESLint + Prettier + husky pre-commit
  • 配置 Tailwind CSS 主题色(暗色系 + 琥珀色 accent)
  • 建立全局 Layout(Sidebar + Topbar + 主内容区占位)
  • 编写 db/seed.ts,插入 10 条测试收藏数据
  • 验证:npm run dev 启动无报错,数据库文件正常创建

交付物: 可运行的开发环境,数据库结构已建立


8.3 M1 — 核心 API(Week 2,第 4-8 天)

任务清单(按接口优先级):

收藏 CRUD(第 4-5 天):

  • GET /api/items — 列表查询(含分页、类型过滤、排序)
  • POST /api/items — 创建收藏(Zod 校验)
  • GET /api/items/:id — 单条详情
  • PATCH /api/items/:id — 部分更新
  • DELETE /api/items/:id — 删除

分类 + 标签(第 6 天):

  • GET/POST/PATCH/DELETE /api/categories — 分类 CRUD(含递归树查询)
  • GET/POST/PATCH/DELETE /api/tags — 标签 CRUD
  • POST /api/items/:id/tagsDELETE /api/items/:id/tags/:tagId

URL 抓取(第 7 天):

  • POST /api/items/fetch-url — URL 元数据抓取
  • 实现 server/services/fetcher.ts
  • 错误处理:超时 / 抓取失败 / 非 HTML 响应

FTS 搜索(第 8 天):

  • 在迁移脚本中创建 FTS5 虚拟表和同步触发器
  • GET /api/search — 全文搜索,返回带高亮片段
  • GET /api/search/suggest — 搜索建议

验证: 用 Postman/Hoppscotch 测试所有接口,全部通过


8.4 M2 — 收藏管理前端(Week 2-3,第 6-10 天)

任务清单:

基础列表(第 6-7 天):

  • pages/items.vue — 收藏列表页,接入 GET /api/items
  • components/item/ItemCard.vue — 卡片组件(含 hover 效果、封面图、类型 badge)
  • components/item/ItemCardList.vue — 列表视图行
  • 卡片/列表视图切换
  • 无限滚动加载(Intersection Observer + useInfiniteScroll composable)

新增收藏(第 8 天):

  • components/item/ItemAddModal.vue
    • 类型切换(5 种)
    • URL 输入 + 自动抓取(loading 态)
    • 标题、分类选择、标签输入(autocomplete)
    • AI 标签推荐展示(后续接入)
  • 接入 POST /api/items
  • 成功后刷新列表(乐观更新)

Pinia Store(第 9 天):

  • stores/items.ts — 收藏状态管理(列表、分页、加载状态)
  • stores/categories.ts
  • stores/ui.ts — 面板状态、视图模式

快捷键(第 10 天):

  • Cmd/Ctrl + K 打开快速收藏输入框
  • Esc 关闭 Modal / 详情面板

8.5 M3 — 分类 + 标签系统(Week 3-4,第 11-13 天)

任务清单:

分类树(第 11 天):

  • components/category/CategoryTree.vue — 递归树形组件,展开/折叠
  • 分类创建/编辑 Modal(名称、图标 Emoji、颜色)
  • 拖拽排序(使用 vue-draggable-plus
  • 接入分类 CRUD API

标签系统(第 12 天):

  • components/common/TagBadge.vue — 标签组件(含颜色、点击过滤)
  • pages/settings/tags.vue — 标签管理页(列表、编辑、删除、合并)
  • 侧边栏常用标签显示

收件箱(第 13 天):

  • pages/inbox.vue — 收件箱视图(仅显示 categoryId IS NULL
  • 顶部 Topbar 收件箱数量角标
  • 收件箱批量分类操作

8.6 M4 — 搜索与过滤(Week 4,第 14-16 天)

任务清单:

搜索(第 14 天):

  • components/search/SearchBar.vue — 顶部搜索框,输入 autocomplete
  • 搜索结果页,含高亮显示
  • 搜索历史(localStorage 存储)

过滤系统(第 15-16 天):

  • components/common/FilterBar.vue — 类型快速切换
  • 高级过滤面板(抽屉 Drawer):时间范围、评分、星标
  • 多标签 AND/OR 模式切换
  • 过滤状态 URL 参数化(使用 useRoute + navigateTo

8.7 M5 — 详情面板与批注(Week 5,第 17-20 天)

任务清单:

详情面板(第 17-18 天):

  • components/item/ItemDetail.vue — 右侧详情面板
    • 封面大图
    • 标题(inline 编辑)
    • 来源信息、发布时间
    • AI 摘要(含 loading skeleton)
    • 标签展示 + 添加标签
    • 备注编辑区(Markdown 支持,自动保存)
    • 星标评分
    • 相关推荐(3 条)
    • 操作按钮:写文章 / 复制链接 / 编辑 / 删除
  • 面板打开/关闭动画(CSS transition)
  • 移动端改为全屏 Drawer

批注功能(第 19-20 天):

  • components/item/ItemHighlight.vue — 网页正文展示 + 高亮选择
  • 选中文字触发颜色选择气泡
  • 批注 CRUD(存储到 items.highlights JSON 字段)
  • 批注汇总列表

8.8 M6 — 文章编辑器(Week 6,第 21-25 天)

任务清单:

编辑器核心(第 21-22 天):

  • 集成 CodeMirror 6(Markdown 语法高亮、vi/emacs 键位可选)
  • 实时预览(markdown-it 渲染)
  • 工具栏(标题/加粗/斜体/链接/图片/代码块/引用)
  • 自动保存(debounce 1s,显示保存状态)
  • 字数统计、阅读时间预估

收藏引用(第 23 天):

  • components/article/ArticleCitePanel.vue[[ 触发的收藏搜索面板
  • 选中收藏后插入引用 Markdown 块
  • 预览区渲染引用块为卡片样式
  • 解析引用关系,保存到 article_items

发布与导出(第 24 天):

  • 文章状态切换(草稿 → 发布)
  • 导出 Markdown(直接下载)
  • 生成分享链接(生成 token,写入 DB)
  • 分享页 pages/share/[token].vue

版本历史(第 25 天):

  • 每次保存记录快照到 article_versions
  • 版本历史侧边栏(时间 + 字数)
  • 点击恢复某版本

8.9 M7 — AI 功能(Week 7,第 26-29 天)

任务清单:

AI 基础接入(第 26 天):

  • server/services/ai.ts — 统一 AI 接入层
    • 支持 OpenAI / Anthropic / Ollama
    • 流式输出封装
  • 设置页:AI 配置(Provider / API Key / Model)
  • 测试连接功能

摘要生成(第 27 天):

  • 收藏录入后异步触发摘要(Server-Sent Events)
  • 详情面板显示摘要 + loading 骨架
  • 手动重新生成摘要按钮

标签推荐(第 28 天):

  • 新增 Modal 中:URL 抓取完成后自动调用 AI 建议标签
  • 建议标签一键添加
  • 接口:POST /api/ai/suggest-tags

对话检索(第 29 天):

  • components/common/AiChatStrip.vue — 底部 AI 对话条
  • POST /api/ai/chat — RAG 检索:先 FTS 搜索相关收藏,再送入 AI 生成回答
  • 对话历史(session 级)
  • 引用相关收藏卡片在回答中展示

8.10 M8 — 设置页 + 导入导出(Week 8,第 30-32 天)

任务清单:

  • pages/settings/index.vue — 设置主页
    • 个人资料(名称、头像)
    • AI 配置
    • 浏览器扩展 Token 管理
    • 主题切换(暗/亮/系统)
    • 危险区域(清空数据)
  • 全量数据导出(JSON + 附件 ZIP)
  • 数据导入与校验
  • GET /api/settings/export 实现

8.11 M9 — 浏览器扩展(Week 9,第 33-36 天)

任务清单:

  • 创建 Chrome Extension(Manifest V3)项目 extension/
  • Popup 页面:
    • 显示当前页面标题/封面
    • 选择分类、输入标签
    • 「保存到万物收藏」按钮
  • Background Service Worker:
    • 调用本地 http://localhost:3000/api/items(或配置的 Host)
    • Token 认证
  • 右键菜单:「保存链接到万物收藏」
  • 选中文字右键:「保存文字片段到万物收藏」
  • 测试扩展在 Chrome / Edge 中正常工作

8.12 M10 — 测试与发布(Week 10,第 37-41 天)

任务清单:

测试(第 37-38 天):

  • 核心 API 单元测试(Vitest):items / categories / tags CRUD
  • 搜索功能测试
  • 边界情况:超长标题、特殊字符、大文件上传
  • 多浏览器兼容测试(Chrome / Firefox / Safari)

性能优化(第 39 天):

  • 图片懒加载
  • 虚拟滚动(收藏列表超过 500 条时)
  • 数据库慢查询排查(EXPLAIN QUERY PLAN)
  • Nuxt build 产物分析(nuxt analyze

打磨(第 40 天):

  • 空状态设计(收件箱为空、搜索无结果等)
  • 错误状态设计(网络失败、抓取失败等)
  • Loading skeleton 补全
  • 响应式适配(iPad 布局)

发布(第 41 天):

  • 编写 README(安装、配置、启动指南)
  • npm run build 验证生产构建
  • Docker 镜像打包(Dockerfile + docker-compose.yml
  • GitHub Release v1.0.0

9. 非功能需求

9.1 性能要求

指标 目标值
收藏列表首屏加载 < 1s(本地 SQLite)
全文搜索响应 < 500ms
URL 元数据抓取 < 3s(含 timeout)
页面路由切换 < 200ms(客户端导航)
数据库查询(单次) < 100ms

9.2 可靠性

  • SQLite WAL 模式,防止写入崩溃数据损坏
  • 文章自动保存,最小化数据丢失风险
  • 导出功能作为数据备份手段
  • URL 抓取失败不影响收藏创建

9.3 安全性

  • AI API Key 不明文存储(本地应用采用系统 Keychain 或环境变量)
  • 文件上传类型白名单校验(不允许可执行文件)
  • 文件上传大小限制(图片 10MB,文档 50MB)
  • 分享链接使用 UUID v4 Token,无法枚举
  • 所有 API 输入通过 Zod 校验,防止注入

9.4 数据持久化

  • 数据库文件:./data/wanwu.db
  • 上传文件:./public/uploads/(按日期分目录)
  • 建议用户定期使用「导出功能」备份数据
  • 数据库迁移向后兼容,升级不丢数据

10. 验收标准

10.1 P0 功能验收(必须完成)

功能 验收标准
新增网页收藏 输入 URL → 自动填充标题和描述 → 保存成功 → 列表显示
新增文本收藏 输入文字 → 保存 → 列表显示
分类创建 创建分类 → 侧边栏显示 → 收藏可分配该分类
标签打标 录入时添加标签 → 卡片显示 → 点击标签过滤有效
全文搜索 输入关键词 → 500ms 内返回结果 → 关键词高亮
编辑备注 详情面板编辑备注 → 1s 内自动保存 → 刷新后仍存在
写文章 新建文章 → 使用 [[ 引用收藏 → 导出 Markdown 正确
删除收藏 删除后列表消失,数据库记录删除
收件箱 无分类收藏显示在收件箱,分配分类后消失

10.2 P1 功能验收(重要)

功能 验收标准
AI 摘要 保存收藏后,详情面板摘要在 10s 内显示
标签推荐 新增收藏时展示 AI 建议标签,点击可采纳
批注高亮 在正文中选中文字 → 选色 → 高亮持久化
文章分享 生成链接 → 无需登录访问 → 内容正确显示
浏览器扩展 扩展 Popup 可保存当前页面到收藏
数据导出 导出 ZIP 包含 JSON 数据和所有附件

10.3 已知限制(v1.0 不做)

  • 不支持多用户 / 账户系统
  • 不支持云同步(本地优先)
  • 不支持移动 App(Web 响应式适配为主)
  • AI 对话无跨 session 记忆
  • 不支持 PDF 正文全文搜索(仅标题和备注)

文档结束 — 万物收藏 PRD v1.0.0