重构数据库管理逻辑,新增旧数据库清理功能,优化数据库初始化流程,更新联系人管理和消息处理逻辑,提升代码可读性和用户体验。

This commit is contained in:
超级老白兔
2025-10-24 16:14:13 +08:00
parent e1d4f678ab
commit 28cbcea4f9
13 changed files with 278 additions and 561 deletions

View File

@@ -8,7 +8,8 @@ import {
} from "@/pages/pc/ckbox/weChat/api";
import { useCurrentContact } from "@/store/module/weChat/weChat";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { contractService, weChatGroupService } from "@/utils/db";
import { ContactManager } from "@/utils/dbAction";
import { useUserStore } from "@/store/module/user";
const { TextArea } = Input;
const { Option } = Select;
@@ -92,11 +93,9 @@ const ToContract: React.FC<ToContractProps> = ({
// 删除聊天会话
deleteChatSession(currentContact.id);
// 删除本地数据库记录
if ("chatroomId" in currentContact) {
await weChatGroupService.delete(currentContact.id);
} else {
await contractService.delete(currentContact.id);
}
const currentUserId = useUserStore.getState().user?.id || 0;
const contactType = "chatroomId" in currentContact ? "group" : "friend";
await ContactManager.deleteContact(currentContact.id);
} catch (deleteError) {
console.error("删除本地数据失败:", deleteError);
}
@@ -132,11 +131,9 @@ const ToContract: React.FC<ToContractProps> = ({
// 删除聊天会话
deleteChatSession(currentContact.id);
// 删除本地数据库记录
if ("chatroomId" in currentContact) {
await weChatGroupService.delete(currentContact.id);
} else {
await contractService.delete(currentContact.id);
}
const currentUserId = useUserStore.getState().user?.id || 0;
const contactType = "chatroomId" in currentContact ? "group" : "friend";
await ContactManager.deleteContact(currentContact.id);
} catch (deleteError) {
console.error("删除本地数据失败:", deleteError);
}

View File

@@ -111,27 +111,58 @@ const ClickMenu: React.FC<ClickMenuProps> = ({
return timeDiffInSeconds <= 108;
};
const isText = (): boolean => {
return messageData.msgType === 1;
// 检查是否为文本消息
const isTextMessage = (): boolean => {
// msgType === 1 是纯文本消息
if (messageData.msgType === 1) return true;
// 尝试解析 content如果是 JSON 且只包含 text/content 字段,也认为是文本
try {
const parsed = JSON.parse(messageData.content || "");
// 如果包含图片、视频、语音等字段,则不是纯文本
if (
parsed.previewImage ||
parsed.tencentUrl ||
parsed.voiceUrl ||
parsed.voice ||
parsed.videoUrl ||
parsed.video ||
parsed.fileUrl ||
parsed.file
) {
return false;
}
// 包含 text 或 content 字段,认为是文本
return !!(parsed.text || parsed.content);
} catch (error) {
// 不是 JSON如果 msgType 不确定,按文本处理
return true;
}
};
const menuItems = [
{
key: "transmit",
icon: <ExportOutlined />,
label: "转发",
},
{
key: "copy",
icon: <CopyOutlined />,
label: "复制",
},
// 只在文本消息时显示复制选项
...(isTextMessage()
? [
{
key: "copy",
icon: <CopyOutlined />,
label: "复制",
},
]
: []),
{
key: "multipleForwarding",
icon: <CheckSquareOutlined />,
label: "多条转发",
},
...(isText()
// 只在文本消息时显示引用选项
...(isTextMessage()
? [
{
key: "quote",

View File

@@ -17,8 +17,10 @@ import {
TeamOutlined,
} from "@ant-design/icons";
import styles from "./TransmitModal.module.scss";
import { weChatGroupService, contractService } from "@/utils/db";
import { ContactManager } from "@/utils/dbAction";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { useContactStore } from "@/store/module/weChat/contacts";
import { useUserStore } from "@/store/module/user";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
const TransmitModal: React.FC = () => {
@@ -33,11 +35,11 @@ const TransmitModal: React.FC = () => {
const [page, setPage] = useState(1);
const pageSize = 20;
const { sendCommand } = useWebSocketStore.getState();
const currentUserId = useUserStore(state => state.user?.id) || 0;
// 从 Zustand store 获取更新方法
const openTransmitModal = useWeChatStore(state => state.openTransmitModal);
const updateTransmitModal = useWeChatStore(
state => state.updateTransmitModal,
);
const openTransmitModal = useContactStore(state => state.openTransmitModal);
const setTransmitModal = useContactStore(state => state.setTransmitModal);
const updateSelectedChatRecords = useWeChatStore(
state => state.updateSelectedChatRecords,
);
@@ -50,14 +52,10 @@ const TransmitModal: React.FC = () => {
const loadContacts = async () => {
setLoading(true);
try {
// 并行加载联系人和群组数据
const [contractsData, groupsData] = await Promise.all([
contractService.findAll(),
weChatGroupService.findAll(),
]);
// 合并并排序(联系人在前,群组在后)
const allContactsData = [...contractsData, ...groupsData];
setAllContacts(allContactsData);
// 从统一联系人表加载所有联系人
const allContactsData =
await ContactManager.getUserContacts(currentUserId);
setAllContacts(allContactsData as any);
} catch (err) {
console.error("加载联系人数据失败:", err);
message.error("加载联系人数据失败");
@@ -132,7 +130,7 @@ const TransmitModal: React.FC = () => {
}
}
updateSelectedChatRecords([]);
updateTransmitModal(false);
setTransmitModal(false);
};
// 检查联系人是否已选择

View File

@@ -349,20 +349,38 @@ const MessageList: React.FC<MessageListProps> = () => {
// 初始化加载会话列表
useEffect(() => {
const initializeSessions = async () => {
if (!currentUserId) return;
console.log("MessageList 初始化加载,currentUserId:", currentUserId);
console.log("user 对象:", user);
// 如果已经加载过一次,只从本地数据库读取,不请求接口
if (hasLoadedOnce) {
try {
const cachedSessions =
await MessageManager.getUserSessions(currentUserId);
setSessions(cachedSessions);
} catch (error) {
console.error("从本地加载会话列表失败:", error);
}
if (!currentUserId || currentUserId === 0) {
console.warn("currentUserId 无效,跳过加载:", currentUserId);
return;
}
// 如果已经加载过一次,只从本地数据库读取,不请求接口
if (hasLoadedOnce) {
console.log("已加载过,只从本地数据库读取");
try {
const cachedSessions =
await MessageManager.getUserSessions(currentUserId);
console.log("从本地加载会话数:", cachedSessions.length);
// 如果本地数据为空,重置 hasLoadedOnce 并重新加载
if (cachedSessions.length === 0) {
console.warn("本地数据为空,重置加载状态并重新加载");
setHasLoadedOnce(false);
// 不 return继续执行下面的首次加载逻辑
} else {
setSessions(cachedSessions);
return;
}
} catch (error) {
console.error("从本地加载会话列表失败:", error);
return;
}
}
console.log("首次加载,开始初始化...");
setLoading(true);
try {
@@ -370,19 +388,26 @@ const MessageList: React.FC<MessageListProps> = () => {
const cachedSessions =
await MessageManager.getUserSessions(currentUserId);
console.log("本地缓存会话数:", cachedSessions.length);
if (cachedSessions.length > 0) {
// 有缓存数据,立即显示
console.log("有缓存数据,立即显示");
setSessions(cachedSessions);
setLoading(false);
// 2. 后台静默同步(不显示同步提示)
console.log("后台静默同步中...");
await syncWithServer();
setHasLoadedOnce(true); // 标记已加载过
console.log("同步完成");
} else {
// 无缓存直接API加载
console.log("无缓存,从服务器加载...");
await syncWithServer();
const newSessions =
await MessageManager.getUserSessions(currentUserId);
console.log("从服务器加载会话数:", newSessions.length);
setSessions(newSessions);
setLoading(false);
setHasLoadedOnce(true); // 标记已加载过

View File

@@ -12,23 +12,12 @@ interface SidebarMenuProps {
}
const SidebarMenu: React.FC<SidebarMenuProps> = ({ loading = false }) => {
const {
searchKeyword,
setSearchKeyword,
clearSearchKeyword,
currentContact,
} = useContactStore();
const { searchKeyword, setSearchKeyword, clearSearchKeyword } =
useContactStore();
const currentCustomer = useCustomerStore(state => state.currentCustomer);
const [activeTab, setActiveTab] = useState("chats");
// 监听 currentContact 变化自动切换到聊天tab
useEffect(() => {
if (currentContact !== null) {
setActiveTab("chats");
}
}, [currentContact]);
const handleSearch = (value: string) => {
setSearchKeyword(value);
};

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useEffect } from "react";
import { Layout } from "antd";
import { MessageOutlined } from "@ant-design/icons";
import ChatWindow from "./components/ChatWindow/index";
@@ -6,34 +6,33 @@ import SidebarMenu from "./components/SidebarMenu/index";
import CustomerList from "./components/CustomerList";
import PageSkeleton from "./components/Skeleton";
import styles from "./index.module.scss";
const { Content, Sider } = Layout;
import { chatInitAPIdata, initSocket } from "./main";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { useUserStore } from "@/store/module/user";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { useWeChatStore } from "@/store/module/weChat/weChat";
import { getIsLoadWeChat } from "@/store/module/ckchat/ckchat";
const { Content, Sider } = Layout;
const CkboxPage: React.FC = () => {
// 不要在组件初始化时获取sendCommand而是在需要时动态获取
const [loading, setLoading] = useState(false);
const currentContract = useWeChatStore(state => state.currentContract);
// 初始化 WebSocket 连接
useEffect(() => {
// 方法一:使用 Promise 链式调用处理异步函数
// if (!getIsLoadWeChat()) {
// setLoading(true);
// }
chatInitAPIdata()
.then(() => {
// 数据加载完成后初始化WebSocket连接
initSocket();
})
.catch(error => {
console.error("获取联系人列表失败:", error);
})
.finally(() => {
setLoading(false);
});
const { token2 } = useUserStore.getState();
const { getAccountId } = useCkChatStore.getState();
const { connect } = useWebSocketStore.getState();
connect({
accessToken: token2,
accountId: Number(getAccountId()),
client: "kefu-client",
cmdType: "CmdSignIn",
seq: +new Date(),
});
}, []);
return (
<PageSkeleton loading={loading}>
<PageSkeleton loading={false}>
<Layout className={styles.ckboxLayout}>
<Layout>
{/* 垂直侧边栏 */}
@@ -44,7 +43,7 @@ const CkboxPage: React.FC = () => {
{/* 左侧联系人边栏 */}
<Sider width={280} className={styles.sider}>
<SidebarMenu loading={loading} />
<SidebarMenu />
</Sider>
{/* 主内容区 */}

View File

@@ -1,321 +0,0 @@
import {
asyncChatSessions,
asyncWeChatGroup,
asyncCountLables,
useCkChatStore,
updateIsLoadWeChat,
getIsLoadWeChat,
} from "@/store/module/ckchat/ckchat";
import { useContactStore } from "@/store/module/weChat/contacts";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { useUserStore } from "@/store/module/user";
import { weChatGroupService, contractService } from "@/utils/db";
import {
getControlTerminalList,
getContactList,
getGroupList,
getCustomerList,
getLabelsListByGroup,
getMessageList,
} from "./api";
import {
KfUserListData,
ContractData,
weChatGroup,
} from "@/pages/pc/ckbox/data";
import { updateCustomerList } from "@weChatStore/customer";
//获取触客宝基础信息
export const chatInitAPIdata = async () => {
try {
let contractList = [];
let groupList = [];
if (getIsLoadWeChat()) {
//获取联系人列表
contractList = await contractService.findAll();
//获取群列表
groupList = await weChatGroupService.findAll();
} else {
//获取联系人列表
contractList = await getAllContactList();
//获取群列表
groupList = await getAllGroupList();
updateIsLoadWeChat(true);
}
//获取联系人列表 - 使用新的 contacts store
// 注意:这里需要将 contractList 和 groupList 转换为统一的 Contact 格式
// 暂时保留旧的数据库写入逻辑,后续需要统一到 ContactManager
await contractService.createManyWithServerId(contractList);
await asyncWeChatGroup(groupList);
//获取标签列表
const countLables = await getCountLables();
await asyncCountLables(countLables);
// 获取显示名称的辅助函数(优先 conRemark其次 nickname
const getDisplayName = (session: any) => {
return session.conRemark || session.nickname || "";
};
const messageList = await getAllMessageList();
//排序功能
const sortedSessions = messageList.sort((a, b) => {
// 获取置顶状态
const aTop = a.config?.top || false;
const bTop = b.config?.top || false;
// 首先按置顶状态排序(置顶的排在前面)
if (aTop !== bTop) {
return aTop ? -1 : 1;
}
// 如果都是置顶或都不是置顶,则按未读消息数量降序排列(未读消息多的排在前面)
const aUnread = a.config?.unreadCount || 0;
const bUnread = b.config?.unreadCount || 0;
if (aUnread !== bUnread) {
return bUnread - aUnread;
}
// 如果未读消息数量相同,则按显示名称排序(字母排序)
const aName = getDisplayName(a).toLowerCase();
const bName = getDisplayName(b).toLowerCase();
if (aName !== bName) {
return aName.localeCompare(bName, "zh-CN");
}
// 如果名称也相同,则按时间降序排列(最新的在前面)
// 如果lastUpdateTime不存在则将其排在最后
if (!a.lastUpdateTime) return 1;
if (!b.lastUpdateTime) return -1;
const timeCompare =
new Date(b.lastUpdateTime).getTime() -
new Date(a.lastUpdateTime).getTime();
return timeCompare;
});
//会话数据同步
asyncChatSessions(sortedSessions);
return {
contractList,
groupList,
};
} catch (error) {
console.error("获取联系人列表失败:", error);
return [];
}
};
// 递归获取所有联系人列表
export const getAllMessageList = async () => {
try {
let allMessages = [];
let page = 1;
const limit = 500;
let hasMore = true;
while (hasMore) {
const Result = await getMessageList({
page,
limit,
});
const messageList = Result;
if (
!messageList ||
!Array.isArray(messageList) ||
messageList.length === 0
) {
hasMore = false;
break;
}
allMessages = [...allMessages, ...messageList];
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
if (messageList.length == 0) {
hasMore = false;
} else {
page = page + 1;
}
}
return allMessages;
} catch (error) {
console.error("获取所有联系人列表失败:", error);
return [];
}
};
//发起soket连接
export const initSocket = () => {
// 从store获取token和accountId
const { token2 } = useUserStore.getState();
const { getAccountId } = useCkChatStore.getState();
const Token = token2;
const accountId = getAccountId();
// 使用WebSocket store初始化连接
const { connect } = useWebSocketStore.getState();
// 连接WebSocket
connect({
accessToken: Token,
accountId: Number(accountId),
client: "kefu-client",
cmdType: "CmdSignIn",
seq: +new Date(),
});
};
export const getCountLables = async () => {
const Result = await getLabelsListByGroup({});
const LablesRes = Result.list;
return [
...[
{
id: 0,
groupName: "默认群分组",
groupType: 2,
},
],
...LablesRes,
...[
{
id: 0,
groupName: "未分组",
groupType: 1,
},
],
];
};
/**
* 根据标签组织联系人
* @param contractList 联系人列表
* @param countLables 标签列表
* @returns 按标签分组的联系人
*/
//获取控制终端列表
export const getControlTerminalListByWechatAccountIds = (
WechatAccountIds: number[],
) => {
return Promise.all(
WechatAccountIds.map(id => getControlTerminalList({ id: id })),
);
};
// 递归获取所有联系人列表
export const getAllContactList = async () => {
try {
let allContacts = [];
let page = 1;
const limit = 500;
let hasMore = true;
while (hasMore) {
const Result = await getContactList({
page,
limit,
});
const contractList = Result.list;
if (
!contractList ||
!Array.isArray(contractList) ||
contractList.length === 0
) {
hasMore = false;
break;
}
allContacts = [...allContacts, ...contractList];
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
if (contractList.length == 0) {
hasMore = false;
} else {
page = page + 1;
}
}
return allContacts;
} catch (error) {
console.error("获取所有联系人列表失败:", error);
return [];
}
};
// 递归获取所有群列表
export const getAllGroupList = async () => {
try {
let allContacts = [];
let page = 1;
const limit = 500;
let hasMore = true;
while (hasMore) {
const Result = await getGroupList({
page,
limit,
});
const contractList = Result.list;
if (
!contractList ||
!Array.isArray(contractList) ||
contractList.length === 0
) {
hasMore = false;
break;
}
allContacts = [...allContacts, ...contractList];
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
if (contractList.length < limit) {
hasMore = false;
} else {
// 获取最后一条数据的id作为下一次请求的page
const lastContact = contractList[contractList.length - 1];
page = lastContact.id;
}
}
return allContacts;
} catch (error) {
console.error("获取所有群列表失败:", error);
return [];
}
};
// 提取不重复的wechatAccountId组
export const getUniqueWechatAccountIds = (
contacts: ContractData[],
groupList: weChatGroup[],
) => {
if (!contacts || !Array.isArray(contacts) || contacts.length === 0) {
return [];
}
// 使用Set来存储不重复的wechatAccountId
const uniqueAccountIdsSet = new Set<number>();
// 遍历联系人列表将每个wechatAccountId添加到Set中
contacts.forEach(contact => {
if (contact && contact.wechatAccountId) {
uniqueAccountIdsSet.add(contact.wechatAccountId);
}
});
// 遍历联系人列表将每个wechatAccountId添加到Set中
groupList.forEach(group => {
if (group && group.wechatAccountId) {
uniqueAccountIdsSet.add(group.wechatAccountId);
}
});
// 将Set转换为数组并返回
return Array.from(uniqueAccountIdsSet);
};