feat(db): 添加Dexie数据库支持并重构数据存储结构

添加Dexie作为IndexedDB封装库,实现本地数据存储功能
重构数据接口定义和存储模块结构,优化类型定义
统一数据接口文件位置,增强代码可维护性
This commit is contained in:
2025-08-29 15:13:31 +08:00
parent bdc94d853d
commit bc7cc6810d
13 changed files with 1051 additions and 82 deletions

446
Cunkebao/src/utils/db.ts Normal file
View File

@@ -0,0 +1,446 @@
import Dexie, { Table } from "dexie";
import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data";
// 定义数据库表结构接口
export interface BaseEntity {
id?: number;
createdAt?: Date;
updatedAt?: Date;
}
export interface User extends BaseEntity {
name: string;
email: string;
avatar?: string;
status: "active" | "inactive";
}
export interface Message extends BaseEntity {
userId: number;
content: string;
type: "text" | "image" | "file";
isRead: boolean;
}
export interface ChatRoom extends BaseEntity {
name: string;
description?: string;
memberIds: number[];
lastMessageAt?: Date;
}
export interface Setting extends BaseEntity {
key: string;
value: any;
category: string;
}
// 数据库类
class AppDatabase extends Dexie {
users!: Table<User>;
messages!: Table<Message>;
chatRooms!: Table<ChatRoom>;
settings!: Table<Setting>;
constructor() {
super("CunkebaoDatabase");
this.version(1).stores({
users: "++id, name, email, status, createdAt",
messages: "++id, userId, type, isRead, createdAt",
chatRooms: "++id, name, lastMessageAt, createdAt",
settings: "++id, key, category, createdAt",
});
// 自动添加时间戳
this.users.hook("creating", (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
});
this.users.hook("updating", (modifications, primKey, obj, trans) => {
modifications.updatedAt = new Date();
});
this.messages.hook("creating", (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
});
this.chatRooms.hook("creating", (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
});
this.settings.hook("creating", (primKey, obj, trans) => {
obj.createdAt = new Date();
obj.updatedAt = new Date();
});
}
}
// 创建数据库实例
export const db = new AppDatabase();
// 通用数据库操作类
export class DatabaseService<T extends BaseEntity> {
constructor(private table: Table<T>) {}
// 基础 CRUD 操作
async create(
data: Omit<T, "id" | "createdAt" | "updatedAt">,
): Promise<number> {
return await this.table.add(data as T);
}
async createMany(
dataList: Omit<T, "id" | "createdAt" | "updatedAt">[],
): Promise<number[]> {
return await this.table.bulkAdd(dataList as T[], { allKeys: true });
}
async findById(id: number): Promise<T | undefined> {
return await this.table.get(id);
}
async findAll(): Promise<T[]> {
return await this.table.toArray();
}
async findByIds(ids: number[]): Promise<T[]> {
return await this.table.where("id").anyOf(ids).toArray();
}
async update(
id: number,
data: Partial<Omit<T, "id" | "createdAt">>,
): Promise<number> {
return await this.table.update(id, data);
}
async updateMany(
updates: { id: number; data: Partial<Omit<T, "id" | "createdAt">> }[],
): Promise<number> {
return await this.table.bulkUpdate(
updates.map(u => ({ key: u.id, changes: u.data })),
);
}
async delete(id: number): Promise<void> {
await this.table.delete(id);
}
async deleteMany(ids: number[]): Promise<void> {
await this.table.bulkDelete(ids);
}
async deleteAll(): Promise<void> {
await this.table.clear();
}
// 分页查询
async paginate(
page: number = 1,
limit: number = 10,
): Promise<{ data: T[]; total: number; page: number; limit: number }> {
const offset = (page - 1) * limit;
const total = await this.table.count();
const data = await this.table.offset(offset).limit(limit).toArray();
return { data, total, page, limit };
}
// 条件查询
async findWhere(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.equals(value)
.toArray();
}
async findWhereIn(field: keyof T, values: any[]): Promise<T[]> {
return await this.table
.where(field as string)
.anyOf(values)
.toArray();
}
async findWhereNot(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.notEqual(value)
.toArray();
}
// 排序查询
async findAllSorted(
field: keyof T,
direction: "asc" | "desc" = "asc",
): Promise<T[]> {
const collection = this.table.orderBy(field as string);
return direction === "desc"
? await collection.reverse().toArray()
: await collection.toArray();
}
// 搜索功能
async search(field: keyof T, keyword: string): Promise<T[]> {
return await this.table
.where(field as string)
.startsWithIgnoreCase(keyword)
.toArray();
}
// 统计功能
async count(): Promise<number> {
return await this.table.count();
}
async countWhere(field: keyof T, value: any): Promise<number> {
return await this.table
.where(field as string)
.equals(value)
.count();
}
// 存在性检查
async exists(id: number): Promise<boolean> {
const item = await this.table.get(id);
return !!item;
}
async existsWhere(field: keyof T, value: any): Promise<boolean> {
const count = await this.table
.where(field as string)
.equals(value)
.count();
return count > 0;
}
}
// 创建各表的服务实例
export const userService = new DatabaseService(db.users);
export const messageService = new DatabaseService(db.messages);
export const chatRoomService = new DatabaseService(db.chatRooms);
export const settingService = new DatabaseService(db.settings);
// 专门的业务方法
export class UserService extends DatabaseService<User> {
constructor() {
super(db.users);
}
async findByEmail(email: string): Promise<User | undefined> {
return await db.users.where("email").equals(email).first();
}
async findActiveUsers(): Promise<User[]> {
return await db.users.where("status").equals("active").toArray();
}
async searchByName(name: string): Promise<User[]> {
return await db.users.where("name").startsWithIgnoreCase(name).toArray();
}
async updateStatus(
id: number,
status: "active" | "inactive",
): Promise<number> {
return await this.update(id, { status });
}
}
export class MessageService extends DatabaseService<Message> {
constructor() {
super(db.messages);
}
async findByUserId(userId: number): Promise<Message[]> {
return await db.messages.where("userId").equals(userId).toArray();
}
async findUnreadMessages(): Promise<Message[]> {
return await db.messages.where("isRead").equals(false).toArray();
}
async markAsRead(id: number): Promise<number> {
return await this.update(id, { isRead: true });
}
async markAllAsRead(userId: number): Promise<number> {
const messages = await db.messages
.where("userId")
.equals(userId)
.and(msg => !msg.isRead)
.toArray();
const updates = messages.map(msg => ({
id: msg.id!,
data: { isRead: true },
}));
return await this.updateMany(updates);
}
async getRecentMessages(limit: number = 50): Promise<Message[]> {
return await db.messages
.orderBy("createdAt")
.reverse()
.limit(limit)
.toArray();
}
}
export class SettingService extends DatabaseService<Setting> {
constructor() {
super(db.settings);
}
async getSetting(key: string): Promise<any> {
const setting = await db.settings.where("key").equals(key).first();
return setting?.value;
}
async setSetting(
key: string,
value: any,
category: string = "general",
): Promise<number> {
const existing = await db.settings.where("key").equals(key).first();
if (existing) {
return await this.update(existing.id!, { value });
} else {
return await this.create({ key, value, category });
}
}
async getSettingsByCategory(category: string): Promise<Setting[]> {
return await db.settings.where("category").equals(category).toArray();
}
async deleteSetting(key: string): Promise<void> {
await db.settings.where("key").equals(key).delete();
}
}
// 数据库工具类
export class DatabaseUtils {
// 数据导出
static async exportData(): Promise<string> {
const data = {
users: await db.users.toArray(),
messages: await db.messages.toArray(),
chatRooms: await db.chatRooms.toArray(),
settings: await db.settings.toArray(),
exportedAt: new Date().toISOString(),
};
return JSON.stringify(data, null, 2);
}
// 数据导入
static async importData(jsonData: string): Promise<void> {
try {
const data = JSON.parse(jsonData);
await db.transaction(
"rw",
[db.users, db.messages, db.chatRooms, db.settings],
async () => {
if (data.users) await db.users.bulkPut(data.users);
if (data.messages) await db.messages.bulkPut(data.messages);
if (data.chatRooms) await db.chatRooms.bulkPut(data.chatRooms);
if (data.settings) await db.settings.bulkPut(data.settings);
},
);
} catch (error) {
throw new Error("导入数据失败: " + error);
}
}
// 清空所有数据
static async clearAllData(): Promise<void> {
await db.transaction(
"rw",
[db.users, db.messages, db.chatRooms, db.settings],
async () => {
await db.users.clear();
await db.messages.clear();
await db.chatRooms.clear();
await db.settings.clear();
},
);
}
// 获取数据库统计信息
static async getStats(): Promise<{
users: number;
messages: number;
chatRooms: number;
settings: number;
totalSize: number;
}> {
const [users, messages, chatRooms, settings] = await Promise.all([
db.users.count(),
db.messages.count(),
db.chatRooms.count(),
db.settings.count(),
]);
// 估算数据库大小(简单估算)
const totalSize = users + messages + chatRooms + settings;
return { users, messages, chatRooms, settings, totalSize };
}
// 数据库健康检查
static async healthCheck(): Promise<{
status: "healthy" | "error";
message: string;
}> {
try {
await db.users.limit(1).toArray();
return { status: "healthy", message: "数据库连接正常" };
} catch (error) {
return { status: "error", message: "数据库连接异常: " + error };
}
}
// 数据备份到文件
static async backupToFile(): Promise<void> {
const data = await this.exportData();
const blob = new Blob([data], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `cunkebao-backup-${new Date().toISOString().split("T")[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// 从文件恢复数据
static async restoreFromFile(file: File): Promise<void> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async e => {
try {
const jsonData = e.target?.result as string;
await this.importData(jsonData);
resolve();
} catch (error) {
reject(error);
}
};
reader.onerror = () => reject(new Error("文件读取失败"));
reader.readAsText(file);
});
}
}
// 创建业务服务实例
export const userBusinessService = new UserService();
export const messageBusinessService = new MessageService();
export const settingBusinessService = new SettingService();
// 默认导出数据库实例
export default db;