diff --git a/src/App.vue b/src/App.vue index 1fb90f71..087985a5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,6 +8,7 @@ import { defineComponent, onMounted } from 'vue' import { storeToRefs } from 'pinia' import { useScreenStore, useMainStore, useSnapshotStore } from '@/store' +import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage' import { isPC } from './utils/common' import Editor from './views/Editor/index.vue' @@ -24,6 +25,7 @@ export default defineComponent({ setup() { const mainStore = useMainStore() const snapshotStore = useSnapshotStore() + const { databaseId } = storeToRefs(mainStore) const { screening } = storeToRefs(useScreenStore()) if (process.env.NODE_ENV === 'production') { @@ -35,6 +37,17 @@ export default defineComponent({ mainStore.setAvailableFonts() }) + // 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库 + window.addEventListener('unload', () => { + const discardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB) + const discardedDBList: string[] = discardedDB ? JSON.parse(discardedDB) : [] + + discardedDBList.push(databaseId.value) + + const newDiscardedDB = JSON.stringify(discardedDBList) + localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB) + }) + return { screening, isPC: isPC(), diff --git a/src/configs/storage.ts b/src/configs/storage.ts new file mode 100644 index 00000000..a849fc33 --- /dev/null +++ b/src/configs/storage.ts @@ -0,0 +1 @@ +export const LOCALSTORAGE_KEY_DISCARDED_DB = 'PPTIST_DISCARDED_DB' \ No newline at end of file diff --git a/src/store/main.ts b/src/store/main.ts index 7225978d..ae6a0e40 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -1,3 +1,4 @@ +import { customAlphabet } from 'nanoid' import { defineStore } from 'pinia' import { CreatingElement } from '@/types/edit' import { ToolbarStates } from '@/types/toolbar' @@ -8,8 +9,6 @@ import { isSupportFont } from '@/utils/font' import { useSlidesStore } from './slides' - - export interface MainState { activeElementIdList: string[]; handleElementId: string; @@ -31,8 +30,12 @@ export interface MainState { selectedTableCells: string[]; selectedSlidesIndex: number[]; dialogForExport: DialogForExportTypes; + databaseId: string; } +const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') +export const databaseId = nanoid(10) + export const useMainStore = defineStore('main', { state: (): MainState => ({ activeElementIdList: [], // 被选中的元素ID集合,包含 handleElementId @@ -55,6 +58,7 @@ export const useMainStore = defineStore('main', { isScaling: false, // 正在进行元素缩放 selectedSlidesIndex: [], // 当前被选中的页面索引集合 dialogForExport: '', // 导出面板 + databaseId, // 标识当前应用的indexedDB数据库ID }), getters: { diff --git a/src/store/snapshot.ts b/src/store/snapshot.ts index 0dfda5ee..0fea0efe 100644 --- a/src/store/snapshot.ts +++ b/src/store/snapshot.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { IndexableTypeArray } from 'dexie' -import { snapshotDB, Snapshot } from '@/utils/database' +import { db, deleteDiscardedDB, Snapshot } from '@/utils/database' import { useSlidesStore } from './slides' import { useMainStore } from './main' @@ -36,18 +36,13 @@ export const useSnapshotStore = defineStore('snapshot', { async initSnapshotDatabase() { const slidesStore = useSlidesStore() - const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray() - const lastSnapshot = snapshots.slice(-1)[0] - - if (lastSnapshot) { - snapshotDB.snapshots.clear() - } + await deleteDiscardedDB() const newFirstSnapshot = { index: slidesStore.slideIndex, slides: slidesStore.slides, } - await snapshotDB.snapshots.add(newFirstSnapshot) + await db.snapshots.add(newFirstSnapshot) this.setSnapshotCursor(0) this.setSnapshotLength(1) }, @@ -56,7 +51,7 @@ export const useSnapshotStore = defineStore('snapshot', { const slidesStore = useSlidesStore() // 获取当前indexeddb中全部快照的ID - const allKeys = await snapshotDB.snapshots.orderBy('id').keys() + const allKeys = await db.snapshots.orderBy('id').keys() let needDeleteKeys: IndexableTypeArray = [] @@ -72,7 +67,7 @@ export const useSnapshotStore = defineStore('snapshot', { index: slidesStore.slideIndex, slides: slidesStore.slides, } - await snapshotDB.snapshots.add(snapshot) + await db.snapshots.add(snapshot) // 计算当前快照长度,用于设置快照指针的位置(此时指针应该处在最后一位,即:快照长度 - 1) let snapshotLength = allKeys.length - needDeleteKeys.length + 1 @@ -87,10 +82,10 @@ export const useSnapshotStore = defineStore('snapshot', { // 快照数大于1时,需要保证撤回操作后维持页面焦点不变:也就是将倒数第二个快照对应的索引设置为当前页的索引 // https://github.com/pipipi-pikachu/PPTist/issues/27 if (snapshotLength >= 2) { - snapshotDB.snapshots.update(allKeys[snapshotLength - 2] as number, { index: slidesStore.slideIndex }) + db.snapshots.update(allKeys[snapshotLength - 2] as number, { index: slidesStore.slideIndex }) } - await snapshotDB.snapshots.bulkDelete(needDeleteKeys) + await db.snapshots.bulkDelete(needDeleteKeys) this.setSnapshotCursor(snapshotLength - 1) this.setSnapshotLength(snapshotLength) @@ -103,7 +98,7 @@ export const useSnapshotStore = defineStore('snapshot', { const mainStore = useMainStore() const snapshotCursor = this.snapshotCursor - 1 - const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray() + const snapshots: Snapshot[] = await db.snapshots.orderBy('id').toArray() const snapshot = snapshots[snapshotCursor] const { index, slides } = snapshot @@ -122,7 +117,7 @@ export const useSnapshotStore = defineStore('snapshot', { const mainStore = useMainStore() const snapshotCursor = this.snapshotCursor + 1 - const snapshots: Snapshot[] = await snapshotDB.snapshots.orderBy('id').toArray() + const snapshots: Snapshot[] = await db.snapshots.orderBy('id').toArray() const snapshot = snapshots[snapshotCursor] const { index, slides } = snapshot diff --git a/src/utils/database.ts b/src/utils/database.ts index 2afc254f..7354bc52 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,16 +1,46 @@ import Dexie from 'dexie' +import { databaseId } from '@/store/main' import { Slide } from '@/types/slides' +import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage' export interface Snapshot { index: number; slides: Slide[]; } -class SnapshotDatabase extends Dexie { +const databaseNamePrefix = 'PPTist' + +// 删除失效/过期的数据库 +// 应用关闭时(关闭或刷新浏览器),会将其数据库ID记录在 localStorage 中,表示该ID指向的数据库已失效 +// 当应用初始化时,检查当前所有数据库,将被记录失效的数据库删除 +// 另外,距离初始化时间超过12小时的数据库也将被删除(这是为了防止出现因以外未被正确删除的库) +export const deleteDiscardedDB = async () => { + const now = new Date().getTime() + + const localStorageDiscardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB) + const localStorageDiscardedDBList: string[] = localStorageDiscardedDB ? JSON.parse(localStorageDiscardedDB) : [] + + const databaseNames = await Dexie.getDatabaseNames() + const discardedDBNames = databaseNames.filter(name => { + if (name.indexOf(databaseNamePrefix) === -1) return false + + const [prefix, id, time] = name.split('_') + if (prefix !== databaseNamePrefix || !id || !time) return true + if (localStorageDiscardedDBList.includes(id)) return true + if (now - (+time) >= 1000 * 60 * 60 * 12) return true + + return false + }) + + for (const name of discardedDBNames) Dexie.delete(name) + localStorage.removeItem(LOCALSTORAGE_KEY_DISCARDED_DB) +} + +class PPTistDB extends Dexie { public snapshots: Dexie.Table public constructor() { - super('SnapshotDatabase') + super(`${databaseNamePrefix}_${databaseId}_${new Date().getTime()}`) this.version(1).stores({ snapshots: '++id' }) @@ -18,4 +48,4 @@ class SnapshotDatabase extends Dexie { } } -export const snapshotDB = new SnapshotDatabase() \ No newline at end of file +export const db = new PPTistDB() \ No newline at end of file