用 Claude Code 从零构建离线记事本 PWA 应用

探索如何使用 AI 编程助手逐步开发一个功能完整的渐进式 Web 应用

目录:

在 AI 辅助编程的时代,我们如何利用 Claude Code 这样的智能工具来构建一个完整的 Web 应用?本文将详细记录使用 Claude Code 开发一个功能完整的离线记事本 PWA (Progressive Web App) 的全过程,展示 AI 编程助手如何帮助我们快速实现从需求到部署的完整开发流程。

这个项目的特别之处在于:它是一个零依赖框架的纯 JavaScript 应用,支持 Markdown 渲染JSON 可视化,并且可以完全离线工作。更重要的是,整个开发过程展示了如何与 AI 编程助手高效协作。

项目概览

我们要构建的是一个具有以下特性的离线记事本应用:

  • 完全离线支持 - 使用 Service Worker 实现离线缓存
  • PWA 可安装 - 可以像原生应用一样安装到桌面
  • Markdown 支持 - 实时预览 Markdown 渲染效果
  • JSON 可视化 - 智能识别 JSON 内容并提供可折叠的语法高亮显示
  • 实时搜索 - 按标题和内容过滤笔记
  • 响应式设计 - 适配移动端和桌面端
  • 零框架依赖 - 纯 vanilla JavaScript (除了 Markdown 解析库)

技术栈:

  • HTML5 + CSS3
  • Vanilla JavaScript (ES6+)
  • LocalStorage API (数据持久化)
  • Service Worker API (离线支持)
  • Web App Manifest (PWA 配置)
  • Marked.js (Markdown 解析)

第一步:与 Claude Code 明确需求

在开始编码之前,与 Claude Code 的第一次对话至关重要。清晰的需求描述能让 AI 更好地理解项目目标。

我的初始提示词:

我想创建一个离线记事本 PWA 应用,需要具备以下功能:
1. 创建、编辑、删除笔记
2. 支持 Markdown 格式
3. 能够离线使用
4. 可以安装到桌面
5. 数据保存在浏览器本地
6. 实时搜索功能
7. 能够识别和美化 JSON 内容

技术要求:
- 不使用任何前端框架,用纯 JavaScript 实现
- 响应式设计,支持移动端
- 使用 Service Worker 实现离线功能

Claude Code 的响应策略:

Claude Code 首先会询问一些关键的设计决策:

  • 数据存储方式 (LocalStorage vs IndexedDB)
  • UI 风格偏好 (简约现代 vs 经典样式)
  • Markdown 库选择 (Marked.js vs Markdown-it)
  • 是否需要数据导出功能

这个交互过程帮助我们明确了技术选型和设计方向。

第二步:项目结构设计

基于需求分析,Claude Code 建议了一个简洁的项目结构:

notepad/
├── index.html          # 主页面结构
├── styles.css          # 样式表 (538 行)
├── app.js             # 核心应用逻辑 (398 行)
├── sw.js              # Service Worker (71 行)
├── manifest.json      # PWA 配置文件
└── README.md          # 项目文档

设计原则:

  • 关注点分离 - HTML (结构)、CSS (样式)、JS (逻辑) 独立
  • 单一职责 - 每个文件有明确的功能边界
  • 最小化依赖 - 只引入必要的外部库 (Marked.js)

第三步:构建核心数据模型

3.1 数据结构设计

我向 Claude Code 描述了笔记的基本属性需求,它建议了以下数据模型:

// 笔记数据结构
{
  id: "1675332000000",           // 时间戳作为唯一 ID
  title: "我的第一条笔记",
  content: "支持 **Markdown** 和 JSON",
  createdAt: "2025-02-02T10:00:00.000Z",
  updatedAt: "2025-02-02T10:30:00.000Z"
}

// generated by hugo's coding agent

设计亮点:

  • 使用时间戳生成唯一 ID,避免 UUID 库依赖
  • ISO 8601 格式的时间戳便于国际化
  • 简单的扁平结构,易于序列化到 LocalStorage

3.2 应用核心类

Claude Code 建议使用 ES6 类来组织应用逻辑:

class NotesApp {
  constructor() {
    this.notes = [];           // 笔记数组
    this.currentNoteId = null; // 当前编辑的笔记 ID
    this.isPreviewMode = false; // 预览模式标志
  }

