diff --git a/IFLOW.md b/IFLOW.md index f324770..1460428 100644 --- a/IFLOW.md +++ b/IFLOW.md @@ -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` 导出 ### 注释规范 diff --git a/console.txt b/console.txt index e465495..e69de29 100644 --- a/console.txt +++ b/console.txt @@ -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 () - 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.._cache. @ 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 () - 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.._cache. @ 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 () - 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.._cache. @ 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 () - 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.._cache. @ 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 () - 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.._cache. @ 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 () - 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.._cache. @ 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 搜索栏失去焦点 diff --git a/package-lock.json b/package-lock.json index 004120f..cfedf63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/src/main.js b/src/main.js index a08a540..0437208 100644 --- a/src/main.js +++ b/src/main.js @@ -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进行路由管理 diff --git a/src/pages/NoteListPage.vue b/src/pages/NoteListPage.vue index 87e81e7..5604f02 100644 --- a/src/pages/NoteListPage.vue +++ b/src/pages/NoteListPage.vue @@ -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 diff --git a/src/pages/SettingsPage.vue b/src/pages/SettingsPage.vue index a22b8c4..9ef1019 100644 --- a/src/pages/SettingsPage.vue +++ b/src/pages/SettingsPage.vue @@ -45,7 +45,7 @@ const store = useAppStore() const router = useRouter() // 页面挂载时加载初始数据 -// 从localStorage加载用户设置和便签数据 +// 从Storage加载用户设置和便签数据 onMounted(() => { store.loadData() }) diff --git a/src/stores/useAppStore.js b/src/stores/useAppStore.js index 1a3a3b2..b49fc7d 100644 --- a/src/stores/useAppStore.js +++ b/src/stores/useAppStore.js @@ -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} */ 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} */ async saveNotes() { @@ -220,7 +220,7 @@ export const useAppStore = defineStore('app', { }, /** - * 保存文件夹数据到localStorage + * 保存文件夹数据到Storage * @returns {Promise} */ async saveFolders() { @@ -232,7 +232,7 @@ export const useAppStore = defineStore('app', { }, /** - * 保存设置数据到localStorage + * 保存设置数据到Storage * @returns {Promise} */ async saveSettings() { diff --git a/src/utils/indexedDBStorage.js b/src/utils/indexedDBStorage.js new file mode 100644 index 0000000..2c6179c --- /dev/null +++ b/src/utils/indexedDBStorage.js @@ -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} 数据库实例 + */ +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} 数据数组 + */ +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} + */ +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} 项对象或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} 添加的项 + */ +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} 更新后的项或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} 删除成功返回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} 便签数组 + */ +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} + */ +export const saveNotes = async (notes) => { + try { + await saveToStore(NOTES_STORE, notes) + } catch (error) { + console.error('Error saving notes:', error) + } +} + +/** + * 添加新便签 + * 创建一个新的便签对象并添加到便签列表中 + * @param {Object} note - 便签对象,包含便签内容和其他属性 + * @returns {Promise} 新创建的便签对象 + */ +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} 更新后的便签对象,如果未找到则返回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} 删除成功返回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} 文件夹数组 + */ +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} + */ +export const saveFolders = async (folders) => { + try { + await saveToStore(FOLDERS_STORE, folders) + } catch (error) { + console.error('Error saving folders:', error) + } +} + +/** + * 添加新文件夹 + * 创建一个新的文件夹对象并添加到文件夹列表中 + * @param {Object} folder - 文件夹对象,包含文件夹名称等属性 + * @returns {Promise} 新创建的文件夹对象 + */ +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} 设置对象,如果读取失败则返回默认设置 + */ +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} + */ +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} + */ +export const initDB = async () => { + try { + await openDB() + } catch (error) { + console.error('Error initializing database:', error) + } +} \ No newline at end of file diff --git a/src/utils/storage.js b/src/utils/storage.js deleted file mode 100644 index 05497a5..0000000 --- a/src/utils/storage.js +++ /dev/null @@ -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} 便签数组,如果读取失败则返回空数组 - */ -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} - */ -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} 新创建的便签对象 - */ -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} 更新后的便签对象,如果未找到则返回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} 删除成功返回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} 文件夹数组,如果读取失败则返回空数组 - */ -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} - */ -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} 新创建的文件夹对象 - */ -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} 设置对象,如果读取失败则返回默认设置 - */ -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} - */ -export const saveSettings = async (settings) => { - try { - localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); - } catch (error) { - console.error('Error saving settings:', error); - } -}; \ No newline at end of file