From bc7cc6810d365c655b7e809df617f1f6d54ae0e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Fri, 29 Aug 2025 15:13:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(db):=20=E6=B7=BB=E5=8A=A0Dexie=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=94=AF=E6=8C=81=E5=B9=B6=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=AD=98=E5=82=A8=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加Dexie作为IndexedDB封装库,实现本地数据存储功能 重构数据接口定义和存储模块结构,优化类型定义 统一数据接口文件位置,增强代码可维护性 --- Cunkebao/package.json | 1 + Cunkebao/pnpm-lock.yaml | 8 + .../pc/ckbox/components/ChatWindow/index.tsx | 7 +- .../SidebarMenu/MessageList/index.tsx | 8 +- .../SidebarMenu/WechatFriends/index.tsx | 10 +- .../pc/ckbox/components/SidebarMenu/index.tsx | 12 +- Cunkebao/src/pages/pc/ckbox/data.ts | 46 +- Cunkebao/src/pages/pc/ckbox/index.tsx | 10 +- Cunkebao/src/pages/pc/ckbox/main.ts | 23 +- Cunkebao/src/store/module/ckchat.data.ts | 44 +- Cunkebao/src/store/module/ckchat.ts | 10 +- Cunkebao/src/utils/db-examples.ts | 508 ++++++++++++++++++ Cunkebao/src/utils/db.ts | 446 +++++++++++++++ 13 files changed, 1051 insertions(+), 82 deletions(-) create mode 100644 Cunkebao/src/utils/db-examples.ts create mode 100644 Cunkebao/src/utils/db.ts diff --git a/Cunkebao/package.json b/Cunkebao/package.json index 0c8101e8..313741a1 100644 --- a/Cunkebao/package.json +++ b/Cunkebao/package.json @@ -10,6 +10,7 @@ "antd-mobile-icons": "^0.3.0", "axios": "^1.6.7", "dayjs": "^1.11.13", + "dexie": "^4.2.0", "echarts": "^5.6.0", "echarts-for-react": "^3.0.2", "react": "^18.2.0", diff --git a/Cunkebao/pnpm-lock.yaml b/Cunkebao/pnpm-lock.yaml index 781b87a9..dda30990 100644 --- a/Cunkebao/pnpm-lock.yaml +++ b/Cunkebao/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + dexie: + specifier: ^4.2.0 + version: 4.2.0 echarts: specifier: ^5.6.0 version: 5.6.0 @@ -1064,6 +1067,9 @@ packages: engines: {node: '>=0.10'} hasBin: true + dexie@4.2.0: + resolution: {integrity: sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -3397,6 +3403,8 @@ snapshots: detect-libc@1.0.3: optional: true + dexie@4.2.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx index 2c52a897..560649df 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx @@ -43,16 +43,15 @@ import { FilePptOutlined, PlayCircleFilled, } from "@ant-design/icons"; -import { ChatRecord, ContractData } from "@/pages/pc/ckbox/data"; +import { ChatRecord, ContractData, GroupData } from "@/pages/pc/ckbox/data"; import { clearUnreadCount, getMessages } from "@/pages/pc/ckbox/api"; import styles from "./ChatWindow.module.scss"; import { useWebSocketStore, WebSocketMessage } from "@/store/module/websocket"; import { formatWechatTime } from "@/utils/common"; const { Header, Content, Footer, Sider } = Layout; const { TextArea } = Input; - interface ChatWindowProps { - contract: ContractData; + contract: ContractData | GroupData; onSendMessage: (message: string) => void; showProfile?: boolean; onToggleProfile?: () => void; @@ -797,6 +796,7 @@ const ChatWindow: React.FC = ({ label: "视频通话", }, { + key: "divider1", type: "divider", }, { @@ -808,6 +808,7 @@ const ChatWindow: React.FC = ({ label: "消息免打扰", }, { + key: "divider2", type: "divider", }, { diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx index 581a45e9..ab399177 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -1,13 +1,12 @@ import React from "react"; import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; -import dayjs from "dayjs"; import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; import styles from "./MessageList.module.scss"; import { formatWechatTime } from "@/utils/common"; interface MessageListProps { - chatSessions: ContractData[]; - currentChat: ContractData; + chatSessions: ContractData[] | GroupData[]; + currentChat: ContractData | GroupData; onChatSelect: (chat: ContractData | GroupData) => void; } @@ -19,9 +18,10 @@ const MessageList: React.FC = ({ return (
( void; - selectedContactId?: ContractData; + contracts: ContractData[] | GroupData[]; + onContactClick: (contract: ContractData | GroupData) => void; + selectedContactId?: ContractData | GroupData; } const ContactListSimple: React.FC = ({ @@ -174,7 +174,7 @@ const ContactListSimple: React.FC = ({
全部好友
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 7114f7b1..4c3f88ab 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -6,17 +6,17 @@ import { ChromeOutlined, MessageOutlined, } from "@ant-design/icons"; -import { ContractData } from "@/pages/pc/ckbox/data"; +import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; import WechatFriends from "./WechatFriends"; import MessageList from "./MessageList/index"; import styles from "./SidebarMenu.module.scss"; import { getChatSessions } from "@/store/module/ckchat"; interface SidebarMenuProps { - contracts: ContractData[]; - currentChat: ContractData; - onContactClick: (contract: ContractData) => void; - onChatSelect: (chat: ContractData) => void; + contracts: ContractData[] | GroupData[]; + currentChat: ContractData | GroupData; + onContactClick: (contract: ContractData | GroupData) => void; + onChatSelect: (chat: ContractData | GroupData) => void; loading?: boolean; } @@ -152,7 +152,7 @@ const SidebarMenu: React.FC = ({ case "contracts": return ( diff --git a/Cunkebao/src/pages/pc/ckbox/data.ts b/Cunkebao/src/pages/pc/ckbox/data.ts index 63d6fffc..20e79c83 100644 --- a/Cunkebao/src/pages/pc/ckbox/data.ts +++ b/Cunkebao/src/pages/pc/ckbox/data.ts @@ -1,3 +1,45 @@ +//终端用户数据接口 +export interface KfUserListData { + id: number; + tenantId: number; + wechatId: string; + nickname: string; + alias: string; + avatar: string; + gender: number; + region: string; + signature: string; + bindQQ: string; + bindEmail: string; + bindMobile: string; + createTime: string; + currentDeviceId: number; + isDeleted: boolean; + deleteTime: string; + groupId: number; + memo: string; + wechatVersion: string; + labels: string[]; + lastUpdateTime: string; + isOnline?: boolean; + [key: string]: any; +} + +// 账户信息接口 +export interface CkAccount { + id: number; + realName: string; + nickname: string | null; + memo: string | null; + avatar: string; + userName: string; + secret: string; + accountType: number; + departmentId: number; + useGoogleSecretKey: boolean; + hasVerifyGoogleSecret: boolean; +} + //群聊数据接口 export interface GroupData { id?: number; @@ -10,7 +52,9 @@ export interface GroupData { nickname: string; chatroomAvatar: string; groupId: number; - config: any; + config?: { + chat: boolean; + }; unreadCount: number; notice: string; selfDisplyName: string; diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index f8ed2abf..408f1ea5 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import { Layout, Button, Space, message, Tooltip } from "antd"; import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; -import { ContractData } from "./data"; import ChatWindow from "./components/ChatWindow/index"; import SidebarMenu from "./components/SidebarMenu/index"; import VerticalUserList from "./components/VerticalUserList"; @@ -11,13 +10,14 @@ import styles from "./index.module.scss"; import { addChatSession } from "@/store/module/ckchat"; const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; -import { KfUserListData } from "@/store/module/ckchat.data"; +import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); const [contracts, setContacts] = useState([]); - const [currentChat, setCurrentChat] = useState(null); - const [activeVerticalUserId, setActiveVerticalUserId] = useState(0); + const [currentChat, setCurrentChat] = useState( + null, + ); const [loading, setLoading] = useState(false); const [showProfile, setShowProfile] = useState(true); @@ -53,7 +53,7 @@ const CkboxPage: React.FC = () => { }); }, []); - const handleContactClick = (contract: ContractData) => { + const handleContactClick = (contract: ContractData | GroupData) => { addChatSession(contract); setCurrentChat(contract); }; diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index 4669ef63..c06209fd 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -16,7 +16,7 @@ import { } from "./api"; const { sendCommand } = useWebSocketStore.getState(); import { useUserStore } from "@/store/module/user"; -import { KfUserListData } from "@/store/module/ckchat.data"; +import { ContractData, GroupData, KfUserListData } from "@/pages/pc/ckbox/data"; const { login2 } = useUserStore.getState(); //获取触客宝基础信息 export const chatInitAPIdata = async () => { @@ -24,11 +24,6 @@ export const chatInitAPIdata = async () => { //获取联系人列表 const contractList = await getAllContactList(); - //构建联系人列表标签 - const newContractList = await createContractList(contractList); - - // 会话列表分组 - asyncNewContractList(newContractList); //获取联系人列表 asyncContractList(contractList); @@ -46,6 +41,12 @@ export const chatInitAPIdata = async () => { //获取群列表 const groupList = await getAllGroupList(); + //构建联系人列表标签 + const newContractList = await createContractList(contractList, groupList); + console.log("分组信息", newContractList); + + // 会话列表分组 + asyncNewContractList(newContractList); //获取消息会话列表并按lastUpdateTime排序 const filterUserSessions = contractList?.filter( v => v?.config && v.config?.chat, @@ -92,7 +93,10 @@ export const chatInitAPIdata = async () => { }; //构建联系人列表标签 -export const createContractList = async (contractList: any[]) => { +export const createContractList = async ( + contractList: ContractData[], + groupList: GroupData[], +) => { const LablesRes = await Promise.all( [1, 2].map(item => WechatGroup({ @@ -106,7 +110,7 @@ export const createContractList = async (contractList: any[]) => { // 根据countLables中的groupName整理contractList数据 // 返回按标签分组的联系人数组,包括未分组标签(在数组最后) - return organizeContactsByLabels(contractList, countLables); + return organizeContactsByLabels(countLables, contractList, groupList); }; /** @@ -116,8 +120,9 @@ export const createContractList = async (contractList: any[]) => { * @returns 按标签分组的联系人 */ export const organizeContactsByLabels = ( - contractList: any[], countLables: any[], + contractList: ContractData[], + groupList: GroupData[], ) => { // 创建结果对象,用于存储按标签分组的联系人 const result: { [key: string]: any[] } = {}; diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat.data.ts index 9f166da1..d760b8f6 100644 --- a/Cunkebao/src/store/module/ckchat.data.ts +++ b/Cunkebao/src/store/module/ckchat.data.ts @@ -1,46 +1,4 @@ -import { ContractData } from "../../pages/pc/ckbox/data"; - -//终端用户数据接口 -export interface KfUserListData { - id: number; - tenantId: number; - wechatId: string; - nickname: string; - alias: string; - avatar: string; - gender: number; - region: string; - signature: string; - bindQQ: string; - bindEmail: string; - bindMobile: string; - createTime: string; - currentDeviceId: number; - isDeleted: boolean; - deleteTime: string; - groupId: number; - memo: string; - wechatVersion: string; - labels: string[]; - lastUpdateTime: string; - isOnline?: boolean; - [key: string]: any; -} - -// 账户信息接口 -export interface CkAccount { - id: number; - realName: string; - nickname: string | null; - memo: string | null; - avatar: string; - userName: string; - secret: string; - accountType: number; - departmentId: number; - useGoogleSecretKey: boolean; - hasVerifyGoogleSecret: boolean; -} +import { ContractData, KfUserListData, CkAccount } from "@/pages/pc/ckbox/data"; // 权限片段接口 export interface PrivilegeFrag { diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index 9786e803..efe8dc57 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -1,13 +1,11 @@ import { createPersistStore } from "@/store/createPersistStore"; - +import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data"; import { - CkChatState, - CkUserInfo, + ContractData, + GroupData, CkAccount, - CkTenant, KfUserListData, -} from "./ckchat.data"; -import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; +} from "@/pages/pc/ckbox/data"; export const useCkChatStore = createPersistStore( set => ({ diff --git a/Cunkebao/src/utils/db-examples.ts b/Cunkebao/src/utils/db-examples.ts new file mode 100644 index 00000000..daa7d71b --- /dev/null +++ b/Cunkebao/src/utils/db-examples.ts @@ -0,0 +1,508 @@ +// 数据库使用示例 +import { + db, + userService, + messageService, + chatRoomService, + settingService, + userBusinessService, + messageBusinessService, + settingBusinessService, + DatabaseUtils, + type User, + type Message, + type ChatRoom, + type Setting, +} from "./db"; + +// ============= 基础 CRUD 操作示例 ============= + +// 1. 用户操作示例 +export const userExamples = { + // 创建用户 + async createUser() { + const userId = await userService.create({ + name: "张三", + email: "zhangsan@example.com", + status: "active", + }); + console.log("创建用户成功,ID:", userId); + return userId; + }, + + // 批量创建用户 + async createMultipleUsers() { + const userIds = await userService.createMany([ + { name: "李四", email: "lisi@example.com", status: "active" }, + { name: "王五", email: "wangwu@example.com", status: "inactive" }, + { name: "赵六", email: "zhaoliu@example.com", status: "active" }, + ]); + console.log("批量创建用户成功,IDs:", userIds); + return userIds; + }, + + // 查询用户 + async findUsers() { + // 查询所有用户 + const allUsers = await userService.findAll(); + console.log("所有用户:", allUsers); + + // 根据ID查询 + const user = await userService.findById(1); + console.log("ID为1的用户:", user); + + // 条件查询 + const activeUsers = await userService.findWhere("status", "active"); + console.log("活跃用户:", activeUsers); + + return { allUsers, user, activeUsers }; + }, + + // 分页查询 + async paginateUsers() { + const result = await userService.paginate(1, 5); // 第1页,每页5条 + console.log("分页结果:", result); + return result; + }, + + // 更新用户 + async updateUser() { + const count = await userService.update(1, { + name: "张三(已更新)", + status: "inactive", + }); + console.log("更新用户数量:", count); + return count; + }, + + // 删除用户 + async deleteUser() { + await userService.delete(1); + console.log("删除用户成功"); + }, +}; + +// 2. 消息操作示例 +export const messageExamples = { + // 创建消息 + async createMessage() { + const messageId = await messageService.create({ + userId: 1, + content: "这是一条测试消息", + type: "text", + isRead: false, + }); + console.log("创建消息成功,ID:", messageId); + return messageId; + }, + + // 查询未读消息 + async findUnreadMessages() { + const unreadMessages = await messageService.findWhere("isRead", false); + console.log("未读消息:", unreadMessages); + return unreadMessages; + }, + + // 标记消息为已读 + async markMessageAsRead() { + const count = await messageService.update(1, { isRead: true }); + console.log("标记已读消息数量:", count); + return count; + }, + + // 获取最近消息 + async getRecentMessages() { + const recentMessages = await messageService.findAllSorted( + "createdAt", + "desc", + ); + console.log("最近消息:", recentMessages.slice(0, 10)); + return recentMessages.slice(0, 10); + }, +}; + +// 3. 设置操作示例 +export const settingExamples = { + // 保存设置 + async saveSetting() { + const settingId = await settingService.create({ + key: "theme", + value: "dark", + category: "appearance", + }); + console.log("保存设置成功,ID:", settingId); + return settingId; + }, + + // 批量保存设置 + async saveMultipleSettings() { + const settingIds = await settingService.createMany([ + { key: "language", value: "zh-CN", category: "general" }, + { key: "fontSize", value: 14, category: "appearance" }, + { key: "autoSave", value: true, category: "editor" }, + ]); + console.log("批量保存设置成功,IDs:", settingIds); + return settingIds; + }, + + // 查询设置 + async getSettings() { + // 查询所有设置 + const allSettings = await settingService.findAll(); + console.log("所有设置:", allSettings); + + // 按分类查询 + const appearanceSettings = await settingService.findWhere( + "category", + "appearance", + ); + console.log("外观设置:", appearanceSettings); + + return { allSettings, appearanceSettings }; + }, +}; + +// ============= 业务方法示例 ============= + +// 4. 用户业务操作示例 +export const userBusinessExamples = { + // 根据邮箱查找用户 + async findUserByEmail() { + const user = await userBusinessService.findByEmail("zhangsan@example.com"); + console.log("根据邮箱查找的用户:", user); + return user; + }, + + // 查找活跃用户 + async findActiveUsers() { + const activeUsers = await userBusinessService.findActiveUsers(); + console.log("活跃用户列表:", activeUsers); + return activeUsers; + }, + + // 搜索用户 + async searchUsers() { + const users = await userBusinessService.searchByName("张"); + console.log("搜索结果:", users); + return users; + }, + + // 更新用户状态 + async updateUserStatus() { + const count = await userBusinessService.updateStatus(1, "active"); + console.log("更新用户状态数量:", count); + return count; + }, +}; + +// 5. 消息业务操作示例 +export const messageBusinessExamples = { + // 查找用户消息 + async findUserMessages() { + const messages = await messageBusinessService.findByUserId(1); + console.log("用户消息:", messages); + return messages; + }, + + // 查找未读消息 + async findUnreadMessages() { + const unreadMessages = await messageBusinessService.findUnreadMessages(); + console.log("未读消息:", unreadMessages); + return unreadMessages; + }, + + // 标记消息为已读 + async markAsRead() { + const count = await messageBusinessService.markAsRead(1); + console.log("标记已读数量:", count); + return count; + }, + + // 标记用户所有消息为已读 + async markAllAsRead() { + const count = await messageBusinessService.markAllAsRead(1); + console.log("标记用户所有消息已读数量:", count); + return count; + }, + + // 获取最近消息 + async getRecentMessages() { + const messages = await messageBusinessService.getRecentMessages(20); + console.log("最近20条消息:", messages); + return messages; + }, +}; + +// 6. 设置业务操作示例 +export const settingBusinessExamples = { + // 获取设置值 + async getSetting() { + const theme = await settingBusinessService.getSetting("theme"); + console.log("主题设置:", theme); + return theme; + }, + + // 设置值 + async setSetting() { + const settingId = await settingBusinessService.setSetting( + "theme", + "light", + "appearance", + ); + console.log("设置主题成功,ID:", settingId); + return settingId; + }, + + // 获取分类设置 + async getSettingsByCategory() { + const settings = + await settingBusinessService.getSettingsByCategory("appearance"); + console.log("外观设置:", settings); + return settings; + }, + + // 删除设置 + async deleteSetting() { + await settingBusinessService.deleteSetting("oldSetting"); + console.log("删除设置成功"); + }, +}; + +// ============= 数据库工具示例 ============= + +// 7. 数据库工具操作示例 +export const databaseUtilsExamples = { + // 获取数据库统计信息 + async getStats() { + const stats = await DatabaseUtils.getStats(); + console.log("数据库统计:", stats); + return stats; + }, + + // 健康检查 + async healthCheck() { + const health = await DatabaseUtils.healthCheck(); + console.log("数据库健康状态:", health); + return health; + }, + + // 导出数据 + async exportData() { + const jsonData = await DatabaseUtils.exportData(); + console.log("导出的数据长度:", jsonData.length); + return jsonData; + }, + + // 备份到文件 + async backupToFile() { + await DatabaseUtils.backupToFile(); + console.log("备份文件下载成功"); + }, + + // 从文件恢复(需要文件输入) + async restoreFromFile(file: File) { + try { + await DatabaseUtils.restoreFromFile(file); + console.log("数据恢复成功"); + } catch (error) { + console.error("数据恢复失败:", error); + } + }, + + // 清空所有数据 + async clearAllData() { + const confirmed = confirm("确定要清空所有数据吗?此操作不可恢复!"); + if (confirmed) { + await DatabaseUtils.clearAllData(); + console.log("所有数据已清空"); + } + }, +}; + +// ============= 高级查询示例 ============= + +// 8. 高级查询示例 +export const advancedQueryExamples = { + // 复杂条件查询 + async complexQuery() { + // 查询活跃用户的未读消息 + const activeUsers = await db.users + .where("status") + .equals("active") + .toArray(); + const activeUserIds = activeUsers.map(user => user.id!); + const unreadMessages = await db.messages + .where("userId") + .anyOf(activeUserIds) + .and(msg => !msg.isRead) + .toArray(); + + console.log("活跃用户的未读消息:", unreadMessages); + return unreadMessages; + }, + + // 统计查询 + async statisticsQuery() { + const stats = { + totalUsers: await db.users.count(), + activeUsers: await db.users.where("status").equals("active").count(), + totalMessages: await db.messages.count(), + unreadMessages: await db.messages.where("isRead").equals(false).count(), + messagesThisWeek: await db.messages + .where("createdAt") + .above(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)) + .count(), + }; + + console.log("统计信息:", stats); + return stats; + }, + + // 事务操作 + async transactionExample() { + try { + await db.transaction("rw", [db.users, db.messages], async () => { + // 创建用户 + const userId = await db.users.add({ + name: "事务用户", + email: "transaction@example.com", + status: "active", + }); + + // 为该用户创建欢迎消息 + await db.messages.add({ + userId, + content: "欢迎使用我们的应用!", + type: "text", + isRead: false, + }); + + console.log("事务执行成功"); + }); + } catch (error) { + console.error("事务执行失败:", error); + } + }, +}; + +// ============= 完整使用流程示例 ============= + +// 9. 完整的应用场景示例 +export const fullScenarioExample = { + // 模拟用户注册和使用流程 + async simulateUserFlow() { + console.log("=== 开始模拟用户流程 ==="); + + try { + // 1. 用户注册 + const userId = await userBusinessService.create({ + name: "新用户", + email: "newuser@example.com", + status: "active", + }); + console.log("1. 用户注册成功,ID:", userId); + + // 2. 设置用户偏好 + await settingBusinessService.setSetting( + "theme", + "dark", + "user-" + userId, + ); + await settingBusinessService.setSetting( + "language", + "zh-CN", + "user-" + userId, + ); + console.log("2. 用户偏好设置完成"); + + // 3. 发送欢迎消息 + const messageId = await messageBusinessService.create({ + userId, + content: "欢迎加入我们的平台!", + type: "text", + isRead: false, + }); + console.log("3. 欢迎消息发送成功,ID:", messageId); + + // 4. 用户查看消息 + const userMessages = await messageBusinessService.findByUserId(userId); + console.log("4. 用户消息列表:", userMessages); + + // 5. 标记消息为已读 + await messageBusinessService.markAsRead(messageId); + console.log("5. 消息已标记为已读"); + + // 6. 获取用户统计信息 + const userStats = { + totalMessages: await messageService.countWhere("userId", userId), + unreadMessages: await db.messages + .where("userId") + .equals(userId) + .and(msg => !msg.isRead) + .count(), + }; + console.log("6. 用户统计信息:", userStats); + + console.log("=== 用户流程模拟完成 ==="); + return { userId, messageId, userStats }; + } catch (error) { + console.error("用户流程模拟失败:", error); + throw error; + } + }, +}; + +// 导出所有示例 +export const allExamples = { + userExamples, + messageExamples, + settingExamples, + userBusinessExamples, + messageBusinessExamples, + settingBusinessExamples, + databaseUtilsExamples, + advancedQueryExamples, + fullScenarioExample, +}; + +// 快速测试函数 +export async function quickTest() { + console.log("=== 开始快速测试 ==="); + + try { + // 健康检查 + const health = await DatabaseUtils.healthCheck(); + console.log("数据库健康状态:", health); + + // 创建测试数据 + const userId = await userBusinessService.create({ + name: "测试用户", + email: "test@example.com", + status: "active", + }); + + const messageId = await messageBusinessService.create({ + userId, + content: "测试消息", + type: "text", + isRead: false, + }); + + // 查询测试 + const user = await userBusinessService.findById(userId); + const message = await messageBusinessService.findById(messageId); + + console.log("创建的用户:", user); + console.log("创建的消息:", message); + + // 统计信息 + const stats = await DatabaseUtils.getStats(); + console.log("数据库统计:", stats); + + console.log("=== 快速测试完成 ==="); + return { userId, messageId, stats }; + } catch (error) { + console.error("快速测试失败:", error); + throw error; + } +} diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts new file mode 100644 index 00000000..6f6fc9ff --- /dev/null +++ b/Cunkebao/src/utils/db.ts @@ -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; + messages!: Table; + chatRooms!: Table; + settings!: Table; + + 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 { + constructor(private table: Table) {} + + // 基础 CRUD 操作 + async create( + data: Omit, + ): Promise { + return await this.table.add(data as T); + } + + async createMany( + dataList: Omit[], + ): Promise { + return await this.table.bulkAdd(dataList as T[], { allKeys: true }); + } + + async findById(id: number): Promise { + return await this.table.get(id); + } + + async findAll(): Promise { + return await this.table.toArray(); + } + + async findByIds(ids: number[]): Promise { + return await this.table.where("id").anyOf(ids).toArray(); + } + + async update( + id: number, + data: Partial>, + ): Promise { + return await this.table.update(id, data); + } + + async updateMany( + updates: { id: number; data: Partial> }[], + ): Promise { + return await this.table.bulkUpdate( + updates.map(u => ({ key: u.id, changes: u.data })), + ); + } + + async delete(id: number): Promise { + await this.table.delete(id); + } + + async deleteMany(ids: number[]): Promise { + await this.table.bulkDelete(ids); + } + + async deleteAll(): Promise { + 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 { + return await this.table + .where(field as string) + .equals(value) + .toArray(); + } + + async findWhereIn(field: keyof T, values: any[]): Promise { + return await this.table + .where(field as string) + .anyOf(values) + .toArray(); + } + + async findWhereNot(field: keyof T, value: any): Promise { + return await this.table + .where(field as string) + .notEqual(value) + .toArray(); + } + + // 排序查询 + async findAllSorted( + field: keyof T, + direction: "asc" | "desc" = "asc", + ): Promise { + 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 { + return await this.table + .where(field as string) + .startsWithIgnoreCase(keyword) + .toArray(); + } + + // 统计功能 + async count(): Promise { + return await this.table.count(); + } + + async countWhere(field: keyof T, value: any): Promise { + return await this.table + .where(field as string) + .equals(value) + .count(); + } + + // 存在性检查 + async exists(id: number): Promise { + const item = await this.table.get(id); + return !!item; + } + + async existsWhere(field: keyof T, value: any): Promise { + 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 { + constructor() { + super(db.users); + } + + async findByEmail(email: string): Promise { + return await db.users.where("email").equals(email).first(); + } + + async findActiveUsers(): Promise { + return await db.users.where("status").equals("active").toArray(); + } + + async searchByName(name: string): Promise { + return await db.users.where("name").startsWithIgnoreCase(name).toArray(); + } + + async updateStatus( + id: number, + status: "active" | "inactive", + ): Promise { + return await this.update(id, { status }); + } +} + +export class MessageService extends DatabaseService { + constructor() { + super(db.messages); + } + + async findByUserId(userId: number): Promise { + return await db.messages.where("userId").equals(userId).toArray(); + } + + async findUnreadMessages(): Promise { + return await db.messages.where("isRead").equals(false).toArray(); + } + + async markAsRead(id: number): Promise { + return await this.update(id, { isRead: true }); + } + + async markAllAsRead(userId: number): Promise { + 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 { + return await db.messages + .orderBy("createdAt") + .reverse() + .limit(limit) + .toArray(); + } +} + +export class SettingService extends DatabaseService { + constructor() { + super(db.settings); + } + + async getSetting(key: string): Promise { + const setting = await db.settings.where("key").equals(key).first(); + return setting?.value; + } + + async setSetting( + key: string, + value: any, + category: string = "general", + ): Promise { + 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 { + return await db.settings.where("category").equals(category).toArray(); + } + + async deleteSetting(key: string): Promise { + await db.settings.where("key").equals(key).delete(); + } +} + +// 数据库工具类 +export class DatabaseUtils { + // 数据导出 + static async exportData(): Promise { + 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 { + 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 { + 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 { + 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 { + 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;