  // 初始化应用
  init() {
    this.loadNotes();
    this.bindEvents();
    this.registerServiceWorker();
    this.showList();
  }

  // 从 LocalStorage 加载笔记
  loadNotes() {
    const stored = localStorage.getItem('notes');
    this.notes = stored ? JSON.parse(stored) : [];
  }

  // 保存笔记到 LocalStorage
  saveNotes() {
    localStorage.setItem('notes', JSON.stringify(this.notes));
  }

  // 绑定所有事件监听器
  bindEvents() {
    // 新建笔记按钮
    document.getElementById('newNoteBtn')
      .addEventListener('click', () => this.showEditor(null));

    // 返回列表按钮
    document.getElementById('backBtn')
      .addEventListener('click', () => this.showList());

    // 保存按钮
    document.getElementById('saveBtn')
      .addEventListener('click', () => this.saveNote());

    // 删除按钮
    document.getElementById('deleteBtn')
      .addEventListener('click', () => this.deleteNote());

    // 搜索输入框
    document.getElementById('searchInput')
      .addEventListener('input', (e) => this.searchNotes(e.target.value));

    // 预览切换按钮
    document.getElementById('previewBtn')
      .addEventListener('click', () => this.togglePreview());

    // 实时预览更新
    document.getElementById('noteContent')
      .addEventListener('input', () => this.updatePreview());
  }

  // 显示笔记列表视图
  showList() {
    document.getElementById('listView').style.display = 'block';
    document.getElementById('editView').style.display = 'none';
    this.renderNotesList(this.notes);
  }

  // 显示编辑器视图
  showEditor(noteId) {
    this.currentNoteId = noteId;
    const note = noteId ? this.notes.find(n => n.id === noteId) : null;

    document.getElementById('noteTitle').value = note ? note.title : '';
    document.getElementById('noteContent').value = note ? note.content : '';

    document.getElementById('listView').style.display = 'none';
    document.getElementById('editView').style.display = 'block';

    // 新建笔记时自动聚焦标题
    if (!noteId) {
      document.getElementById('noteTitle').focus();
    }

    this.updatePreview();
    this.updateJsonIndicator();
  }

  // CRUD 操作省略...
}

// 初始化应用
const app = new NotesApp();
app.init();

// generated by hugo's coding agent

与 Claude Code 的协作要点:

  1. 迭代式开发 - 先实现基础的 CRUD 功能,再添加高级特性
  2. 代码审查 - 每次生成代码后,我会要求 Claude Code 检查潜在的 bug 和性能问题
  3. XSS 防护 - Claude Code 主动建议添加 HTML 转义函数防止 XSS 攻击
// Claude Code 建议的 XSS 防护函数
escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// generated by hugo's coding agent

第四步:实现 Markdown 支持

4.1 集成 Marked.js

我向 Claude Code 询问:“如何添加 Markdown 预览功能?”

它建议使用轻量级的 Marked.js 库,并提供了实现代码:

// 渲染 Markdown 预览
renderMarkdownPreview() {
  const content = document.getElementById('noteContent').value;
  const previewContainer = document.getElementById('markdownPreview');

  // 检测是否为 JSON 格式
  if (this.isValidJson(content)) {
    this.renderJsonPreview(content, previewContainer);
  } else {
    // 使用 Marked.js 渲染 Markdown
    previewContainer.innerHTML = marked.parse(content);
    previewContainer.className = 'markdown-preview';
  }
}

// 验证 JSON 格式
isValidJson(str) {
  try {
    JSON.parse(str);
    return true;
  } catch (e) {
    return false;
  }
}

// generated by hugo's coding agent

4.2 Markdown 样式优化

Claude Code 还生成了完整的 Markdown 样式表:

/* Markdown 渲染样式 */
.markdown-preview {
  padding: 20px;
  background: white;
  border-radius: 8px;
  line-height: 1.8;
}

.markdown-preview h1 {
  font-size: 2em;
  border-bottom: 2px solid #667eea;
  padding-bottom: 10px;
  margin-bottom: 20px;
}

.markdown-preview h2 {
  font-size: 1.5em;
  color: #667eea;
  margin-top: 30px;
}

.markdown-preview code {
  background: #f5f5f5;
  padding: 2px 6px;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  font-size: 0.9em;
}

