{/* 渲染所有可用标签,选中的排在前面 */}
- {[...new Set([...selectedTags, ...allAvailableTags])].map(
- (tag, index) => {
- const isSelected = selectedTags.includes(tag);
- return (
-
handleTagToggle(tag)}
- >
- {tag}
-
- );
- },
- )}
+ {[...new Set([...selectedTags, ...allAvailableTags])].map(tag => {
+ const isSelected = selectedTags.includes(tag);
+ return (
+
handleTagToggle(tag)}
+ >
+ {tag}
+
+ );
+ })}
{/* 新增标签区域 */}
{isAddingTag ? (
@@ -933,29 +1141,7 @@ const Person: React.FC
= ({ contract }) => {
>
}
- onClick={async () => {
- try {
- const contractData = getContactsByCustomer(
- contract.wechatAccountId,
- );
- // 转换 Contact[] 为 FriendSelectionItem[]
- const friendSelectionData = (contractData || []).map(
- item => ({
- id: item.id || item.serverId,
- wechatId: item.wechatId,
- nickname: item.nickname,
- avatar: item.avatar || "",
- conRemark: item.conRemark,
- name: item.conRemark || item.nickname, // 用于搜索显示
- }),
- );
- setContractList(friendSelectionData);
- setIsFriendSelectionVisible(true);
- } catch (error) {
- console.error("获取联系人列表失败:", error);
- messageApi.error("获取联系人列表失败");
- }
- }}
+ onClick={addMember}
type="primary"
style={{
flex: 1,
@@ -1044,7 +1230,7 @@ const Person: React.FC = ({ contract }) => {
className={styles.groupMemberList}
style={{ maxHeight: "400px", overflowY: "auto" }}
>
- {currentGroupMembers.map((member, index) => (
+ {currentGroupMembers.map(member => (
= ({ contract }) => {
{/* 添加成员弹窗 */}
setIsFriendSelectionVisible(false)}
+ onCancel={() => {
+ setIsFriendSelectionVisible(false);
+ setCurrentContactPage(1); // 重置页码
+ }}
onConfirm={(selectedIds, selectedItems) => {
- setSelectedFriends(selectedItems);
handleAddMember(
selectedIds.map(id => parseInt(id)),
selectedItems,
);
+ setCurrentContactPage(1); // 重置页码
}}
dataSource={contractList}
title="添加群成员"
+ onLoadMore={loadMoreContacts}
+ hasMore={true} // 强制设置为true,确保显示加载更多按钮
+ loading={isLoadingContacts}
/>
{/* 删除成员弹窗 */}
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx
index 420d942bb..0754db881 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/components/GroupModal.tsx
@@ -28,7 +28,7 @@ const GroupModal: React.FC = ({
form.resetFields();
}}
footer={null}
- destroyOnClose
+ destroyOnHidden
>
);
+ const tabContentCacheRef = useRef>({});
+
+ const getTabContent = (tabKey: string) => {
+ if (!tabContentCacheRef.current[tabKey]) {
+ switch (tabKey) {
+ case "chats":
+ tabContentCacheRef.current[tabKey] = ;
+ break;
+ case "contracts":
+ tabContentCacheRef.current[tabKey] = ;
+ break;
+ case "friendsCicle":
+ tabContentCacheRef.current[tabKey] = ;
+ break;
+ default:
+ tabContentCacheRef.current[tabKey] = null;
+ }
+ }
+ return tabContentCacheRef.current[tabKey];
+ };
+
// 渲染内容部分
const renderContent = () => {
// 如果正在切换tab到聊天,显示骨架屏
@@ -200,16 +221,27 @@ const SidebarMenu: React.FC = ({ loading = false }) => {
return renderSkeleton();
}
- switch (activeTab) {
- case "chats":
- return ;
- case "contracts":
- return ;
- case "friendsCicle":
- return ;
- default:
- return null;
+ const availableTabs = ["chats", "contracts"];
+ if (currentCustomer && currentCustomer.id !== 0) {
+ availableTabs.push("friendsCicle");
}
+
+ return (
+ <>
+ {availableTabs.map(tabKey => (
+
+ {getTabContent(tabKey)}
+
+ ))}
+ >
+ );
};
if (loading) {
diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/data.ts b/Touchkebao/src/pages/pc/ckbox/weChat/data.ts
index 7b0a7acaf..d69fe801b 100644
--- a/Touchkebao/src/pages/pc/ckbox/weChat/data.ts
+++ b/Touchkebao/src/pages/pc/ckbox/weChat/data.ts
@@ -144,7 +144,7 @@ export interface ContractData {
labels: string[];
signature: string;
accountId: number;
- extendFields: null;
+ extendFields?: Record | null;
city?: string;
lastUpdateTime: string;
isPassed: boolean;
diff --git a/Touchkebao/src/store/module/user.ts b/Touchkebao/src/store/module/user.ts
index 745af11c5..7d1dea2fe 100644
--- a/Touchkebao/src/store/module/user.ts
+++ b/Touchkebao/src/store/module/user.ts
@@ -1,5 +1,52 @@
import { createPersistStore } from "@/store/createPersistStore";
import { Toast } from "antd-mobile";
+import { databaseManager } from "@/utils/db";
+
+const STORE_CACHE_KEYS = [
+ "user-store",
+ "app-store",
+ "settings-store",
+ "websocket-store",
+ "ckchat-store",
+ "wechat-storage",
+ "contacts-storage",
+ "message-storage",
+ "customer-storage",
+];
+
+const allStorages = (): Storage[] => {
+ if (typeof window === "undefined") {
+ return [];
+ }
+ const storages: Storage[] = [];
+ try {
+ storages.push(window.localStorage);
+ } catch (error) {
+ console.warn("无法访问 localStorage:", error);
+ }
+ try {
+ storages.push(window.sessionStorage);
+ } catch (error) {
+ console.warn("无法访问 sessionStorage:", error);
+ }
+ return storages;
+};
+
+const clearStoreCaches = () => {
+ const storages = allStorages();
+ if (!storages.length) {
+ return;
+ }
+ STORE_CACHE_KEYS.forEach(key => {
+ storages.forEach(storage => {
+ try {
+ storage.removeItem(key);
+ } catch (error) {
+ console.warn(`清理持久化数据失败: ${key}`, error);
+ }
+ });
+ });
+};
export interface User {
id: number;
@@ -28,7 +75,7 @@ interface UserState {
setToken: (token: string) => void;
setToken2: (token2: string) => void;
clearUser: () => void;
- login: (token: string, userInfo: User) => void;
+ login: (token: string, userInfo: User) => Promise;
login2: (token2: string) => void;
logout: () => void;
}
@@ -39,12 +86,27 @@ export const useUserStore = createPersistStore(
token: null,
token2: null,
isLoggedIn: false,
- setUser: user => set({ user, isLoggedIn: true }),
+ setUser: user => {
+ set({ user, isLoggedIn: true });
+ databaseManager.ensureDatabase(user.id).catch(error => {
+ console.warn("Failed to initialize database for user:", error);
+ });
+ },
setToken: token => set({ token }),
setToken2: token2 => set({ token2 }),
- clearUser: () =>
- set({ user: null, token: null, token2: null, isLoggedIn: false }),
- login: (token, userInfo) => {
+ clearUser: () => {
+ databaseManager.closeCurrentDatabase().catch(error => {
+ console.warn("Failed to close database on clearUser:", error);
+ });
+ clearStoreCaches();
+ set({ user: null, token: null, token2: null, isLoggedIn: false });
+ },
+ login: async (token, userInfo) => {
+ clearStoreCaches();
+
+ // 清除旧的双token缓存
+ localStorage.removeItem("token2");
+
// 只将token存储到localStorage
localStorage.setItem("token", token);
@@ -66,6 +128,11 @@ export const useUserStore = createPersistStore(
lastLoginIp: userInfo.lastLoginIp,
lastLoginTime: userInfo.lastLoginTime,
};
+ try {
+ await databaseManager.ensureDatabase(user.id);
+ } catch (error) {
+ console.error("Failed to initialize user database:", error);
+ }
set({ user, token, isLoggedIn: true });
Toast.show({ content: "登录成功", position: "top" });
@@ -80,6 +147,10 @@ export const useUserStore = createPersistStore(
// 清除localStorage中的token
localStorage.removeItem("token");
localStorage.removeItem("token2");
+ databaseManager.closeCurrentDatabase().catch(error => {
+ console.warn("Failed to close user database on logout:", error);
+ });
+ clearStoreCaches();
set({ user: null, token: null, token2: null, isLoggedIn: false });
},
}),
@@ -92,7 +163,11 @@ export const useUserStore = createPersistStore(
isLoggedIn: state.isLoggedIn,
}),
onRehydrateStorage: () => state => {
- // console.log("User store hydrated:", state);
+ if (state?.user?.id) {
+ databaseManager.ensureDatabase(state.user!.id).catch(error => {
+ console.warn("Failed to restore user database:", error);
+ });
+ }
},
},
);
diff --git a/Touchkebao/src/store/module/weChat/contacts.data.ts b/Touchkebao/src/store/module/weChat/contacts.data.ts
index 0ef039e10..c0769fd90 100644
--- a/Touchkebao/src/store/module/weChat/contacts.data.ts
+++ b/Touchkebao/src/store/module/weChat/contacts.data.ts
@@ -50,7 +50,7 @@ export interface ContractData {
labels: string[];
signature: string;
accountId: number;
- extendFields: null;
+ extendFields?: Record | null;
city?: string;
lastUpdateTime: string;
isPassed: boolean;
diff --git a/Touchkebao/src/store/module/weChat/contacts.ts b/Touchkebao/src/store/module/weChat/contacts.ts
index ca875b974..ee8827f2b 100644
--- a/Touchkebao/src/store/module/weChat/contacts.ts
+++ b/Touchkebao/src/store/module/weChat/contacts.ts
@@ -3,6 +3,10 @@ import { persist } from "zustand/middleware";
import { ContactGroupByLabel } from "@/pages/pc/ckbox/data";
import { Contact } from "@/utils/db";
import { ContactManager } from "@/utils/dbAction";
+import { useUserStore } from "@/store/module/user";
+
+const SEARCH_DEBOUNCE_DELAY = 300;
+let searchDebounceTimer: ReturnType | null = null;
/**
* 联系人状态管理接口
@@ -171,8 +175,16 @@ export const useContactStore = create()(
setSearchKeyword: (keyword: string) => {
set({ searchKeyword: keyword });
+
+ if (searchDebounceTimer) {
+ clearTimeout(searchDebounceTimer);
+ searchDebounceTimer = null;
+ }
+
if (keyword.trim()) {
- get().searchContacts(keyword);
+ searchDebounceTimer = setTimeout(() => {
+ get().searchContacts(keyword);
+ }, SEARCH_DEBOUNCE_DELAY);
} else {
set({ isSearchMode: false, searchResults: [] });
}
@@ -204,8 +216,15 @@ export const useContactStore = create()(
set({ loading: true, isSearchMode: true });
try {
+ const currentUserId = useUserStore.getState().user?.id;
+
+ if (!currentUserId) {
+ set({ searchResults: [], isSearchMode: false, loading: false });
+ return;
+ }
+
const results = await ContactManager.searchContacts(
- get().currentContact?.userId || 0,
+ currentUserId,
keyword,
);
set({ searchResults: results });
diff --git a/Touchkebao/src/store/module/weChat/message.data.ts b/Touchkebao/src/store/module/weChat/message.data.ts
index 0925bbfd4..e59e75061 100644
--- a/Touchkebao/src/store/module/weChat/message.data.ts
+++ b/Touchkebao/src/store/module/weChat/message.data.ts
@@ -1,3 +1,5 @@
+import { ChatSession } from "@/utils/db";
+
export interface Message {
id: number;
wechatId: string;
@@ -26,13 +28,15 @@ export interface Message {
}
//Store State - 会话列表状态管理(不存储数据,只管理状态)
+export type SessionsUpdater =
+ | ChatSession[]
+ | ((previous: ChatSession[]) => ChatSession[]);
+
export interface MessageState {
//加载状态
loading: boolean;
//后台同步状态
refreshing: boolean;
- //刷新触发器(用于通知组件重新查询数据库)
- refreshTrigger: number;
//最后刷新时间
lastRefreshTime: string | null;
//是否已经加载过一次(避免重复请求)
@@ -42,8 +46,6 @@ export interface MessageState {
setLoading: (loading: boolean) => void;
//设置同步状态
setRefreshing: (refreshing: boolean) => void;
- //触发刷新(通知组件重新查询)
- triggerRefresh: () => void;
//设置已加载标识
setHasLoadedOnce: (loaded: boolean) => void;
//重置加载状态(用于登出或切换用户)
@@ -60,4 +62,16 @@ export interface MessageState {
updateMessageStatus: (messageId: number, status: string) => void;
//更新当前选中的消息(废弃,保留兼容)
updateCurrentMessage: (message: Message) => void;
+
+ // ==================== 新的会话数据接口 ====================
+ // 当前会话列表
+ sessions: ChatSession[];
+ // 设置或更新会话列表(支持回调写法)
+ setSessions: (updater: SessionsUpdater) => void;
+ // 新增或替换某个会话
+ upsertSession: (session: ChatSession) => void;
+ // 按 ID 和类型移除会话
+ removeSessionById: (sessionId: number, type: ChatSession["type"]) => void;
+ // 清空所有会话(登出/切账号使用)
+ clearSessions: () => void;
}
diff --git a/Touchkebao/src/store/module/weChat/message.ts b/Touchkebao/src/store/module/weChat/message.ts
index 51503f193..c1b2d12c4 100644
--- a/Touchkebao/src/store/module/weChat/message.ts
+++ b/Touchkebao/src/store/module/weChat/message.ts
@@ -1,6 +1,43 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
-import { Message, MessageState } from "./message.data";
+import { ChatSession } from "@/utils/db";
+import { Message, MessageState, SessionsUpdater } from "./message.data";
+
+const computeSortKey = (session: ChatSession) => {
+ const isTop = session.config?.top ? 1 : 0;
+ const timestamp = new Date(session.lastUpdateTime || new Date()).getTime();
+ const displayName = (
+ session.conRemark ||
+ session.nickname ||
+ (session as any).wechatId ||
+ ""
+ ).toLowerCase();
+
+ return `${isTop}|${timestamp}|${displayName}`;
+};
+
+const normalizeSessions = (sessions: ChatSession[]) => {
+ if (!Array.isArray(sessions)) {
+ return [];
+ }
+
+ return [...sessions]
+ .map(session => ({
+ ...session,
+ sortKey: computeSortKey(session),
+ }))
+ .sort((a, b) => b.sortKey.localeCompare(a.sortKey));
+};
+
+const resolveUpdater = (
+ updater: SessionsUpdater,
+ previous: ChatSession[],
+): ChatSession[] => {
+ if (typeof updater === "function") {
+ return updater(previous);
+ }
+ return updater;
+};
/**
* 会话列表状态管理Store
@@ -13,24 +50,18 @@ export const useMessageStore = create()(
// ==================== 新增状态管理 ====================
loading: false,
refreshing: false,
- refreshTrigger: 0,
lastRefreshTime: null,
hasLoadedOnce: false,
setLoading: (loading: boolean) => set({ loading }),
setRefreshing: (refreshing: boolean) => set({ refreshing }),
- triggerRefresh: () =>
- set({
- refreshTrigger: get().refreshTrigger + 1,
- lastRefreshTime: new Date().toISOString(),
- }),
setHasLoadedOnce: (loaded: boolean) => set({ hasLoadedOnce: loaded }),
resetLoadState: () =>
set({
hasLoadedOnce: false,
loading: false,
refreshing: false,
- refreshTrigger: 0,
+ sessions: [],
}),
// ==================== 保留原有接口(向后兼容) ====================
@@ -45,6 +76,45 @@ export const useMessageStore = create()(
message.id === messageId ? { ...message, status } : message,
),
}),
+
+ // ==================== 会话数据接口 ====================
+ sessions: [],
+ setSessions: (updater: SessionsUpdater) =>
+ set(state => ({
+ sessions: normalizeSessions(resolveUpdater(updater, state.sessions)),
+ lastRefreshTime: new Date().toISOString(),
+ })),
+ upsertSession: (session: ChatSession) =>
+ set(state => {
+ const next = [...state.sessions];
+ const index = next.findIndex(
+ s => s.id === session.id && s.type === session.type,
+ );
+
+ if (index > -1) {
+ next[index] = session;
+ } else {
+ next.push(session);
+ }
+ return {
+ sessions: normalizeSessions(next),
+ lastRefreshTime: new Date().toISOString(),
+ };
+ }),
+ removeSessionById: (sessionId: number, type: ChatSession["type"]) =>
+ set(state => ({
+ sessions: normalizeSessions(
+ state.sessions.filter(
+ s => !(s.id === sessionId && s.type === type),
+ ),
+ ),
+ lastRefreshTime: new Date().toISOString(),
+ })),
+ clearSessions: () =>
+ set({
+ sessions: [],
+ lastRefreshTime: new Date().toISOString(),
+ }),
}),
{
name: "message-storage",
@@ -105,11 +175,6 @@ export const setLoading = (loading: boolean) =>
export const setRefreshing = (refreshing: boolean) =>
useMessageStore.getState().setRefreshing(refreshing);
-/**
- * 触发刷新(通知组件重新查询数据库)
- */
-export const triggerRefresh = () => useMessageStore.getState().triggerRefresh();
-
/**
* 设置已加载标识
* @param loaded 是否已加载
diff --git a/Touchkebao/src/store/module/weChat/weChat.data.ts b/Touchkebao/src/store/module/weChat/weChat.data.ts
index d23dfdf86..dc9aa163e 100644
--- a/Touchkebao/src/store/module/weChat/weChat.data.ts
+++ b/Touchkebao/src/store/module/weChat/weChat.data.ts
@@ -40,6 +40,12 @@ export interface WeChatState {
// ==================== 聊天消息管理 ====================
/** 当前聊天的消息列表 */
currentMessages: ChatRecord[];
+ /** 当前聊天记录分页页码 */
+ currentMessagesPage: number;
+ /** 单页消息条数 */
+ currentMessagesPageSize: number;
+ /** 是否还有更多历史消息 */
+ currentMessagesHasMore: boolean;
/** 添加新消息 */
addMessage: (message: ChatRecord) => void;
/** 更新指定消息 */
@@ -83,7 +89,7 @@ export interface WeChatState {
// ==================== 消息加载方法 ====================
/** 加载聊天消息 */
- loadChatMessages: (Init: boolean, To?: number) => Promise;
+ loadChatMessages: (Init: boolean, pageOverride?: number) => Promise;
/** 搜索消息 */
SearchMessage: (params: {
From: number;
diff --git a/Touchkebao/src/store/module/weChat/weChat.ts b/Touchkebao/src/store/module/weChat/weChat.ts
index f161bac04..89a001a95 100644
--- a/Touchkebao/src/store/module/weChat/weChat.ts
+++ b/Touchkebao/src/store/module/weChat/weChat.ts
@@ -28,6 +28,7 @@ let pendingMessages: ChatRecord[] = []; // 待处理的消息队列
let currentAiGenerationId: string | null = null; // 当前AI生成的唯一ID
const AI_REQUEST_DELAY = 3000; // 3秒延迟
const FILE_MESSAGE_TYPE = "file";
+const DEFAULT_MESSAGE_PAGE_SIZE = 20;
type FileMessagePayload = {
type?: string;
@@ -120,6 +121,108 @@ const isFileLikeMessage = (msg: ChatRecord): boolean => {
return false;
};
+const normalizeMessages = (source: any): ChatRecord[] => {
+ if (Array.isArray(source)) {
+ return source;
+ }
+ if (Array.isArray(source?.list)) {
+ return source.list;
+ }
+ return [];
+};
+
+const parseTimeValue = (value: unknown): number => {
+ if (value === null || value === undefined) {
+ return 0;
+ }
+ if (typeof value === "number") {
+ return value;
+ }
+ if (typeof value === "string") {
+ const numeric = Number(value);
+ if (!Number.isNaN(numeric)) {
+ return numeric;
+ }
+ const parsed = Date.parse(value);
+ if (!Number.isNaN(parsed)) {
+ return parsed;
+ }
+ }
+ if (value instanceof Date) {
+ return value.getTime();
+ }
+ return 0;
+};
+
+const getMessageTimestamp = (msg: ChatRecord): number => {
+ const candidates = [
+ (msg as any)?.wechatTime,
+ (msg as any)?.createTime,
+ (msg as any)?.msgTime,
+ (msg as any)?.timestamp,
+ (msg as any)?.time,
+ ];
+
+ for (const candidate of candidates) {
+ const parsed = parseTimeValue(candidate);
+ if (parsed) {
+ return parsed;
+ }
+ }
+
+ return typeof msg.id === "number" ? msg.id : 0;
+};
+
+const sortMessagesByTime = (messages: ChatRecord[]): ChatRecord[] => {
+ return [...messages].sort(
+ (a, b) => getMessageTimestamp(a) - getMessageTimestamp(b),
+ );
+};
+
+const resolvePaginationState = (
+ source: any,
+ requestedPage: number,
+ requestedLimit: number,
+ listLength: number,
+) => {
+ const page =
+ typeof source?.page === "number"
+ ? source.page
+ : typeof source?.current === "number"
+ ? source.current
+ : requestedPage;
+
+ const limit =
+ typeof source?.limit === "number"
+ ? source.limit
+ : typeof source?.pageSize === "number"
+ ? source.pageSize
+ : requestedLimit;
+
+ let hasMore: boolean;
+ if (typeof source?.hasNext === "boolean") {
+ hasMore = source.hasNext;
+ } else if (typeof source?.hasNextPage === "boolean") {
+ hasMore = source.hasNextPage;
+ } else if (typeof source?.pages === "number") {
+ hasMore = page < source.pages;
+ } else if (typeof source?.total === "number" && limit > 0) {
+ hasMore = page * limit < source.total;
+ } else {
+ hasMore = listLength >= limit && listLength > 0;
+ }
+
+ if (listLength === 0) {
+ hasMore = false;
+ }
+
+ return {
+ page,
+ limit: limit || requestedLimit || DEFAULT_MESSAGE_PAGE_SIZE,
+ hasMore,
+ };
+};
+
const normalizeFilePayload = (
payload: FileMessagePayload | null | undefined,
msg: ChatRecord,
@@ -348,6 +451,10 @@ export const useWeChatStore = create()(
currentContract: null,
/** 当前聊天的消息列表 */
currentMessages: [],
+ /** 当前消息分页信息 */
+ currentMessagesPage: 1,
+ currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
+ currentMessagesHasMore: true,
// ==================== 聊天消息管理方法 ====================
/** 添加新消息到当前聊天 */
@@ -429,7 +536,13 @@ export const useWeChatStore = create()(
aiRequestTimer = null;
}
pendingMessages = [];
- set({ currentContract: null, currentMessages: [] });
+ set({
+ currentContract: null,
+ currentMessages: [],
+ currentMessagesPage: 1,
+ currentMessagesHasMore: true,
+ currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
+ });
},
/** 设置当前联系人并加载相关数据 */
setCurrentContact: (contract: ContractData | weChatGroup) => {
@@ -443,7 +556,13 @@ export const useWeChatStore = create()(
const state = useWeChatStore.getState();
// 切换联系人时清空当前消息,等待重新加载
- set({ currentMessages: [], isLoadingAiChat: false });
+ set({
+ currentMessages: [],
+ currentMessagesPage: 1,
+ currentMessagesHasMore: true,
+ currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
+ isLoadingAiChat: false,
+ });
const params: any = {};
@@ -468,62 +587,91 @@ export const useWeChatStore = create()(
id: contract.id,
config: { chat: true },
});
- state.loadChatMessages(true, 4704624000000);
+ state.loadChatMessages(true);
},
// ==================== 消息加载方法 ====================
/** 加载聊天消息 */
- loadChatMessages: async (Init: boolean, To?: number) => {
+ loadChatMessages: async (Init: boolean, pageOverride?: number) => {
const state = useWeChatStore.getState();
const contact = state.currentContract;
- set({ messagesLoading: true });
- set({ isLoadingData: Init });
+
+ if (!contact) {
+ return;
+ }
+
+ if (!Init && !state.currentMessagesHasMore) {
+ return;
+ }
+
+ const nextPage = Init
+ ? 1
+ : (pageOverride ?? state.currentMessagesPage + 1);
+ const limit =
+ state.currentMessagesPageSize || DEFAULT_MESSAGE_PAGE_SIZE;
+
+ if (state.messagesLoading && !Init) {
+ return;
+ }
+
+ set({
+ messagesLoading: true,
+ isLoadingData: Init,
+ });
+
try {
const params: any = {
wechatAccountId: contact.wechatAccountId,
- From: 1,
- To: To || +new Date(),
- Count: 20,
- olderData: true,
+ page: nextPage,
+ limit,
};
- if ("chatroomId" in contact && contact.chatroomId) {
- // 群聊消息加载
+ const isGroup =
+ "chatroomId" in contact && Boolean(contact.chatroomId);
+
+ if (isGroup) {
params.wechatChatroomId = contact.id;
- const messages = await getChatroomMessages(params);
- const currentGroupMembers = await getGroupMembers({
+ } else {
+ params.wechatFriendId = contact.id;
+ }
+
+ const response = isGroup
+ ? await getChatroomMessages(params)
+ : await getChatMessages(params);
+
+ const normalizedMessages = normalizeMessages(response);
+ const sortedMessages = sortMessagesByTime(normalizedMessages);
+ const paginationMeta = resolvePaginationState(
+ response,
+ nextPage,
+ limit,
+ sortedMessages.length,
+ );
+
+ let nextGroupMembers = state.currentGroupMembers;
+ if (Init && isGroup) {
+ nextGroupMembers = await getGroupMembers({
id: contact.id,
});
- if (Init) {
- set({ currentMessages: messages || [], currentGroupMembers });
- } else {
- set({
- currentMessages: [
- ...(messages || []),
- ...state.currentMessages,
- ],
- });
- }
- } else {
- // 私聊消息加载
- params.wechatFriendId = contact.id;
- const messages = await getChatMessages(params);
- if (Init) {
- set({ currentMessages: messages || [] });
- } else {
- set({
- currentMessages: [
- ...(messages || []),
- ...state.currentMessages,
- ],
- });
- }
}
- set({ messagesLoading: false });
+
+ set(current => ({
+ currentMessages: Init
+ ? sortedMessages
+ : [...sortedMessages, ...current.currentMessages],
+ currentGroupMembers:
+ Init && isGroup ? nextGroupMembers : current.currentGroupMembers,
+ currentMessagesPage: paginationMeta.page,
+ currentMessagesPageSize: paginationMeta.limit,
+ currentMessagesHasMore: paginationMeta.hasMore,
+ }));
} catch (error) {
console.error("获取聊天消息失败:", error);
} finally {
- set({ messagesLoading: false });
+ set({
+ messagesLoading: false,
+ isLoadingData: false,
+ });
}
},
@@ -546,11 +694,11 @@ export const useWeChatStore = create()(
try {
const params: any = {
wechatAccountId: contact.wechatAccountId,
+ keyword,
From,
To,
- keyword,
- Count,
- olderData: true,
+ page: 1,
+ limit: Count,
};
if ("chatroomId" in contact && contact.chatroomId) {
@@ -560,12 +708,23 @@ export const useWeChatStore = create()(
const currentGroupMembers = await getGroupMembers({
id: contact.id,
});
- set({ currentMessages: messages || [], currentGroupMembers });
+ set({
+ currentMessages: sortMessagesByTime(normalizeMessages(messages)),
+ currentGroupMembers,
+ currentMessagesPage: 1,
+ currentMessagesHasMore: false,
+ currentMessagesPageSize: Count || state.currentMessagesPageSize,
+ });
} else {
// 私聊消息搜索
params.wechatFriendId = contact.id;
const messages = await getChatMessages(params);
- set({ currentMessages: messages || [] });
+ set({
+ currentMessages: sortMessagesByTime(normalizeMessages(messages)),
+ currentMessagesPage: 1,
+ currentMessagesHasMore: false,
+ currentMessagesPageSize: Count || state.currentMessagesPageSize,
+ });
}
set({ messagesLoading: false });
} catch (error) {
@@ -831,6 +990,9 @@ export const useWeChatStore = create()(
set({
currentContract: null,
currentMessages: [],
+ currentMessagesPage: 1,
+ currentMessagesHasMore: true,
+ currentMessagesPageSize: DEFAULT_MESSAGE_PAGE_SIZE,
messagesLoading: false,
});
},
diff --git a/Touchkebao/src/store/module/websocket/websocket.ts b/Touchkebao/src/store/module/websocket/websocket.ts
index 68a17c459..57ed7f15e 100644
--- a/Touchkebao/src/store/module/websocket/websocket.ts
+++ b/Touchkebao/src/store/module/websocket/websocket.ts
@@ -1,7 +1,7 @@
import { createPersistStore } from "@/store/createPersistStore";
-import { Toast } from "antd-mobile";
import { useUserStore } from "../user";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
+import { useCustomerStore } from "@/store/module/weChat/customer";
const { getAccountId } = useCkChatStore.getState();
import { msgManageCore } from "./msgManage";
// WebSocket消息类型
@@ -52,6 +52,8 @@ interface WebSocketState {
reconnectAttempts: number;
reconnectTimer: NodeJS.Timeout | null;
aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器
+ aliveStatusUnsubscribe: (() => void) | null;
+ aliveStatusLastRequest: number | null;
// 方法
connect: (config: Partial) => void;
@@ -87,6 +89,8 @@ const DEFAULT_CONFIG: WebSocketConfig = {
maxReconnectAttempts: 5,
};
+const ALIVE_STATUS_MIN_INTERVAL = 5 * 1000; // ms
+
export const useWebSocketStore = createPersistStore(
(set, get) => ({
status: WebSocketStatus.DISCONNECTED,
@@ -97,6 +101,8 @@ export const useWebSocketStore = createPersistStore(
reconnectAttempts: 0,
reconnectTimer: null,
aliveStatusTimer: null,
+ aliveStatusUnsubscribe: null,
+ aliveStatusLastRequest: null,
// 连接WebSocket
connect: (config: Partial) => {
@@ -232,11 +238,6 @@ export const useWebSocketStore = createPersistStore(
currentState.status !== WebSocketStatus.CONNECTED ||
!currentState.ws
) {
- // Toast.show({
- // content: "WebSocket未连接,正在重新连接...",
- // position: "top",
- // });
-
// 重置连接状态并发起重新连接
set({ status: WebSocketStatus.DISCONNECTED });
if (currentState.config) {
@@ -392,7 +393,7 @@ export const useWebSocketStore = createPersistStore(
set({
messages: [...currentState.messages, newMessage],
- unreadCount: currentState.config.unreadCount + 1,
+ unreadCount: (currentState.unreadCount ?? 0) + 1,
});
//消息处理器
msgManageCore(data);
@@ -405,7 +406,7 @@ export const useWebSocketStore = createPersistStore(
},
// 内部方法:处理连接关闭
- _handleClose: (event: CloseEvent) => {
+ _handleClose: () => {
const currentState = get();
// console.log("WebSocket连接关闭:", event.code, event.reason);
@@ -431,7 +432,7 @@ export const useWebSocketStore = createPersistStore(
},
// 内部方法:处理连接错误
- _handleError: (event: Event) => {
+ _handleError: () => {
// console.error("WebSocket连接错误:", event);
set({ status: WebSocketStatus.ERROR });
@@ -477,42 +478,97 @@ export const useWebSocketStore = createPersistStore(
// 先停止现有定时器
currentState._stopAliveStatusTimer();
- // 获取客服用户列表
- const { kfUserList } = useCkChatStore.getState();
+ const requestAliveStatus = () => {
+ const state = get();
+ if (state.status !== WebSocketStatus.CONNECTED) {
+ return;
+ }
- // 如果没有客服用户,不启动定时器
- if (!kfUserList || kfUserList.length === 0) {
- return;
- }
+ const now = Date.now();
+ if (
+ state.aliveStatusLastRequest &&
+ now - state.aliveStatusLastRequest < ALIVE_STATUS_MIN_INTERVAL
+ ) {
+ return;
+ }
+
+ const { customerList } = useCustomerStore.getState();
+ const { kfUserList } = useCkChatStore.getState();
+ const targets =
+ customerList && customerList.length > 0
+ ? customerList
+ : kfUserList && kfUserList.length > 0
+ ? kfUserList
+ : [];
+
+ if (targets.length > 0) {
+ state.sendCommand("CmdRequestWechatAccountsAliveStatus", {
+ wechatAccountIds: targets.map(v => v.id),
+ });
+ set({ aliveStatusLastRequest: now });
+ }
+ };
+
+ // 尝试立即请求一次,如果客服列表尚未加载,后续定时器会继续检查
+ requestAliveStatus();
+
+ const unsubscribeCustomer = useCustomerStore.subscribe(state => {
+ if (
+ get().status === WebSocketStatus.CONNECTED &&
+ state.customerList &&
+ state.customerList.length > 0
+ ) {
+ requestAliveStatus();
+ }
+ });
+
+ const unsubscribeKf = useCkChatStore.subscribe(state => {
+ if (
+ get().status === WebSocketStatus.CONNECTED &&
+ state.kfUserList &&
+ state.kfUserList.length > 0
+ ) {
+ requestAliveStatus();
+ }
+ });
// 启动定时器,每5秒查询一次
const timer = setInterval(() => {
const state = get();
// 检查连接状态
if (state.status === WebSocketStatus.CONNECTED) {
- const { kfUserList: currentKfUserList } = useCkChatStore.getState();
- if (currentKfUserList && currentKfUserList.length > 0) {
- state.sendCommand("CmdRequestWechatAccountsAliveStatus", {
- wechatAccountIds: currentKfUserList.map(v => v.id),
- });
- }
+ requestAliveStatus();
} else {
// 如果连接断开,停止定时器
state._stopAliveStatusTimer();
}
}, 5 * 1000);
- set({ aliveStatusTimer: timer });
+ set({
+ aliveStatusTimer: timer,
+ aliveStatusUnsubscribe: () => {
+ unsubscribeCustomer();
+ unsubscribeKf();
+ },
+ });
},
// 内部方法:停止客服状态查询定时器
_stopAliveStatusTimer: () => {
const currentState = get();
+ if (currentState.aliveStatusUnsubscribe) {
+ currentState.aliveStatusUnsubscribe();
+ }
+
if (currentState.aliveStatusTimer) {
clearInterval(currentState.aliveStatusTimer);
- set({ aliveStatusTimer: null });
}
+ set({
+ aliveStatusTimer: null,
+ aliveStatusUnsubscribe: null,
+ aliveStatusLastRequest: null,
+ });
},
}),
{
@@ -524,6 +580,7 @@ export const useWebSocketStore = createPersistStore(
messages: state.messages.slice(-100), // 只保留最近100条消息
unreadCount: state.unreadCount,
reconnectAttempts: state.reconnectAttempts,
+ aliveStatusLastRequest: state.aliveStatusLastRequest,
// 注意:定时器不需要持久化,重新连接时会重新创建
}),
onRehydrateStorage: () => state => {
diff --git a/Touchkebao/src/store/persistUtils.ts b/Touchkebao/src/store/persistUtils.ts
index abfb1e8c5..bcbc93bb5 100644
--- a/Touchkebao/src/store/persistUtils.ts
+++ b/Touchkebao/src/store/persistUtils.ts
@@ -5,6 +5,12 @@ export const PERSIST_KEYS = {
USER_STORE: "user-store",
APP_STORE: "app-store",
SETTINGS_STORE: "settings-store",
+ CKCHAT_STORE: "ckchat-store",
+ WEBSOCKET_STORE: "websocket-store",
+ WECHAT_STORAGE: "wechat-storage",
+ CONTACTS_STORAGE: "contacts-storage",
+ MESSAGE_STORAGE: "message-storage",
+ CUSTOMER_STORAGE: "customer-storage",
} as const;
// 存储类型
diff --git a/Touchkebao/src/utils/db.ts b/Touchkebao/src/utils/db.ts
index abd22d02a..69da02e2c 100644
--- a/Touchkebao/src/utils/db.ts
+++ b/Touchkebao/src/utils/db.ts
@@ -16,6 +16,8 @@
*/
import Dexie, { Table } from "dexie";
+import { getPersistedData, PERSIST_KEYS } from "@/store/persistUtils";
+const DB_NAME_PREFIX = "CunkebaoDatabase";
// ==================== 用户登录记录 ====================
export interface UserLoginRecord {
@@ -58,6 +60,9 @@ export interface ChatSession {
chatroomOwner?: string; // 群主
selfDisplayName?: string; // 群内昵称
notice?: string; // 群公告
+ phone?: string; // 联系人电话
+ region?: string; // 联系人地区
+ extendFields?: string; // 扩展字段(JSON 字符串)
}
// ==================== 统一联系人表(兼容好友和群聊) ====================
@@ -88,6 +93,7 @@ export interface Contact {
signature?: string; // 个性签名
phone?: string; // 手机号
quanPin?: string; // 全拼
+ extendFields?: string; // 扩展字段(JSON 字符串)
// 群聊特有字段(type='group'时有效)
chatroomId?: string; // 群聊ID
@@ -123,18 +129,17 @@ class CunkebaoDatabase extends Dexie {
contactLabelMap!: Table; // 联系人标签映射表
userLoginRecords!: Table; // 用户登录记录表
- constructor() {
- super("CunkebaoDatabase");
+ constructor(dbName: string) {
+ super(dbName);
- // 版本1:统一表结构
this.version(1).stores({
// 会话表索引:支持按用户、类型、时间、置顶等查询
chatSessions:
- "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], sortKey, nickname, conRemark, avatar, content, lastUpdateTime",
+ "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], [userId+aiType], sortKey, nickname, conRemark, avatar, content, lastUpdateTime, aiType, phone, region",
// 联系人表索引:支持按用户、类型、标签、搜索等查询
contactsUnified:
- "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime, groupId",
+ "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+aiType], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime, groupId, aiType, phone, region",
// 联系人标签映射表索引:支持按用户、标签、联系人、类型查询
contactLabelMap:
@@ -145,68 +150,200 @@ class CunkebaoDatabase extends Dexie {
"serverId, userId, lastLoginTime, loginCount, createTime, lastActiveTime",
});
- // 版本2:添加 aiType 字段
this.version(2)
.stores({
- // 会话表索引:添加 aiType 索引
chatSessions:
- "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], [userId+aiType], sortKey, nickname, conRemark, avatar, content, lastUpdateTime, aiType",
-
- // 联系人表索引:添加 aiType 索引
+ "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], [userId+aiType], sortKey, nickname, conRemark, avatar, content, lastUpdateTime, aiType, phone, region, extendFields",
contactsUnified:
- "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+aiType], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime, groupId, aiType",
-
- // 联系人标签映射表索引:保持不变
+ "serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+aiType], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime, groupId, aiType, phone, region, extendFields",
contactLabelMap:
"serverId, userId, labelId, contactId, contactType, [userId+labelId], [userId+contactId], [userId+labelId+sortKey], sortKey, searchKey, avatar, nickname, conRemark, unreadCount, lastUpdateTime",
-
- // 用户登录记录表索引:保持不变
userLoginRecords:
"serverId, userId, lastLoginTime, loginCount, createTime, lastActiveTime",
})
- .upgrade(tx => {
- // 数据迁移:为现有数据添加 aiType 默认值
- return tx
+ .upgrade(async tx => {
+ await tx
.table("chatSessions")
.toCollection()
.modify(session => {
- if (session.aiType === undefined) {
- session.aiType = 0; // 默认为普通类型
+ if (!("extendFields" in session) || session.extendFields == null) {
+ session.extendFields = "{}";
+ } else if (typeof session.extendFields !== "string") {
+ session.extendFields = JSON.stringify(session.extendFields);
+ }
+ });
+
+ await tx
+ .table("contactsUnified")
+ .toCollection()
+ .modify(contact => {
+ if (!("extendFields" in contact) || contact.extendFields == null) {
+ contact.extendFields = "{}";
+ } else if (typeof contact.extendFields !== "string") {
+ contact.extendFields = JSON.stringify(contact.extendFields);
}
- })
- .then(() => {
- return tx
- .table("contactsUnified")
- .toCollection()
- .modify(contact => {
- if (contact.aiType === undefined) {
- contact.aiType = 0; // 默认为普通类型
- }
- });
});
});
}
}
-// 创建数据库实例
-export const db = new CunkebaoDatabase();
+class DatabaseManager {
+ private currentDb: CunkebaoDatabase | null = null;
+ private currentUserId: number | null = null;
+
+ private getDatabaseName(userId: number) {
+ return `${DB_NAME_PREFIX}_${userId}`;
+ }
+
+ private async openDatabase(dbName: string) {
+ const instance = new CunkebaoDatabase(dbName);
+ await instance.open();
+ return instance;
+ }
+
+ async ensureDatabase(userId: number) {
+ if (userId === undefined || userId === null) {
+ throw new Error("Invalid userId provided for database initialization");
+ }
+
+ if (
+ this.currentDb &&
+ this.currentUserId === userId &&
+ this.currentDb.isOpen()
+ ) {
+ return this.currentDb;
+ }
+
+ await this.closeCurrentDatabase();
+
+ const dbName = this.getDatabaseName(userId);
+ this.currentDb = await this.openDatabase(dbName);
+ this.currentUserId = userId;
+
+ return this.currentDb;
+ }
+
+ getCurrentDatabase(): CunkebaoDatabase {
+ if (!this.currentDb) {
+ throw new Error("Database has not been initialized for the current user");
+ }
+ return this.currentDb;
+ }
+
+ getCurrentUserId() {
+ return this.currentUserId;
+ }
+
+ isInitialized(): boolean {
+ return !!this.currentDb && this.currentDb.isOpen();
+ }
+
+ async closeCurrentDatabase() {
+ if (this.currentDb) {
+ try {
+ this.currentDb.close();
+ } catch (error) {
+ console.warn("Failed to close current database:", error);
+ }
+ this.currentDb = null;
+ }
+ this.currentUserId = null;
+ }
+}
+
+export const databaseManager = new DatabaseManager();
+
+let pendingDatabaseRestore: Promise | null = null;
+
+async function restoreDatabaseFromPersistedState() {
+ if (typeof window === "undefined") {
+ return null;
+ }
+
+ const persistedData = getPersistedData>(
+ PERSIST_KEYS.USER_STORE,
+ "localStorage",
+ );
+
+ if (!persistedData) {
+ return null;
+ }
+
+ let parsed: any = persistedData;
+
+ if (typeof persistedData === "string") {
+ try {
+ parsed = JSON.parse(persistedData);
+ } catch (error) {
+ console.warn("Failed to parse persisted user-store value:", error);
+ return null;
+ }
+ }
+
+ const state = parsed?.state ?? parsed;
+ const userId = state?.user?.id;
+
+ if (!userId) {
+ return null;
+ }
+
+ try {
+ return await databaseManager.ensureDatabase(userId);
+ } catch (error) {
+ console.warn("Failed to initialize database from persisted user:", error);
+ return null;
+ }
+}
+
+export async function initializeDatabaseFromPersistedUser() {
+ if (databaseManager.isInitialized()) {
+ return databaseManager.getCurrentDatabase();
+ }
+
+ if (!pendingDatabaseRestore) {
+ pendingDatabaseRestore = restoreDatabaseFromPersistedState().finally(() => {
+ pendingDatabaseRestore = null;
+ });
+ }
+
+ return pendingDatabaseRestore;
+}
+
+const dbProxy = new Proxy({} as CunkebaoDatabase, {
+ get(_target, prop: string | symbol) {
+ const currentDb = databaseManager.getCurrentDatabase();
+ const value = (currentDb as any)[prop];
+ if (typeof value === "function") {
+ return value.bind(currentDb);
+ }
+ return value;
+ },
+});
+
+export const db = dbProxy;
// 简单的数据库操作类
export class DatabaseService {
- constructor(private table: Table) {}
+ constructor(private readonly tableAccessor: () => Table) {}
+
+ private get table(): Table {
+ return this.tableAccessor();
+ }
// 基础 CRUD 操作 - 使用serverId作为主键
async create(data: Omit): Promise {
- return await this.table.add(data as T);
+ return await this.table.add(this.prepareDataForWrite(data) as T);
}
// 创建数据(直接使用接口数据)
// 接口数据的id字段直接作为serverId主键,原id字段保留
async createWithServerId(data: any): Promise {
- const dataToInsert = {
+ const dataToInsert = this.prepareDataForWrite({
...data,
serverId: data.id, // 使用接口的id作为serverId主键
- };
+ phone: data.phone ?? "",
+ region: data.region ?? "",
+ });
return await this.table.add(dataToInsert as T);
}
@@ -225,7 +362,10 @@ export class DatabaseService {
}
async update(serverId: string | number, data: Partial): Promise {
- return await this.table.update(serverId, data as any);
+ return await this.table.update(
+ serverId,
+ this.prepareDataForWrite(data) as any,
+ );
}
async updateMany(
@@ -234,7 +374,7 @@ export class DatabaseService {
return await this.table.bulkUpdate(
dataList.map(item => ({
key: item.serverId,
- changes: item.data as any,
+ changes: this.prepareDataForWrite(item.data) as any,
})),
);
}
@@ -242,7 +382,8 @@ export class DatabaseService {
async createMany(
dataList: Omit[],
): Promise<(string | number)[]> {
- return await this.table.bulkAdd(dataList as T[], { allKeys: true });
+ const processed = dataList.map(item => this.prepareDataForWrite(item));
+ return await this.table.bulkAdd(processed as T[], { allKeys: true });
}
// 批量创建数据(直接使用接口数据)
@@ -266,10 +407,14 @@ export class DatabaseService {
return [];
}
- const processedData = newData.map(item => ({
- ...item,
- serverId: item.id, // 使用接口的id作为serverId主键
- }));
+ const processedData = newData.map(item =>
+ this.prepareDataForWrite({
+ ...item,
+ serverId: item.id, // 使用接口的id作为serverId主键
+ phone: item.phone ?? "",
+ region: item.region ?? "",
+ }),
+ );
return await this.table.bulkAdd(processedData as T[], { allKeys: true });
}
@@ -443,13 +588,42 @@ export class DatabaseService {
.equals(value)
.count();
}
+
+ private prepareDataForWrite(data: any) {
+ if (!data || typeof data !== "object") {
+ return data;
+ }
+
+ const prepared = { ...data };
+
+ if ("extendFields" in prepared) {
+ const value = prepared.extendFields;
+ if (typeof value === "string" && value.trim() !== "") {
+ prepared.extendFields = value;
+ } else if (value && typeof value === "object") {
+ prepared.extendFields = JSON.stringify(value);
+ } else {
+ prepared.extendFields = "{}";
+ }
+ }
+
+ return prepared;
+ }
}
// 创建统一表的服务实例
-export const chatSessionService = new DatabaseService(db.chatSessions);
-export const contactUnifiedService = new DatabaseService(db.contactsUnified);
-export const contactLabelMapService = new DatabaseService(db.contactLabelMap);
-export const userLoginRecordService = new DatabaseService(db.userLoginRecords);
+export const chatSessionService = new DatabaseService(
+ () => databaseManager.getCurrentDatabase().chatSessions,
+);
+export const contactUnifiedService = new DatabaseService(
+ () => databaseManager.getCurrentDatabase().contactsUnified,
+);
+export const contactLabelMapService = new DatabaseService(
+ () => databaseManager.getCurrentDatabase().contactLabelMap,
+);
+export const userLoginRecordService = new DatabaseService(
+ () => databaseManager.getCurrentDatabase().userLoginRecords,
+);
// 默认导出数据库实例
export default db;
diff --git a/Touchkebao/src/utils/dbAction/contact.ts b/Touchkebao/src/utils/dbAction/contact.ts
index 254f2ec8e..abbb7fdb7 100644
--- a/Touchkebao/src/utils/dbAction/contact.ts
+++ b/Touchkebao/src/utils/dbAction/contact.ts
@@ -184,7 +184,10 @@ export class ContactManager {
local.conRemark !== server.conRemark ||
local.avatar !== server.avatar ||
local.wechatAccountId !== server.wechatAccountId ||
- (local.aiType ?? 0) !== (server.aiType ?? 0) // 添加 aiType 比较
+ (local.aiType ?? 0) !== (server.aiType ?? 0) || // 添加 aiType 比较
+ (local.phone ?? "") !== (server.phone ?? "") ||
+ (local.region ?? "") !== (server.region ?? "") ||
+ (local.extendFields ?? "{}") !== (server.extendFields ?? "{}")
);
}
@@ -192,10 +195,12 @@ export class ContactManager {
* 获取联系人分组列表
*/
static async getContactGroups(
- userId: number,
- customerId?: number,
+ _userId: number,
+ _customerId?: number,
): Promise {
try {
+ void _userId;
+ void _customerId;
// 这里应该根据实际的标签系统来实现
// 暂时返回空数组,实际实现需要根据标签表来查询
return [];
diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts
index f6deae3dc..e34041b98 100644
--- a/Touchkebao/src/utils/dbAction/message.ts
+++ b/Touchkebao/src/utils/dbAction/message.ts
@@ -11,8 +11,29 @@ import Dexie from "dexie";
import { db, chatSessionService, ChatSession } from "../db";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
+const serializeExtendFields = (value: any) => {
+ if (typeof value === "string") {
+ return value.trim() ? value : "{}";
+ }
+ if (value && typeof value === "object") {
+ try {
+ return JSON.stringify(value);
+ } catch (error) {
+ console.warn("序列化 extendFields 失败:", error);
+ }
+ }
+ return "{}";
+};
+
+interface SessionUpdatePayload {
+ userId: number;
+ sessions: ChatSession[];
+}
+
export class MessageManager {
- private static updateCallbacks = new Set<(sessions: ChatSession[]) => void>();
+ private static updateCallbacks = new Set<
+ (payload: SessionUpdatePayload) => void
+ >();
// ==================== 回调管理 ====================
@@ -21,9 +42,11 @@ export class MessageManager {
* @param callback 回调函数
* @returns 取消注册的函数
*/
- static onSessionsUpdate(callback: (sessions: ChatSession[]) => void) {
+ static onSessionsUpdate(callback: (payload: SessionUpdatePayload) => void) {
this.updateCallbacks.add(callback);
- return () => this.updateCallbacks.delete(callback);
+ return () => {
+ this.updateCallbacks.delete(callback);
+ };
}
/**
@@ -35,7 +58,7 @@ export class MessageManager {
const sessions = await this.getUserSessions(userId);
this.updateCallbacks.forEach(callback => {
try {
- callback(sessions);
+ callback({ userId, sessions });
} catch (error) {
console.error("会话更新回调执行失败:", error);
}
@@ -93,6 +116,8 @@ export class MessageManager {
content: (friend as any).content || "",
lastUpdateTime: friend.lastUpdateTime || new Date().toISOString(),
aiType: (friend as any).aiType ?? 0, // AI类型,默认为0(普通)
+ phone: (friend as any).phone ?? "",
+ region: (friend as any).region ?? "",
config: {
unreadCount: friend.config?.unreadCount || 0,
top: (friend.config as any)?.top || false,
@@ -101,6 +126,7 @@ export class MessageManager {
wechatFriendId: friend.id,
wechatId: friend.wechatId,
alias: friend.alias,
+ extendFields: serializeExtendFields((friend as any).extendFields),
};
}
@@ -126,6 +152,8 @@ export class MessageManager {
content: (group as any).content || "",
lastUpdateTime: (group as any).lastUpdateTime || new Date().toISOString(),
aiType: (group as any).aiType ?? 0, // AI类型,默认为0(普通)
+ phone: (group as any).phone ?? "",
+ region: (group as any).region ?? "",
config: {
unreadCount: (group.config as any)?.unreadCount || 0,
top: (group.config as any)?.top || false,
@@ -135,6 +163,7 @@ export class MessageManager {
chatroomOwner: group.chatroomOwner,
selfDisplayName: group.selfDisplyName,
notice: group.notice,
+ extendFields: serializeExtendFields((group as any).extendFields),
};
}
@@ -199,6 +228,9 @@ export class MessageManager {
"avatar",
"wechatAccountId", // 添加wechatAccountId比较
"aiType", // 添加aiType比较
+ "phone",
+ "region",
+ "extendFields",
];
for (const field of fieldsToCompare) {