优化: 将本地存储从localStorage迁移至IndexedDB以支持更大数据量存储

This commit is contained in:
yuantao
2025-10-17 10:17:49 +08:00
parent ae375aee8c
commit 0a89eda20b
9 changed files with 505 additions and 530 deletions

View File

@@ -13,6 +13,9 @@ import FolderPage from './pages/FolderPage.vue'
// 设置页面
import SettingsPage from './pages/SettingsPage.vue'
// 导入数据库初始化函数
import { initDB } from './utils/indexedDBStorage'
// 配置路由规则
// 定义应用的所有路由路径和对应的组件
const routes = [
@@ -42,6 +45,16 @@ const router = createRouter({
// 创建并挂载Vue应用实例
// 配置Pinia状态管理和Vue Router路由
const app = createApp(App)
// 初始化数据库
initDB().then(() => {
console.log('数据库初始化成功')
}).catch(error => {
console.error('数据库初始化失败:', error)
// 即使数据库初始化失败,也继续启动应用
// 这样可以确保应用在没有IndexedDB支持的环境中仍然可以运行
})
// 使用Pinia进行状态管理
app.use(createPinia())
// 使用Vue Router进行路由管理

View File

@@ -73,7 +73,7 @@ onMounted(() => {
// 加载预设的模拟数据
store.loadMockData()
} else {
// 从localStorage加载用户数据
// 从Storage加载用户数据
store.loadData()
}
})
@@ -102,8 +102,8 @@ const filteredNotes = computed(() => {
return store.notes.filter(note => {
// 先检查搜索条件
const matchesSearch = !lowerCaseQuery ||
(note.title && note.title.toLowerCase().includes(lowerCaseQuery)) ||
(note.content && note.content.toLowerCase().includes(lowerCaseQuery))
(note.title && typeof note.title === 'string' && note.title.toLowerCase().includes(lowerCaseQuery)) ||
(note.content && typeof note.content === 'string' && note.content.toLowerCase().includes(lowerCaseQuery))
if (!matchesSearch) return false

View File

@@ -45,7 +45,7 @@ const store = useAppStore()
const router = useRouter()
// 页面挂载时加载初始数据
// 从localStorage加载用户设置和便签数据
// 从Storage加载用户设置和便签数据
onMounted(() => {
store.loadData()
})

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import * as storage from '../utils/storage'
import * as storage from '../utils/indexedDBStorage'
import { getCurrentDateTime, getPastDate } from '../utils/dateUtils'
/**
@@ -48,13 +48,13 @@ export const useAppStore = defineStore('app', {
actions: {
/**
* 初始化数据
* 从localStorage加载便签、文件夹和设置数据
* 从Storage加载便签、文件夹和设置数据
* 如果没有数据则加载预设的mock数据
* @returns {Promise<void>}
*/
async loadData() {
try {
// 从localStorage加载数据
// 从Storage加载数据
const loadedNotes = await storage.getNotes()
const loadedFolders = await storage.getFolders()
const loadedSettings = await storage.getSettings()
@@ -201,14 +201,14 @@ export const useAppStore = defineStore('app', {
this.folders = mockFolders
this.settings = mockSettings
// 保存到localStorage
// 保存到Storage
await storage.saveNotes(mockNotes)
await storage.saveFolders(mockFolders)
await storage.saveSettings(mockSettings)
},
/**
* 保存便签数据到localStorage
* 保存便签数据到Storage
* @returns {Promise<void>}
*/
async saveNotes() {
@@ -220,7 +220,7 @@ export const useAppStore = defineStore('app', {
},
/**
* 保存文件夹数据到localStorage
* 保存文件夹数据到Storage
* @returns {Promise<void>}
*/
async saveFolders() {
@@ -232,7 +232,7 @@ export const useAppStore = defineStore('app', {
},
/**
* 保存设置数据到localStorage
* 保存设置数据到Storage
* @returns {Promise<void>}
*/
async saveSettings() {

View File

@@ -0,0 +1,475 @@
import { getCurrentDateTime, getTimestamp } from './dateUtils'
// 数据库配置
const DB_NAME = 'SmartisanNoteDB'
const DB_VERSION = 2 // 更新版本号以确保数据库重新创建
const NOTES_STORE = 'notes'
const FOLDERS_STORE = 'folders'
const SETTINGS_STORE = 'settings'
let db = null
/**
* 打开数据库连接
* @returns {Promise<IDBDatabase>} 数据库实例
*/
const openDB = () => {
return new Promise((resolve, reject) => {
if (db) {
return resolve(db)
}
const request = indexedDB.open(DB_NAME, DB_VERSION)
request.onerror = () => {
reject(new Error('无法打开数据库'))
}
request.onsuccess = () => {
db = request.result
resolve(db)
}
request.onupgradeneeded = (event) => {
const database = event.target.result
// 删除现有的对象存储(如果版本已更改)
if (event.oldVersion > 0) {
if (database.objectStoreNames.contains(NOTES_STORE)) {
database.deleteObjectStore(NOTES_STORE)
}
if (database.objectStoreNames.contains(FOLDERS_STORE)) {
database.deleteObjectStore(FOLDERS_STORE)
}
if (database.objectStoreNames.contains(SETTINGS_STORE)) {
database.deleteObjectStore(SETTINGS_STORE)
}
}
// 创建便签存储对象
const notesStore = database.createObjectStore(NOTES_STORE, { keyPath: 'id' })
notesStore.createIndex('folderId', 'folderId', { unique: false })
notesStore.createIndex('isStarred', 'isStarred', { unique: false })
notesStore.createIndex('isDeleted', 'isDeleted', { unique: false })
notesStore.createIndex('createdAt', 'createdAt', { unique: false })
notesStore.createIndex('updatedAt', 'updatedAt', { unique: false })
// 创建文件夹存储对象
database.createObjectStore(FOLDERS_STORE, { keyPath: 'id' })
// 创建设置存储对象
database.createObjectStore(SETTINGS_STORE)
}
})
}
/**
* 从存储中获取数据
* @param {string} storeName - 存储名称
* @returns {Promise<Array>} 数据数组
*/
const getAllFromStore = async (storeName) => {
const database = await openDB()
const transaction = database.transaction([storeName], 'readonly')
const store = transaction.objectStore(storeName)
const request = store.getAll()
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(request.result || [])
}
request.onerror = () => {
reject(new Error(`获取 ${storeName} 数据失败`))
}
})
}
/**
* 保存数据到存储
* @param {string} storeName - 存储名称
* @param {Array} data - 要保存的数据数组
* @returns {Promise<void>}
*/
const saveToStore = async (storeName, data) => {
const database = await openDB()
const transaction = database.transaction([storeName], 'readwrite')
const store = transaction.objectStore(storeName)
// 清除现有数据
await new Promise((resolve, reject) => {
const clearRequest = store.clear()
clearRequest.onsuccess = () => resolve()
clearRequest.onerror = () => reject(new Error(`清除 ${storeName} 数据失败`))
})
// 添加新数据
for (const item of data) {
await new Promise((resolve, reject) => {
const addRequest = store.add(item)
addRequest.onsuccess = () => resolve()
addRequest.onerror = () => reject(new Error(`保存 ${storeName} 数据失败`))
})
}
}
/**
* 从存储中获取单个项
* @param {string} storeName - 存储名称
* @param {string} id - 项的ID
* @returns {Promise<Object|null>} 项对象或null
*/
const getFromStore = async (storeName, id) => {
const database = await openDB()
const transaction = database.transaction([storeName], 'readonly')
const store = transaction.objectStore(storeName)
const request = store.get(id)
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(request.result || null)
}
request.onerror = () => {
reject(new Error(`获取 ${storeName} 项失败`))
}
})
}
/**
* 向存储中添加项
* @param {string} storeName - 存储名称
* @param {Object} item - 要添加的项
* @returns {Promise<Object>} 添加的项
*/
const addToStore = async (storeName, item) => {
const database = await openDB()
const transaction = database.transaction([storeName], 'readwrite')
const store = transaction.objectStore(storeName)
const request = store.add(item)
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(item)
}
request.onerror = () => {
reject(new Error(`添加 ${storeName} 项失败`))
}
})
}
/**
* 更新存储中的项
* @param {string} storeName - 存储名称
* @param {string} id - 项的ID
* @param {Object} updates - 要更新的属性对象
* @returns {Promise<Object|null>} 更新后的项或null
*/
const updateInStore = async (storeName, id, updates) => {
const item = await getFromStore(storeName, id)
if (!item) return null
const updatedItem = { ...item, ...updates }
const database = await openDB()
const transaction = database.transaction([storeName], 'readwrite')
const store = transaction.objectStore(storeName)
const request = store.put(updatedItem)
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(updatedItem)
}
request.onerror = () => {
reject(new Error(`更新 ${storeName} 项失败`))
}
})
}
/**
* 从存储中删除项
* @param {string} storeName - 存储名称
* @param {string} id - 要删除的项的ID
* @returns {Promise<boolean>} 删除成功返回true失败返回false
*/
const deleteFromStore = async (storeName, id) => {
const database = await openDB()
const transaction = database.transaction([storeName], 'readwrite')
const store = transaction.objectStore(storeName)
const request = store.delete(id)
return new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(true)
}
request.onerror = () => {
reject(new Error(`删除 ${storeName} 项失败`))
}
})
}
// 便签操作函数
// 提供便签的增删改查功能
/**
* 获取所有便签数据
* 从IndexedDB中读取便签数据
* @returns {Promise<Array>} 便签数组
*/
export const getNotes = async () => {
try {
const notes = await getAllFromStore(NOTES_STORE)
return ensureNotesDefaults(notes)
} catch (error) {
console.error('Error getting notes:', error)
return []
}
}
/**
* 保存便签数据
* 将便签数组保存到IndexedDB
* @param {Array} notes - 便签数组
* @returns {Promise<void>}
*/
export const saveNotes = async (notes) => {
try {
await saveToStore(NOTES_STORE, notes)
} catch (error) {
console.error('Error saving notes:', error)
}
}
/**
* 添加新便签
* 创建一个新的便签对象并添加到便签列表中
* @param {Object} note - 便签对象,包含便签内容和其他属性
* @returns {Promise<Object>} 新创建的便签对象
*/
export const addNote = async (note) => {
try {
// 创建新的便签对象,添加必要的属性
const newNote = {
title: note.title || '',
content: note.content || '',
id: note.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: note.createdAt || getCurrentDateTime(), // 创建时间
updatedAt: note.updatedAt || getCurrentDateTime(), // 更新时间
isStarred: note.isStarred || false, // 是否加星
isTop: note.isTop || false, // 是否置顶
hasImage: note.hasImage || false, // 是否包含图片
isDeleted: note.isDeleted || false, // 是否已删除
deletedAt: note.deletedAt || null, // 删除时间
folderId: note.folderId || null, // 文件夹ID
...note
}
// 添加到存储
await addToStore(NOTES_STORE, newNote)
return newNote
} catch (error) {
console.error('Error adding note:', error)
throw error
}
}
/**
* 更新便签
* 根据ID查找并更新便签信息
* @param {string} id - 便签ID
* @param {Object} updates - 要更新的属性对象
* @returns {Promise<Object|null>} 更新后的便签对象如果未找到则返回null
*/
export const updateNote = async (id, updates) => {
try {
// 更新便签并保存
const updatedNote = await updateInStore(NOTES_STORE, id, {
...updates,
updatedAt: getCurrentDateTime() // 更新最后修改时间
})
return updatedNote
} catch (error) {
console.error('Error updating note:', error)
throw error
}
}
/**
* 删除便签
* 根据ID从便签列表中移除便签
* @param {string} id - 要删除的便签ID
* @returns {Promise<boolean>} 删除成功返回true未找到便签返回false
*/
export const deleteNote = async (id) => {
try {
// 从存储中删除
const result = await deleteFromStore(NOTES_STORE, id)
return result
} catch (error) {
console.error('Error deleting note:', error)
return false
}
}
// 文件夹操作函数
// 提供文件夹的增删改查功能
/**
* 获取所有文件夹数据
* 从IndexedDB中读取文件夹数据
* @returns {Promise<Array>} 文件夹数组
*/
export const getFolders = async () => {
try {
const folders = await getAllFromStore(FOLDERS_STORE)
return ensureFoldersDefaults(folders)
} catch (error) {
console.error('Error getting folders:', error)
return []
}
}
/**
* 保存文件夹数据
* 将文件夹数组保存到IndexedDB
* @param {Array} folders - 文件夹数组
* @returns {Promise<void>}
*/
export const saveFolders = async (folders) => {
try {
await saveToStore(FOLDERS_STORE, folders)
} catch (error) {
console.error('Error saving folders:', error)
}
}
/**
* 添加新文件夹
* 创建一个新的文件夹对象并添加到文件夹列表中
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
* @returns {Promise<Object>} 新创建的文件夹对象
*/
export const addFolder = async (folder) => {
try {
// 创建新的文件夹对象,添加必要的属性
const newFolder = {
name: folder.name || '',
id: folder.id || getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: folder.createdAt || getCurrentDateTime(), // 创建时间
...folder
}
// 添加到存储
await addToStore(FOLDERS_STORE, newFolder)
return newFolder
} catch (error) {
console.error('Error adding folder:', error)
throw error
}
}
// 设置操作函数
// 提供应用设置的读取和保存功能
/**
* 获取应用设置
* 从IndexedDB中读取设置数据
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
*/
export const getSettings = async () => {
try {
const database = await openDB()
const transaction = database.transaction([SETTINGS_STORE], 'readonly')
const store = transaction.objectStore(SETTINGS_STORE)
const request = store.get('settings')
const settings = await new Promise((resolve, reject) => {
request.onsuccess = () => {
resolve(request.result || { cloudSync: false, darkMode: false })
}
request.onerror = () => {
reject(new Error('获取设置失败'))
}
})
return settings
} catch (error) {
console.error('Error getting settings:', error)
// 出错时返回默认设置
return { cloudSync: false, darkMode: false }
}
}
/**
* 保存应用设置
* 将设置对象保存到IndexedDB
* @param {Object} settings - 设置对象
* @returns {Promise<void>}
*/
export const saveSettings = async (settings) => {
try {
const database = await openDB()
const transaction = database.transaction([SETTINGS_STORE], 'readwrite')
const store = transaction.objectStore(SETTINGS_STORE)
const request = store.put(settings, 'settings')
await new Promise((resolve, reject) => {
request.onsuccess = () => resolve()
request.onerror = () => reject(new Error('保存设置失败'))
})
} catch (error) {
console.error('Error saving settings:', error)
}
}
/**
* 确保数据有默认值
* @param {Array} notes - 便签数组
* @returns {Array} 处理后的便签数组
*/
const ensureNotesDefaults = (notes) => {
return notes.map(note => ({
title: note.title || '',
content: note.content || '',
id: note.id,
createdAt: note.createdAt,
updatedAt: note.updatedAt,
isStarred: note.isStarred || false,
isTop: note.isTop || false,
hasImage: note.hasImage || false,
isDeleted: note.isDeleted || false,
deletedAt: note.deletedAt || null,
folderId: note.folderId || null,
...note
}))
}
/**
* 确保文件夹数据有默认值
* @param {Array} folders - 文件夹数组
* @returns {Array} 处理后的文件夹数组
*/
const ensureFoldersDefaults = (folders) => {
return folders.map(folder => ({
name: folder.name || '',
id: folder.id,
createdAt: folder.createdAt,
...folder
}))
}
/**
* 初始化数据库
* @returns {Promise<void>}
*/
export const initDB = async () => {
try {
await openDB()
} catch (error) {
console.error('Error initializing database:', error)
}
}