.markdown-preview pre {
  background: #2d2d2d;
  color: #f8f8f2;
  padding: 15px;
  border-radius: 6px;
  overflow-x: auto;
}

.markdown-preview blockquote {
  border-left: 4px solid #667eea;
  padding-left: 20px;
  color: #666;
  margin: 20px 0;
  font-style: italic;
}

/* generated by hugo's coding agent */

第五步:创新的 JSON 可视化功能

这是整个项目最有趣的部分。当我向 Claude Code 提出:“能否自动识别 JSON 内容并提供更好的显示效果?“它提出了一个完整的 JSON 可视化方案。

5.1 JSON 检测和徽章显示

// 更新 JSON 指示器
updateJsonIndicator() {
  const content = document.getElementById('noteContent').value;
  const indicator = document.getElementById('jsonIndicator');

  if (this.isValidJson(content)) {
    indicator.textContent = '📋 JSON';
    indicator.style.display = 'inline-block';
  } else {
    indicator.style.display = 'none';
  }
}

// generated by hugo's coding agent

5.2 JSON 语法高亮渲染器

Claude Code 生成了一个复杂但高效的 JSON 渲染函数:

renderJsonPreview(jsonString, container) {
  try {
    const data = JSON.parse(jsonString);
    container.className = 'json-viewer';
    container.innerHTML = this.renderJsonNode(data, 0);
    this.bindJsonToggleEvents();
  } catch (e) {
    container.innerHTML = `
      <div class="json-error">
        <strong>JSON 解析错误:</strong>
        <pre>${this.escapeHtml(e.message)}</pre>
      </div>
    `;
  }
}

// 递归渲染 JSON 节点
renderJsonNode(data, level) {
  const indent = '  '.repeat(level);

  if (data === null) return '<span class="json-null">null</span>';
  if (data === undefined) return '<span class="json-undefined">undefined</span>';

  const type = typeof data;

  // 布尔值
  if (type === 'boolean') {
    return `<span class="json-boolean">${data}</span>`;
  }

  // 数字
  if (type === 'number') {
    return `<span class="json-number">${data}</span>`;
  }

  // 字符串
  if (type === 'string') {
    // 检测 URL
    const urlRegex = /^https?:\/\/.+/;
    if (urlRegex.test(data)) {
      return `<a href="${data}" target="_blank" class="json-url">"${this.escapeHtml(data)}"</a>`;
    }

    // 长文本处理
    if (data.length > 100) {
      const preview = this.escapeHtml(data.substring(0, 100));
      const full = this.escapeHtml(data);
      return `
        <span class="json-string-long">
          "<span class="json-string-preview">${preview}...</span>
          <span class="json-string-full" style="display:none;">${full}</span>
          <button class="json-expand-btn">Show More</button>"
        </span>
      `;
    }

    return `<span class="json-string">"${this.escapeHtml(data)}"</span>`;
  }

  // 数组
  if (Array.isArray(data)) {
    if (data.length === 0) return '[]';

    const items = data.map((item, index) => {
      return `${indent}  ${this.renderJsonNode(item, level + 1)}${index < data.length - 1 ? ',' : ''}`;
    }).join('\n');

    return `
      <span class="json-bracket">[</span>
      <span class="json-array-count">${data.length} items</span>
      <button class="json-toggle" data-collapsed="false">−</button>
      <div class="json-content">
\n${items}\n${indent}
      </div>
      <span class="json-bracket">]</span>
    `;
  }

  // 对象
  if (type === 'object') {
    const keys = Object.keys(data);
    if (keys.length === 0) return '{}';

    const properties = keys.map((key, index) => {
      const value = this.renderJsonNode(data[key], level + 1);
      return `${indent}  <span class="json-key">"${this.escapeHtml(key)}"</span>: ${value}${index < keys.length - 1 ? ',' : ''}`;
    }).join('\n');

    return `
      <span class="json-bracket">{</span>
      <span class="json-object-count">${keys.length} fields</span>
      <button class="json-toggle" data-collapsed="false">−</button>
      <div class="json-content">
\n${properties}\n${indent}
      </div>
      <span class="json-bracket">}</span>
    `;
  }

  return String(data);
}

// generated by hugo's coding agent

5.3 JSON 折叠/展开交互

