第十二部分:实战案例

功能需求:

  • 创建、编辑、删除笔记
  • 支持富文本内容
  • 按标签分类
  • 全文搜索
  • 离线可用,联网后同步
const DB_NAME = 'NotesApp';
const DB_VERSION = 1;
 
function initDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);
 
    request.onupgradeneeded = (event) => {
      const db = event.target.result;
 
      // 笔记存储
      const noteStore = db.createObjectStore('notes', { 
        keyPath: 'id',
        autoIncrement: true 
      });
      noteStore.createIndex('updatedAtIndex', 'updatedAt', { unique: false });
      noteStore.createIndex('tagIndex', 'tags', { multiEntry: true });
 
      // 标签存储
      const tagStore = db.createObjectStore('tags', { 
        keyPath: 'name' 
      });
 
      // 同步队列
      const syncStore = db.createObjectStore('syncQueue', { 
        keyPath: 'id',
        autoIncrement: true 
      });
      syncStore.createIndex('statusIndex', 'status', { unique: false });
    };
 
    request.onsuccess = (event) => resolve(event.target.result);
    request.onerror = (event) => reject(event.target.error);
  });
}
class NotesApp {
  constructor(db) {
    this.db = db;
  }
 
  async createNote(title, content, tags = []) {
    const note = {
      title,
      content,
      tags,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      syncStatus: 'pending'
    };
 
    const transaction = this.db.transaction(['notes', 'syncQueue'], 'readwrite');
    const noteStore = transaction.objectStore('notes');
    const syncStore = transaction.objectStore('syncQueue');
 
    return new Promise((resolve, reject) => {
      const request = noteStore.add(note);
 
      request.onsuccess = () => {
        const noteId = request.result;
 
        // 添加到同步队列
        syncStore.add({
          action: 'create',
          storeName: 'notes',
          data: { ...note, id: noteId },
          status: 'pending',
          createdAt: new Date().toISOString()
        });
 
        resolve(noteId);
      };
 
      request.onerror = () => reject(request.error);
    });
  }
 
  async searchNotes(query) {
    const transaction = this.db.transaction(['notes'], 'readonly');
    const store = transaction.objectStore('notes');
 
    return new Promise((resolve, reject) => {
      const results = [];
      const request = store.openCursor();
 
      request.onsuccess = (event) => {
        const cursor = event.target.result;
 
        if (cursor) {
          const note = cursor.value;
          // 简单全文搜索
          if (note.title.includes(query) || note.content.includes(query)) {
            results.push(note);
          }
          cursor.continue();
        } else {
          resolve(results);
        }
      };
 
      request.onerror = () => reject(request.error);
    });
  }
 
  async getNotesByTag(tag) {
    const transaction = this.db.transaction(['notes'], 'readonly');
    const store = transaction.objectStore('notes');
    const index = store.index('tagIndex');
 
    return index.getAll(tag);
  }
 
  async getRecentNotes(limit = 10) {
    const transaction = this.db.transaction(['notes'], 'readonly');
    const store = transaction.objectStore('notes');
    const index = store.index('updatedAtIndex');
 
    // 按更新时间倒序
    return new Promise((resolve, reject) => {
      const results = [];
      const request = index.openCursor(null, 'prev');
 
      request.onsuccess = (event) => {
        const cursor = event.target.result;
 
        if (cursor && results.length < limit) {
          results.push(cursor.value);
          cursor.continue();
        } else {
          resolve(results);
        }
      };
 
      request.onerror = () => reject(request.error);
    });
  }
}
function initTodoDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('TodoApp', 1);
 
    request.onupgradeneeded = (event) => {
      const db = event.target.result;
 
      // 待办事项
      const todoStore = db.createObjectStore('todos', { 
        keyPath: 'id',
        autoIncrement: true 
      });
      todoStore.createIndex('statusIndex', 'status', { unique: false });
      todoStore.createIndex('priorityIndex', 'priority', { unique: false });
      todoStore.createIndex('dueDateIndex', 'dueDate', { unique: false });
      todoStore.createIndex('projectIndex', 'projectId', { unique: false });
 
      // 项目
      const projectStore = db.createObjectStore('projects', { 
        keyPath: 'id',
        autoIncrement: true 
      });
 
      // 活动日志
      const logStore = db.createObjectStore('activityLogs', { 
        keyPath: 'id',
        autoIncrement: true 
      });
      logStore.createIndex('todoIndex', 'todoId', { unique: false });
      logStore.createIndex('timeIndex', 'timestamp', { unique: false });
    };
 
    request.onsuccess = (event) => resolve(event.target.result);
    request.onerror = (event) => reject(event.target.error);
  });
}
class TodoApp {
  constructor(db) {
    this.db = db;
  }
 