View File

@@ -1,203 +0,0 @@
import { getCurrentDateTime, getTimestamp } from './dateUtils'
// 本地存储键名常量
// 用于在localStorage中标识不同类型的数据
const NOTES_KEY = 'notes'; // 便签数据键名
const FOLDERS_KEY = 'folders'; // 文件夹数据键名
const SETTINGS_KEY = 'settings'; // 设置数据键名
// 便签操作函数
// 提供便签的增删改查功能
/**
* 获取所有便签数据
* 从localStorage中读取便签数据并解析为JavaScript对象
* @returns {Promise<Array>} 便签数组,如果读取失败则返回空数组
*/
export const getNotes = async () => {
try {
const notesJson = localStorage.getItem(NOTES_KEY);
return notesJson ? JSON.parse(notesJson) : [];
} catch (error) {
console.error('Error getting notes:', error);
return [];
}
};
/**
* 保存便签数据
* 将便签数组转换为JSON字符串并保存到localStorage
* @param {Array} notes - 便签数组
* @returns {Promise<void>}
*/
export const saveNotes = async (notes) => {
try {
localStorage.setItem(NOTES_KEY, JSON.stringify(notes));
} catch (error) {
console.error('Error saving notes:', error);
}
};
/**
* 添加新便签
* 创建一个新的便签对象并添加到便签列表中
* @param {Object} note - 便签对象,包含便签内容和其他属性
* @returns {Promise<Object>} 新创建的便签对象
*/
export const addNote = async (note) => {
// 创建新的便签对象,添加必要的属性
const newNote = {
...note,
id: getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: getCurrentDateTime(), // 创建时间
updatedAt: getCurrentDateTime(), // 更新时间
isStarred: note.isStarred || false, // 是否加星
isTop: note.isTop || false, // 是否置顶
hasImage: note.hasImage || false, // 是否包含图片
isDeleted: note.isDeleted || false, // 是否已删除
deletedAt: note.deletedAt || null // 删除时间
};
// 获取现有便签列表,添加新便签并保存
const notes = await getNotes();
notes.push(newNote);
await saveNotes(notes);
return newNote;
};
/**
* 更新便签
* 根据ID查找并更新便签信息
* @param {string} id - 便签ID
* @param {Object} updates - 要更新的属性对象
* @returns {Promise<Object|null>} 更新后的便签对象如果未找到则返回null
*/
export const updateNote = async (id, updates) => {
// 获取所有便签并查找要更新的便签
const notes = await getNotes();
const index = notes.findIndex(note => note.id === id);
// 如果未找到指定ID的便签返回null
if (index === -1) return null;
// 创建更新后的便签对象
const updatedNote = {
...notes[index],
...updates,
updatedAt: getCurrentDateTime(), // 更新最后修改时间
};
// 更新便签列表并保存
notes[index] = updatedNote;
await saveNotes(notes);
return updatedNote;
};
/**
* 删除便签
* 根据ID从便签列表中移除便签
* @param {string} id - 要删除的便签ID
* @returns {Promise<boolean>} 删除成功返回true未找到便签返回false
*/
export const deleteNote = async (id) => {
// 获取所有便签并过滤掉要删除的便签
const notes = await getNotes();
const filteredNotes = notes.filter(note => note.id !== id);
// 如果便签数量没有变化,说明未找到要删除的便签
if (notes.length === filteredNotes.length) return false;
// 保存更新后的便签列表
await saveNotes(filteredNotes);
return true;
};
// 文件夹操作函数
// 提供文件夹的增删改查功能
/**
* 获取所有文件夹数据
* 从localStorage中读取文件夹数据并解析为JavaScript对象
* @returns {Promise<Array>} 文件夹数组,如果读取失败则返回空数组
*/
export const getFolders = async () => {
try {
const foldersJson = localStorage.getItem(FOLDERS_KEY);
return foldersJson ? JSON.parse(foldersJson) : [];
} catch (error) {
console.error('Error getting folders:', error);
return [];
}
};
/**
* 保存文件夹数据
* 将文件夹数组转换为JSON字符串并保存到localStorage
* @param {Array} folders - 文件夹数组
* @returns {Promise<void>}
*/
export const saveFolders = async (folders) => {
try {
localStorage.setItem(FOLDERS_KEY, JSON.stringify(folders));
} catch (error) {
console.error('Error saving folders:', error);
}
};
/**
* 添加新文件夹
* 创建一个新的文件夹对象并添加到文件夹列表中
* @param {Object} folder - 文件夹对象,包含文件夹名称等属性
* @returns {Promise<Object>} 新创建的文件夹对象
*/
export const addFolder = async (folder) => {
// 创建新的文件夹对象,添加必要的属性
const newFolder = {
...folder,
id: getTimestamp().toString(), // 使用时间戳生成唯一ID
createdAt: getCurrentDateTime(), // 创建时间
};
// 获取现有文件夹列表,添加新文件夹并保存
const folders = await getFolders();
folders.push(newFolder);
await saveFolders(folders);
return newFolder;
};
// 设置操作函数
// 提供应用设置的读取和保存功能
/**
* 获取应用设置
* 从localStorage中读取设置数据并解析为JavaScript对象
* @returns {Promise<Object>} 设置对象,如果读取失败则返回默认设置
*/
export const getSettings = async () => {
try {
const settingsJson = localStorage.getItem(SETTINGS_KEY);
// 如果没有保存的设置,返回默认设置
return settingsJson ? JSON.parse(settingsJson) : { cloudSync: false, darkMode: false };
} catch (error) {
console.error('Error getting settings:', error);
// 出错时返回默认设置
return { cloudSync: false, darkMode: false };
}
};
/**
* 保存应用设置
* 将设置对象转换为JSON字符串并保存到localStorage
* @param {Object} settings - 设置对象
* @returns {Promise<void>}
*/
export const saveSettings = async (settings) => {
try {
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
} catch (error) {
console.error('Error saving settings:', error);
}
};