// 绑定 JSON 折叠按钮事件
bindJsonToggleEvents() {
  document.querySelectorAll('.json-toggle').forEach(btn => {
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      const isCollapsed = btn.dataset.collapsed === 'true';
      const content = btn.nextElementSibling;

      if (isCollapsed) {
        content.style.display = 'block';
        btn.textContent = '−';
        btn.dataset.collapsed = 'false';
      } else {
        content.style.display = 'none';
        btn.textContent = '+';
        btn.dataset.collapsed = 'true';
      }
    });
  });

  // 长文本展开按钮
  document.querySelectorAll('.json-expand-btn').forEach(btn => {
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      const container = btn.closest('.json-string-long');
      const preview = container.querySelector('.json-string-preview');
      const full = container.querySelector('.json-string-full');

      if (full.style.display === 'none') {
        preview.style.display = 'none';
        full.style.display = 'inline';
        btn.textContent = 'Show Less';
      } else {
        preview.style.display = 'inline';
        full.style.display = 'none';
        btn.textContent = 'Show More';
      }
    });
  });
}

// generated by hugo's coding agent

JSON 可视化效果展示:

{
  "user": {
    "id": 12345,
    "name": "张三",
    "email": "zhangsan@example.com",
    "isActive": true,
    "profile": {
      "bio": "这是一段很长的个人简介...",
      "website": "https://example.com",
      "tags": ["developer", "blogger", "AI enthusiast"]
    }
  }
}

渲染后会显示:

  • 语法高亮 (键、值、括号不同颜色)
  • 可折叠的对象和数组
  • 字段/项目计数
  • 长文本自动截断并提供展开按钮
  • URL 自动转为可点击链接

第六步:实现 PWA 离线功能

6.1 Service Worker 缓存策略

我向 Claude Code 询问:“如何实现离线支持?“它解释了 Service Worker 的工作原理,并生成了完整的实现:

// sw.js - Service Worker
const CACHE_NAME = 'notepad-v1';
const URLS_TO_CACHE = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/manifest.json',
  'https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js'
];

// 安装事件 - 缓存核心资源
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('缓存已打开');
        return cache.addAll(URLS_TO_CACHE);
      })
  );
});

// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            console.log('删除旧缓存:', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

// 拦截请求 - Cache First 策略
self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        // 缓存命中,返回缓存
        if (response) {
          return response;
        }

        // 缓存未命中,发起网络请求
        return fetch(event.request).then(response => {
          // 检查是否为有效响应
          if (!response || response.status !== 200 || response.type === 'error') {
            return response;
          }

          // 克隆响应并缓存
          const responseToCache = response.clone();
          caches.open(CACHE_NAME)
            .then(cache => {
              cache.put(event.request, responseToCache);
            });

          return response;
        });
      })
  );
});

// generated by hugo's coding agent

缓存策略说明:

Claude Code 向我解释了三种常见的 Service Worker 缓存策略:

  1. Cache First (本项目采用) - 优先使用缓存,适合静态资源
  2. Network First - 优先网络,适合动态内容
  3. Stale While Revalidate - 返回缓存的同时更新缓存,平衡速度和新鲜度

对于离线记事本应用,Cache First 是最佳选择,因为:

  • 应用资源不经常变化
  • 用户数据存储在 LocalStorage,不依赖网络
  • 优先保证应用可用性

6.2 注册 Service Worker

// 在 NotesApp 类中注册
registerServiceWorker() {
  if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('/sw.js')
        .then(registration => {
          console.log('Service Worker 注册成功:', registration.scope);
        })
        .catch(error => {
          console.log('Service Worker 注册失败:', error);
        });
    });
  }
}

// generated by hugo's coding agent

6.3 Web App Manifest 配置