  async addTodo(todo) {
    const transaction = this.db.transaction(['todos', 'activityLogs'], 'readwrite');
    const todoStore = transaction.objectStore('todos');
    const logStore = transaction.objectStore('activityLogs');
 
    const todoWithMeta = {
      ...todo,
      status: 'pending',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
 
    return new Promise((resolve, reject) => {
      const request = todoStore.add(todoWithMeta);
 
      request.onsuccess = () => {
        const todoId = request.result;
 
        // 记录活动
        logStore.add({
          todoId,
          action: 'created',
          timestamp: new Date().toISOString(),
          details: { title: todo.title }
        });
 
        resolve(todoId);
      };
 
      request.onerror = () => reject(request.error);
    });
  }
 
  async completeTodo(todoId) {
    const transaction = this.db.transaction(['todos', 'activityLogs'], 'readwrite');
    const todoStore = transaction.objectStore('todos');
    const logStore = transaction.objectStore('activityLogs');
 
    const todo = await new Promise((resolve, reject) => {
      const request = todoStore.get(todoId);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
 
    if (!todo) throw new Error('Todo not found');
 
    todo.status = 'completed';
    todo.completedAt = new Date().toISOString();
    todo.updatedAt = new Date().toISOString();
 
    return new Promise((resolve, reject) => {
      const request = todoStore.put(todo);
 
      request.onsuccess = () => {
        logStore.add({
          todoId,
          action: 'completed',
          timestamp: new Date().toISOString()
        });
 
        resolve(todo);
      };
 
      request.onerror = () => reject(request.error);
    });
  }
 
  async getTodosByStatus(status) {
    const transaction = this.db.transaction(['todos'], 'readonly');
    const store = transaction.objectStore('todos');
    const index = store.index('statusIndex');
 
    return index.getAll(status);
  }
 
  async getOverdueTodos() {
    const transaction = this.db.transaction(['todos'], 'readonly');
    const store = transaction.objectStore('todos');
    const index = store.index('dueDateIndex');
 
    const now = new Date().toISOString();
    const range = IDBKeyRange.upperBound(now);
 
    const todos = await index.getAll(range);
    return todos.filter(todo => todo.status !== 'completed');
  }
}
class ImageGalleryDB {
  constructor(db) {
    this.db = db;
  }
 
  async storeImage(file, metadata = {}) {
    const arrayBuffer = await file.arrayBuffer();
 
    const image = {
      name: file.name,
      type: file.type,
      size: file.size,
      data: arrayBuffer,
      thumbnail: await this.generateThumbnail(arrayBuffer),
      metadata: {
        ...metadata,
        uploadedAt: new Date().toISOString()
      }
    };
 
    const transaction = this.db.transaction(['images'], 'readwrite');
    const store = transaction.objectStore('images');
 
    return new Promise((resolve, reject) => {
      const request = store.add(image);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }
 
  async generateThumbnail(arrayBuffer) {
    // 简化示例:实际应使用 Canvas 调整图片大小
    return arrayBuffer.slice(0, 1024); // 占位符
  }
 
  async getImage(id) {
    const transaction = this.db.transaction(['images'], 'readonly');
    const store = transaction.objectStore('images');
 
    return new Promise((resolve, reject) => {
      const request = store.get(id);
      request.onsuccess = () => {
        const record = request.result;
        if (record) {
          // 转为 Blob URL
          const blob = new Blob([record.data], { type: record.type });
          record.url = URL.createObjectURL(blob);
        }
        resolve(record);
      };
      request.onerror = () => reject(request.error);
    });
  }
}

本章通过三个实战案例展示了 IndexedDB 的应用:

  • 离线笔记应用:多存储设计、全文搜索、标签分类
  • 待办事项应用:状态管理、活动日志、过期查询
  • 图片库应用:二进制数据存储、缩略图生成

继续阅读 第十三部分:存储技术对比

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • indexeddb/part12-cases.txt
  • 最后更改: 2026/04/27 19:54
  • 张叶安