feat: 增加本地测评档案与长期追踪
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
AssessmentProfile,
|
||||
AssessmentRecord,
|
||||
MindScopeBackup,
|
||||
} from '@/lib/assessment-types';
|
||||
|
||||
const DB_NAME = 'mindscope';
|
||||
const DB_VERSION = 1;
|
||||
const PROFILE_STORE = 'profiles';
|
||||
const RECORD_STORE = 'records';
|
||||
const ACTIVE_PROFILE_KEY = 'mindscope_active_profile';
|
||||
|
||||
function createId(prefix: string) {
|
||||
return `${prefix}_${Date.now()}_${crypto.randomUUID()}`;
|
||||
}
|
||||
|
||||
function openDatabase(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(PROFILE_STORE)) {
|
||||
db.createObjectStore(PROFILE_STORE, { keyPath: 'id' });
|
||||
}
|
||||
if (!db.objectStoreNames.contains(RECORD_STORE)) {
|
||||
const records = db.createObjectStore(RECORD_STORE, { keyPath: 'id' });
|
||||
records.createIndex('profileId', 'profileId');
|
||||
records.createIndex('questionnaireId', 'questionnaireId');
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function requestResult<T>(request: IDBRequest<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
export function getActiveProfileId() {
|
||||
return localStorage.getItem(ACTIVE_PROFILE_KEY);
|
||||
}
|
||||
|
||||
export function setActiveProfileId(profileId: string) {
|
||||
localStorage.setItem(ACTIVE_PROFILE_KEY, profileId);
|
||||
window.dispatchEvent(new CustomEvent('mindscope-profile-change'));
|
||||
}
|
||||
|
||||
export async function getProfiles(): Promise<AssessmentProfile[]> {
|
||||
const db = await openDatabase();
|
||||
const profiles = await requestResult(
|
||||
db.transaction(PROFILE_STORE, 'readonly').objectStore(PROFILE_STORE).getAll(),
|
||||
);
|
||||
db.close();
|
||||
return profiles.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
||||
}
|
||||
|
||||
export async function createProfile(name: string): Promise<AssessmentProfile> {
|
||||
const now = new Date().toISOString();
|
||||
const profile: AssessmentProfile = {
|
||||
id: createId('profile'),
|
||||
name: name.trim(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
const db = await openDatabase();
|
||||
await requestResult(
|
||||
db.transaction(PROFILE_STORE, 'readwrite').objectStore(PROFILE_STORE).add(profile),
|
||||
);
|
||||
db.close();
|
||||
return profile;
|
||||
}
|
||||
|
||||
export async function ensureActiveProfile(): Promise<AssessmentProfile> {
|
||||
const profiles = await getProfiles();
|
||||
const activeId = getActiveProfileId();
|
||||
const active = profiles.find((profile) => profile.id === activeId);
|
||||
if (active) return active;
|
||||
const profile = profiles[0] || (await createProfile('我'));
|
||||
setActiveProfileId(profile.id);
|
||||
return profile;
|
||||
}
|
||||
|
||||
export async function renameProfile(profileId: string, name: string) {
|
||||
const db = await openDatabase();
|
||||
const store = db.transaction(PROFILE_STORE, 'readwrite').objectStore(PROFILE_STORE);
|
||||
const profile = await requestResult(store.get(profileId));
|
||||
if (profile) {
|
||||
await requestResult(
|
||||
store.put({ ...profile, name: name.trim(), updatedAt: new Date().toISOString() }),
|
||||
);
|
||||
}
|
||||
db.close();
|
||||
}
|
||||
|
||||
export async function deleteProfile(profileId: string) {
|
||||
const db = await openDatabase();
|
||||
const transaction = db.transaction([PROFILE_STORE, RECORD_STORE], 'readwrite');
|
||||
transaction.objectStore(PROFILE_STORE).delete(profileId);
|
||||
const index = transaction.objectStore(RECORD_STORE).index('profileId');
|
||||
const recordKeys = await requestResult(index.getAllKeys(profileId));
|
||||
recordKeys.forEach((key) => transaction.objectStore(RECORD_STORE).delete(key));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onerror = () => reject(transaction.error);
|
||||
});
|
||||
db.close();
|
||||
}
|
||||
|
||||
export async function addAssessmentRecord(
|
||||
record: Omit<AssessmentRecord, 'id'>,
|
||||
): Promise<AssessmentRecord> {
|
||||
const completeRecord = { ...record, id: createId('record') };
|
||||
const db = await openDatabase();
|
||||
await requestResult(
|
||||
db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE).add(completeRecord),
|
||||
);
|
||||
db.close();
|
||||
return completeRecord;
|
||||
}
|
||||
|
||||
export async function updateRecordAnalysis(recordId: string, analysisText: string) {
|
||||
const db = await openDatabase();
|
||||
const store = db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE);
|
||||
const record = await requestResult(store.get(recordId));
|
||||
if (record && record.analysisText !== analysisText) {
|
||||
await requestResult(store.put({ ...record, analysisText }));
|
||||
}
|
||||
db.close();
|
||||
}
|
||||
|
||||
export async function getRecords(profileId?: string): Promise<AssessmentRecord[]> {
|
||||
const db = await openDatabase();
|
||||
const store = db.transaction(RECORD_STORE, 'readonly').objectStore(RECORD_STORE);
|
||||
const records = profileId
|
||||
? await requestResult(store.index('profileId').getAll(profileId))
|
||||
: await requestResult(store.getAll());
|
||||
db.close();
|
||||
return records.sort((a, b) => b.completedAt.localeCompare(a.completedAt));
|
||||
}
|
||||
|
||||
export async function deleteRecord(recordId: string) {
|
||||
const db = await openDatabase();
|
||||
await requestResult(
|
||||
db.transaction(RECORD_STORE, 'readwrite').objectStore(RECORD_STORE).delete(recordId),
|
||||
);
|
||||
db.close();
|
||||
}
|
||||
|
||||
export async function exportBackup(): Promise<MindScopeBackup> {
|
||||
return {
|
||||
format: 'mindscope-backup',
|
||||
version: 1,
|
||||
exportedAt: new Date().toISOString(),
|
||||
profiles: await getProfiles(),
|
||||
records: await getRecords(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function importBackup(backup: MindScopeBackup) {
|
||||
if (backup.format !== 'mindscope-backup' || backup.version !== 1) {
|
||||
throw new Error('不支持的备份格式');
|
||||
}
|
||||
const db = await openDatabase();
|
||||
const transaction = db.transaction([PROFILE_STORE, RECORD_STORE], 'readwrite');
|
||||
backup.profiles.forEach((profile) => transaction.objectStore(PROFILE_STORE).put(profile));
|
||||
backup.records.forEach((record) => transaction.objectStore(RECORD_STORE).put(record));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onerror = () => reject(transaction.error);
|
||||
});
|
||||
db.close();
|
||||
}
|
||||
|
||||
export async function clearAllAssessmentData() {
|
||||
const db = await openDatabase();
|
||||
const transaction = db.transaction([PROFILE_STORE, RECORD_STORE], 'readwrite');
|
||||
transaction.objectStore(PROFILE_STORE).clear();
|
||||
transaction.objectStore(RECORD_STORE).clear();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onerror = () => reject(transaction.error);
|
||||
});
|
||||
db.close();
|
||||
localStorage.removeItem(ACTIVE_PROFILE_KEY);
|
||||
}
|
||||
Reference in New Issue
Block a user