{
  "name": "离线记事本",
  "short_name": "记事本",
  "description": "功能完整的离线记事本应用",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#667eea",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Manifest 配置要点:

  • display: "standalone" - 隐藏浏览器 UI,像原生应用一样显示
  • theme_color - 匹配应用主色调 (#667eea 紫色)
  • 图标尺寸遵循 PWA 标准 (192x192 和 512x512)

第七步:UI/UX 设计与实现

7.1 设计系统

我向 Claude Code 描述了设计需求:“现代简约风格,紫色渐变主题,卡片式布局。”

它生成了完整的设计系统:

:root {
  /* 颜色变量 */
  --primary-color: #667eea;
  --secondary-color: #764ba2;
  --text-dark: #333;
  --text-light: #666;
  --bg-light: #f5f5f5;
  --border-color: #ddd;
  --shadow: 0 2px 8px rgba(0,0,0,0.1);
  --shadow-hover: 0 4px 12px rgba(0,0,0,0.15);
}

/* 渐变背景头部 */
header {
  background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
  color: white;
  padding: 20px;
  box-shadow: var(--shadow);
}

/* 卡片式笔记布局 */
.note-card {
  background: white;
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 15px;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: var(--shadow);
}

.note-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-hover);
}

/* 响应式网格布局 */
.notes-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
  padding: 20px;
}

/* 移动端适配 */
@media (max-width: 768px) {
  .notes-grid {
    grid-template-columns: 1fr;
  }

  .toolbar {
    flex-direction: column;
    gap: 10px;
  }

  button {
    width: 100%;
  }
}

/* generated by hugo's coding agent */

7.2 交互反馈

Claude Code 还建议添加微交互增强用户体验:

/* 按钮悬停效果 */
button {
  transition: all 0.2s ease;
}

button:hover {
  transform: scale(1.05);
  box-shadow: var(--shadow-hover);
}

button:active {
  transform: scale(0.98);
}

/* 输入框焦点状态 */
input:focus,
textarea:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

