163 lines
4.6 KiB
JavaScript
163 lines
4.6 KiB
JavaScript
// IndexedDB 存储封装
|
|
const DB = (function () {
|
|
const DB_NAME = 'calendar_reminder';
|
|
const DB_VERSION = 2; // 升级版本以创建 outbox 队列
|
|
const STORE_EVENTS = 'events';
|
|
const STORE_OUTBOX = 'outbox';
|
|
|
|
let _db;
|
|
|
|
function openDB() {
|
|
return new Promise((resolve, reject) => {
|
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
|
req.onupgradeneeded = (ev) => {
|
|
const db = ev.target.result;
|
|
// 事件存储
|
|
if (!db.objectStoreNames.contains(STORE_EVENTS)) {
|
|
const store = db.createObjectStore(STORE_EVENTS, { keyPath: 'id' });
|
|
store.createIndex('by_start', 'start');
|
|
store.createIndex('by_updated', 'updatedAt');
|
|
}
|
|
// 离线 outbox 队列
|
|
if (!db.objectStoreNames.contains(STORE_OUTBOX)) {
|
|
db.createObjectStore(STORE_OUTBOX, { keyPath: 'id' });
|
|
}
|
|
};
|
|
req.onsuccess = () => {
|
|
_db = req.result;
|
|
resolve(_db);
|
|
};
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function tx(storeName, mode = 'readonly') {
|
|
const t = _db.transaction(storeName, mode);
|
|
return t.objectStore(storeName);
|
|
}
|
|
|
|
async function initDB() {
|
|
if (_db) return _db;
|
|
return await openDB();
|
|
}
|
|
|
|
function addEvent(ev) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_EVENTS, 'readwrite');
|
|
const req = store.add(ev);
|
|
req.onsuccess = () => resolve(ev);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function updateEvent(ev) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_EVENTS, 'readwrite');
|
|
const req = store.put(ev);
|
|
req.onsuccess = () => resolve(ev);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function deleteEvent(id) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_EVENTS, 'readwrite');
|
|
const req = store.delete(id);
|
|
req.onsuccess = () => resolve();
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function getEvent(id) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_EVENTS, 'readonly');
|
|
const req = store.get(id);
|
|
req.onsuccess = () => resolve(req.result);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function listEventsByMonth(year, month) {
|
|
// month: 1..12
|
|
const start = new Date(Date.UTC(year, month - 1, 1, 0, 0, 0));
|
|
const end = new Date(Date.UTC(year, month, 0, 23, 59, 59));
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_EVENTS, 'readonly');
|
|
const idx = store.index('by_start');
|
|
const range = IDBKeyRange.bound(start.toISOString(), end.toISOString());
|
|
const result = [];
|
|
const req = idx.openCursor(range);
|
|
req.onsuccess = (e) => {
|
|
const cursor = e.target.result;
|
|
if (cursor) {
|
|
result.push(cursor.value);
|
|
cursor.continue();
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
};
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
// --- Outbox 队列 ---
|
|
function genId() { return Date.now() + '-' + Math.random().toString(36).slice(2); }
|
|
|
|
function addOutbox(entry) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_OUTBOX, 'readwrite');
|
|
const req = store.add(entry);
|
|
req.onsuccess = () => resolve(entry);
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function addOutboxUpsert(ev) {
|
|
const entry = { id: genId(), type: 'upsert', payload: ev, timestamp: new Date().toISOString() };
|
|
return addOutbox(entry);
|
|
}
|
|
|
|
function addOutboxDelete(id) {
|
|
const entry = { id: genId(), type: 'delete', payload: { id }, timestamp: new Date().toISOString() };
|
|
return addOutbox(entry);
|
|
}
|
|
|
|
function listOutbox() {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_OUTBOX, 'readonly');
|
|
const result = [];
|
|
const req = store.openCursor();
|
|
req.onsuccess = (e) => {
|
|
const cursor = e.target.result;
|
|
if (cursor) { result.push(cursor.value); cursor.continue(); }
|
|
else { resolve(result); }
|
|
};
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
function removeOutbox(id) {
|
|
return new Promise((resolve, reject) => {
|
|
const store = tx(STORE_OUTBOX, 'readwrite');
|
|
const req = store.delete(id);
|
|
req.onsuccess = () => resolve();
|
|
req.onerror = () => reject(req.error);
|
|
});
|
|
}
|
|
|
|
return {
|
|
initDB,
|
|
addEvent,
|
|
updateEvent,
|
|
deleteEvent,
|
|
getEvent,
|
|
listEventsByMonth,
|
|
// outbox
|
|
addOutboxUpsert,
|
|
addOutboxDelete,
|
|
listOutbox,
|
|
removeOutbox,
|
|
};
|
|
})();
|
|
|
|
window.DB = DB; |