====== 第十二部分:实战案例 ======
===== 12.1 离线笔记应用 =====
===== 12.1.1 需求分析 =====
功能需求:
* 创建、编辑、删除笔记
* 支持富文本内容
* 按标签分类
* 全文搜索
* 离线可用,联网后同步
===== 12.1.2 数据库设计 =====
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);
});
}
===== 12.1.3 核心功能实现 =====
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);
});
}
}
===== 12.2 离线优先的待办事项应用 =====
===== 12.2.1 数据库设计 =====
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);
});
}
===== 12.2.2 核心功能 =====
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');
}
}
===== 12.3 图片库应用 =====
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);
});
}
}
===== 12.4 本章小结 =====
本章通过三个实战案例展示了 IndexedDB 的应用:
* 离线笔记应用:多存储设计、全文搜索、标签分类
* 待办事项应用:状态管理、活动日志、过期查询
* 图片库应用:二进制数据存储、缩略图生成
继续阅读 [[part13-compare|第十三部分:存储技术对比]]。