/* 平滑过渡动画 */
.view {
  animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* generated by hugo's coding agent */

第八步:搜索功能实现

8.1 实时搜索过滤

searchNotes(query) {
  const searchTerm = query.toLowerCase().trim();

  if (!searchTerm) {
    // 搜索框为空,显示所有笔记
    this.renderNotesList(this.notes);
    return;
  }

  // 过滤笔记:标题或内容包含搜索词
  const filtered = this.notes.filter(note => {
    const titleMatch = note.title.toLowerCase().includes(searchTerm);
    const contentMatch = note.content.toLowerCase().includes(searchTerm);
    return titleMatch || contentMatch;
  });

  this.renderNotesList(filtered);

  // 显示搜索结果统计
  const resultCount = filtered.length;
  const totalCount = this.notes.length;
  console.log(`搜索结果: ${resultCount}/${totalCount}`);
}

// generated by hugo's coding agent

8.2 搜索高亮显示 (可选增强)

当我询问 Claude Code:“如何高亮显示搜索结果?“它提供了一个高亮函数:

highlightSearchTerm(text, term) {
  if (!term) return this.escapeHtml(text);

  const regex = new RegExp(`(${term})`, 'gi');
  return this.escapeHtml(text).replace(regex, '<mark>$1</mark>');
}

// 在渲染笔记列表时应用
renderNotesList(notes) {
  const searchTerm = document.getElementById('searchInput').value;

  const html = notes.map(note => {
    const highlightedTitle = this.highlightSearchTerm(note.title, searchTerm);
    const preview = note.content.substring(0, 100);
    const highlightedPreview = this.highlightSearchTerm(preview, searchTerm);

    return `
      <div class="note-card" data-id="${note.id}">
        <h3>${highlightedTitle}</h3>
        <p class="note-preview">${highlightedPreview}...</p>
        <div class="note-meta">
          <span>${new Date(note.updatedAt).toLocaleString()}</span>
        </div>
      </div>
    `;
  }).join('');

  document.getElementById('notesList').innerHTML = html || '<p class="empty-state">没有找到匹配的笔记</p>';
}

// generated by hugo's coding agent

第九步:测试与调试

9.1 功能测试清单

在 Claude Code 的帮助下,我创建了完整的测试清单:

基础功能测试:

  • ✅ 创建新笔记
  • ✅ 编辑现有笔记
  • ✅ 删除笔记 (带确认对话框)
  • ✅ 搜索笔记
  • ✅ 数据持久化 (刷新页面后数据仍存在)

Markdown 测试:

  • ✅ 标题渲染 (H1-H6)
  • ✅ 粗体和斜体
  • ✅ 代码块语法高亮
  • ✅ 引用块样式
  • ✅ 列表 (有序和无序)
  • ✅ 链接和图片

JSON 可视化测试:

  • ✅ 简单 JSON 对象
  • ✅ 嵌套对象和数组
  • ✅ 特殊值 (null, boolean, number)
  • ✅ 长文本截断
  • ✅ URL 自动链接
  • ✅ 折叠/展开交互
  • ✅ 无效 JSON 错误提示

PWA 功能测试:

  • ✅ Service Worker 注册成功
  • ✅ 离线访问 (断网后仍可使用)
  • ✅ 应用安装提示
  • ✅ 独立窗口显示 (安装后)

响应式测试:

  • ✅ 桌面端 (1920x1080)
  • ✅ 平板端 (768x1024)
  • ✅ 手机端 (375x667)

9.2 Claude Code 辅助调试

Bug 案例 1: JSON 预览闪烁问题

当我报告"输入时 JSON 预览频繁闪烁"的问题后,Claude Code 立即识别出问题并提供解决方案:

// 问题代码 - 每次输入都重新渲染
document.getElementById('noteContent')
  .addEventListener('input', () => this.updatePreview());

// 优化方案 - 使用防抖
let previewTimeout;
document.getElementById('noteContent')
  .addEventListener('input', () => {
    clearTimeout(previewTimeout);
    previewTimeout = setTimeout(() => this.updatePreview(), 300);
  });

// generated by hugo's coding agent

Bug 案例 2: LocalStorage 溢出

Claude Code 主动提醒我 LocalStorage 的 5MB 限制,并建议添加错误处理:

saveNotes() {
  try {
    const data = JSON.stringify(this.notes);
    localStorage.setItem('notes', data);
  } catch (e) {
    if (e.name === 'QuotaExceededError') {
      alert('存储空间已满!请删除一些笔记。');
      console.error('LocalStorage 配额超出:', e);
    } else {
      alert('保存失败,请重试。');
      console.error('保存错误:', e);
    }
  }
}

// generated by hugo's coding agent

第十步:性能优化

10.1 渲染优化

Claude Code 建议使用批量 DOM 操作减少重绘:

// 优化前 - 逐个插入 DOM
notes.forEach(note => {
  const card = createNoteCard(note);
  container.appendChild(card);
});

// 优化后 - 一次性更新 innerHTML
renderNotesList(notes) {
  const html = notes.map(note => this.createNoteCardHTML(note)).join('');
  document.getElementById('notesList').innerHTML = html;

  // 重新绑定点击事件
  document.querySelectorAll('.note-card').forEach(card => {
    card.addEventListener('click', () => {
      this.showEditor(card.dataset.id);
    });
  });
}

// generated by hugo's coding agent

10.2 Service Worker 缓存优化

// 动态缓存策略 - 只缓存 GET 请求
self.addEventListener('fetch', event => {
  // 跳过非 GET 请求
  if (event.request.method !== 'GET') return;

  // 跳过 chrome-extension 等特殊协议
  if (!event.request.url.startsWith('http')) return;

  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

// generated by hugo's coding agent

关键收获:与 Claude Code 协作的最佳实践

通过这个项目,我总结了以下与 AI 编程助手高效协作的经验:

1. 清晰的需求描述

❌ 模糊: “做一个记事本应用” ✅ 明确: “创建一个支持 Markdown 和 JSON 可视化的 PWA 离线记事本,使用纯 JavaScript,数据存储在 LocalStorage”

2. 迭代式开发

不要试图一次性获得完整代码,而是:

  1. 先搭建基础框架
  2. 实现核心 CRUD 功能
  3. 添加 Markdown 支持
  4. 实现 JSON 可视化
  5. 添加 PWA 离线功能
  6. 优化 UI/UX
  7. 性能优化和 bug 修复

每个阶段都可以向 Claude Code 请求代码审查和改进建议。

3. 主动询问最佳实践

  • “这段代码有性能问题吗?”
  • “如何防止 XSS 攻击?”
  • “有没有更好的缓存策略?”
  • “如何处理 LocalStorage 溢出?”

Claude Code 会主动分析代码并提供专业建议。

4. 利用 AI 的知识广度

当遇到不熟悉的技术时:

  • “Service Worker 的三种缓存策略分别适用于什么场景?”
  • “PWA Manifest 的必填字段有哪些?”
  • “Marked.js 如何配置代码高亮?”

Claude Code 可以快速提供准确的技术信息,省去查文档的时间。

5. 代码审查和重构建议

定期请求 Claude Code 审查代码:

  • “这个函数是否符合单一职责原则?”
  • “有没有潜在的内存泄漏?”
  • “如何重构这段重复代码?”

6. 测试用例生成

询问 Claude Code:“为这个应用生成完整的测试清单”,它会帮你考虑各种边界情况。

7. 文档编写

让 Claude Code 帮助编写:

  • 代码注释
  • API 文档
  • README 文件
  • 用户手册

项目成果

最终,我们用不到 1000 行代码 (HTML + CSS + JavaScript) 实现了一个功能完整的 PWA 应用:

代码统计:

  • index.html: 57 行
  • styles.css: 538 行
  • app.js: 398 行
  • sw.js: 71 行
  • 总计: 1,064 行

开发时间:

  • 传统开发估计: 2-3 天
  • 使用 Claude Code: 约 4-6 小时

功能清单:

  • ✅ 完整的 CRUD 操作
  • ✅ Markdown 实时预览
  • ✅ JSON 智能可视化
  • ✅ 实时搜索过滤
  • ✅ PWA 离线支持
  • ✅ 响应式设计
  • ✅ XSS 防护
  • ✅ 性能优化

部署指南

本地开发

# 克隆项目
git clone https://github.com/yourusername/notepad.git
cd notepad

# 启动本地服务器 (需要 HTTPS 或 localhost 以使用 Service Worker)
python -m http.server 8000

# 或者使用 Node.js
npx http-server -p 8000

# 访问 http://localhost:8000

生产部署

部署到 GitHub Pages:

# 1. 创建 GitHub 仓库
# 2. 推送代码
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/yourusername/notepad.git
git push -u origin main

# 3. 启用 GitHub Pages (Settings → Pages)
# 4. 访问 https://yourusername.github.io/notepad

部署到 Vercel:

# 安装 Vercel CLI
npm i -g vercel

# 部署
vercel

# 生产部署
vercel --prod

部署到 Netlify:

# 拖拽项目文件夹到 Netlify Drop 即可
# 或使用 CLI
netlify deploy --prod

未来改进方向

与 Claude Code 讨论后,我们列出了一些可能的增强功能:

短期改进

  1. 数据导出/导入 - 支持 JSON 格式导出和导入
  2. 标签系统 - 为笔记添加标签分类
  3. 主题切换 - 支持亮色/暗色主题
  4. 快捷键支持 - Ctrl+S 保存,Ctrl+N 新建等

中期改进

  1. 云端同步 - 使用 Firebase 或 Supabase 实现多设备同步
  2. 协作功能 - 支持多人编辑和评论
  3. 富文本编辑器 - 集成 WYSIWYG 编辑器
  4. 附件支持 - 允许上传图片和文件

长期愿景

  1. AI 辅助写作 - 集成 AI 提供写作建议
  2. 语音输入 - 支持语音转文字
  3. OCR 功能 - 图片文字识别
  4. 智能分类 - AI 自动为笔记打标签和分类

总结

通过这个完整的项目实践,我们展示了 Claude Code 如何成为开发者的得力助手:

  1. 加速开发 - 快速生成样板代码和重复性代码
  2. 知识库 - 随时提供技术文档和最佳实践
  3. 代码审查 - 发现潜在问题和安全隐患
  4. 优化建议 - 提供性能优化和重构方案
  5. 学习工具 - 解释复杂概念和技术原理

但要注意:

  • AI 生成的代码需要人工审查
  • 复杂的业务逻辑仍需人类设计
  • 测试和调试是不可省略的步骤
  • 理解代码原理比单纯复制更重要

AI 辅助编程的未来已经到来,关键在于如何与 AI 有效协作,而不是被 AI 替代。 Claude Code 是我们的编程伙伴,而不是替代者 - 它放大了我们的能力,但最终的创造力和判断力仍然掌握在开发者手中。

希望这篇文章能帮助你更好地理解如何使用 Claude Code 进行实际项目开发。你可以在 GitHub 查看完整源码,或者访问 在线演示 体验这个应用。


相关资源:

标签: #Claude-Code #PWA #JavaScript #AI-Coding #Web-Development #Offline-First #Markdown #JSON-Visualization