优化: 将本地存储从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

@@ -12,7 +12,7 @@
* **路由**: Vue Router
* **UI 组件库**: Ionic Vue (部分使用)
* **PWA 支持**: vite-plugin-pwa
* **本地存储**: localStorage (通过 `src/utils/storage.js` 封装)
* **本地存储**: IndexedDB (通过 `src/utils/indexedDBStorage.js` 封装)
* **CSS 预处理器**: Less
## 项目结构
@@ -45,7 +45,7 @@
* **回收站**: 临时存储已删除的便签,支持彻底删除。
* **多种排序方式**: 按更新时间、标题、星标状态排序。
* **PWA 支持**: 可安装为独立应用,支持离线使用。
* **本地存储**: 所有数据存储在浏览器的 `localStorage` 中。
* **本地存储**: 所有数据存储在浏览器的 `IndexedDB` 中。
* **深色模式**: (计划中) 支持切换深色/浅色主题。
* **云同步**: (计划中) 支持多设备间数据同步。
@@ -118,7 +118,7 @@ npm run android
* **路由**: Vue Router
* **UI 组件库**: Ionic Vue (部分使用)
* **PWA 支持**: vite-plugin-pwa
* **本地存储**: localStorage (通过 `src/utils/storage.js` 封装)
* **本地存储**: IndexedDB (通过 `src/utils/indexedDBStorage.js` 封装)
* **CSS 预处理器**: Less
* **CSS 命名**: 使用 BEM 命名规范,部分使用原子化 CSS 类名(以 `code-fun-` 开头)
* **响应式设计**: 使用 viewport 单位 (vw/vh) 和 CSS 变量实现响应式布局
@@ -149,7 +149,7 @@ npm run android
* Store 文件中按照 state, getters, actions 的顺序组织代码
* 异步操作使用 `async/await` 语法
* **工具函数**:
* 工具函数按功能模块划分文件,如 `storage.js`, `dateUtils.js`
* 工具函数按功能模块划分文件,如 `indexedDBStorage.js`, `dateUtils.js`
* 工具函数使用 `export const` 导出
### 注释规范

View File

@@ -1,310 +0,0 @@
prepare.js:1 🍍 "app" store installed 🆕
NoteListPage.vue:291 搜索栏获得焦点
NoteListPage.vue:296 搜索栏失去焦点
NoteListPage.vue:291 搜索栏获得焦点
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at isDirty (reactivity.esm-bundler.js:362:68)
at refreshComputed (reactivity.esm-bundler.js:380:90)
at get value (reactivity.esm-bundler.js:1655:5)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
isDirty @ reactivity.esm-bundler.js:362
refreshComputed @ reactivity.esm-bundler.js:380
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at get value (reactivity.esm-bundler.js:1655:5)
at ComputedRefImpl.fn (NoteListPage.vue:130:28)
at refreshComputed (reactivity.esm-bundler.js:391:28)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
(匿名) @ NoteListPage.vue:130
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at get value (reactivity.esm-bundler.js:1655:5)
at ComputedRefImpl.fn (NoteListPage.vue:130:28)
at refreshComputed (reactivity.esm-bundler.js:391:28)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
(匿名) @ NoteListPage.vue:130
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at get value (reactivity.esm-bundler.js:1655:5)
at ComputedRefImpl.fn (NoteListPage.vue:130:28)
at refreshComputed (reactivity.esm-bundler.js:391:28)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
(匿名) @ NoteListPage.vue:130
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at get value (reactivity.esm-bundler.js:1655:5)
at ComputedRefImpl.fn (NoteListPage.vue:130:28)
at refreshComputed (reactivity.esm-bundler.js:391:28)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
(匿名) @ NoteListPage.vue:130
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:104 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'toLowerCase')
at NoteListPage.vue:104:57
at wrappedFn (reactivity.esm-bundler.js:878:19)
at Array.filter (<anonymous>)
at apply (reactivity.esm-bundler.js:886:27)
at Proxy.filter (reactivity.esm-bundler.js:778:12)
at ComputedRefImpl.fn (NoteListPage.vue:102:22)
at refreshComputed (reactivity.esm-bundler.js:391:28)
at get value (reactivity.esm-bundler.js:1655:5)
at ComputedRefImpl.fn (NoteListPage.vue:130:28)
at refreshComputed (reactivity.esm-bundler.js:391:28)
(匿名) @ NoteListPage.vue:104
wrappedFn @ reactivity.esm-bundler.js:878
apply @ reactivity.esm-bundler.js:886
filter @ reactivity.esm-bundler.js:778
(匿名) @ NoteListPage.vue:102
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
(匿名) @ NoteListPage.vue:130
refreshComputed @ reactivity.esm-bundler.js:391
get value @ reactivity.esm-bundler.js:1655
unref @ reactivity.esm-bundler.js:1500
get @ reactivity.esm-bundler.js:1506
(匿名) @ NoteListPage.vue:35
renderFnWithContext @ runtime-core.esm-bundler.js:695
(匿名) @ runtime.js:286
renderComponentRoot @ runtime-core.esm-bundler.js:6590
componentUpdateFn @ runtime-core.esm-bundler.js:5468
run @ reactivity.esm-bundler.js:237
runIfDirty @ reactivity.esm-bundler.js:275
callWithErrorHandling @ runtime-core.esm-bundler.js:199
flushJobs @ runtime-core.esm-bundler.js:408
Promise.then
queueFlush @ runtime-core.esm-bundler.js:322
queueJob @ runtime-core.esm-bundler.js:317
effect2.scheduler @ runtime-core.esm-bundler.js:5519
trigger @ reactivity.esm-bundler.js:265
endBatch @ reactivity.esm-bundler.js:323
notify @ reactivity.esm-bundler.js:614
trigger @ reactivity.esm-bundler.js:588
set value @ reactivity.esm-bundler.js:1472
set @ reactivity.esm-bundler.js:1510
_createVNode.onUpdate:modelValue._cache.<computed>._cache.<computed> @ NoteListPage.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
emit @ runtime-core.esm-bundler.js:6473
(匿名) @ runtime-core.esm-bundler.js:8188
handleInput @ Search.vue:31
callWithErrorHandling @ runtime-core.esm-bundler.js:199
callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206
invoker @ runtime-dom.esm-bundler.js:730
NoteListPage.vue:296 搜索栏失去焦点

4
package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "smartisannote.vue",
"name": "smartisannote.re",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "smartisannote.vue",
"name": "smartisannote.re",
"version": "1.0.0",
"license": "ISC",
"dependencies": {

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);
}
};