图标
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
//构建联系人列表标签
|
||||
import { weChatGroupService, contractService } from "@/utils/db";
|
||||
import { request } from "@/api/request2";
|
||||
import { ContactGroupByLabel } from "@/pages/pc/ckbox/data";
|
||||
|
||||
export function WechatGroup(params) {
|
||||
return request("/api/WechatGroup/list", params, "GET");
|
||||
}
|
||||
|
||||
export const createContractList = async (
|
||||
kfSelected: number,
|
||||
countLables: ContactGroupByLabel[],
|
||||
) => {
|
||||
// 根据 groupType 决定查询不同的服务
|
||||
const dataByLabels = [];
|
||||
for (const label of countLables) {
|
||||
let data;
|
||||
if (label.groupType === 1) {
|
||||
// groupType: 1, 查询 contractService
|
||||
data = await contractService.findWhere("groupId", label.id);
|
||||
// 过滤出 kfSelected 对应的联系人
|
||||
if (kfSelected && kfSelected != 0) {
|
||||
data = data.filter(contact => contact.wechatAccountId === kfSelected);
|
||||
}
|
||||
// console.log(`标签 ${label.groupName} 对应的联系人数据:`, data);
|
||||
} else if (label.groupType === 2) {
|
||||
// groupType: 2, 查询 weChatGroupService
|
||||
data = await weChatGroupService.findWhere("groupId", label.id);
|
||||
if (kfSelected && kfSelected != 0) {
|
||||
data = data.filter(contact => contact.wechatAccountId === kfSelected);
|
||||
}
|
||||
} else {
|
||||
console.warn(`未知的 groupType: ${label.groupType}`);
|
||||
data = [];
|
||||
}
|
||||
dataByLabels.push({
|
||||
...label,
|
||||
contacts: data,
|
||||
});
|
||||
}
|
||||
|
||||
return dataByLabels;
|
||||
};
|
||||
@@ -1,72 +0,0 @@
|
||||
import {
|
||||
ContractData,
|
||||
KfUserListData,
|
||||
CkAccount,
|
||||
ContactGroupByLabel,
|
||||
weChatGroup,
|
||||
} from "@/pages/pc/ckbox/data";
|
||||
|
||||
// 权限片段接口
|
||||
export interface PrivilegeFrag {
|
||||
// 根据实际数据结构补充
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// 租户信息接口
|
||||
export interface CkTenant {
|
||||
id: number;
|
||||
name: string;
|
||||
guid: string;
|
||||
thirdParty: string | null;
|
||||
tenantType: number;
|
||||
deployName: string;
|
||||
}
|
||||
|
||||
// 触客宝用户信息接口
|
||||
export interface CkUserInfo {
|
||||
account: CkAccount;
|
||||
privilegeFrags: PrivilegeFrag[];
|
||||
tenant: CkTenant;
|
||||
}
|
||||
|
||||
// 状态接口
|
||||
export interface CkChatState {
|
||||
userInfo: CkUserInfo | null;
|
||||
isLoggedIn: boolean;
|
||||
searchKeyword: string;
|
||||
contractList: ContractData[];
|
||||
chatSessions: any[];
|
||||
kfUserList: KfUserListData[];
|
||||
kfSelected: number;
|
||||
getKfSelectedUser: () => KfUserListData | undefined;
|
||||
countLables: ContactGroupByLabel[];
|
||||
newContractList: ContactGroupByLabel[];
|
||||
getContractList: () => ContractData[];
|
||||
getSomeContractList: (kfSelected: number) => ContractData[];
|
||||
getNewContractList: () => ContactGroupByLabel[];
|
||||
setSearchKeyword: (keyword: string) => void;
|
||||
clearSearchKeyword: () => void;
|
||||
asyncKfSelected: (data: number) => void;
|
||||
asyncWeChatGroup: (data: weChatGroup[]) => void;
|
||||
asyncCountLables: (data: ContactGroupByLabel[]) => void;
|
||||
getkfUserList: () => KfUserListData[];
|
||||
asyncKfUserList: (data: KfUserListData[]) => void;
|
||||
getKfUserInfo: (wechatAccountId: number) => KfUserListData | undefined;
|
||||
asyncContractList: (data: ContractData[]) => void;
|
||||
getChatSessions: () => any[];
|
||||
asyncChatSessions: (data: any[]) => void;
|
||||
updateChatSession: (session: ContractData | weChatGroup) => void;
|
||||
deleteCtrlUser: (userId: number) => void;
|
||||
updateCtrlUser: (user: KfUserListData) => void;
|
||||
clearkfUserList: () => void;
|
||||
addChatSession: (session: any) => void;
|
||||
deleteChatSession: (sessionId: number) => void;
|
||||
setUserInfo: (userInfo: CkUserInfo) => void;
|
||||
clearUserInfo: () => void;
|
||||
updateAccount: (account: Partial<CkAccount>) => void;
|
||||
updateTenant: (tenant: Partial<CkTenant>) => void;
|
||||
getAccountId: () => number | null;
|
||||
getTenantId: () => number | null;
|
||||
getAccountName: () => string | null;
|
||||
getTenantName: () => string | null;
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
import { createPersistStore } from "@/store/createPersistStore";
|
||||
import { CkChatState, CkUserInfo, CkTenant } from "./ckchat.data";
|
||||
import {
|
||||
ContractData,
|
||||
weChatGroup,
|
||||
CkAccount,
|
||||
KfUserListData,
|
||||
ContactGroupByLabel,
|
||||
} from "@/pages/pc/ckbox/data";
|
||||
import { weChatGroupService, contractService } from "@/utils/db";
|
||||
import { createContractList } from "@/store/module/ckchat/api";
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
// 从weChat store获取clearCurrentContact方法
|
||||
const getClearCurrentContact = () =>
|
||||
useWeChatStore.getState().clearCurrentContact;
|
||||
export const useCkChatStore = createPersistStore<CkChatState>(
|
||||
set => ({
|
||||
userInfo: null,
|
||||
isLoggedIn: false,
|
||||
contractList: [], //联系人列表
|
||||
chatSessions: [], //聊天会话
|
||||
kfUserList: [], //客服列表
|
||||
countLables: [], //标签列表
|
||||
newContractList: [], //联系人分组
|
||||
kfSelected: 0, //选中的客服
|
||||
searchKeyword: "", //搜索关键词
|
||||
//客服列表
|
||||
asyncKfUserList: async data => {
|
||||
set({ kfUserList: data });
|
||||
// await kfUserService.createManyWithServerId(data);
|
||||
},
|
||||
// 获取客服列表
|
||||
getkfUserList: async () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.kfUserList;
|
||||
// return await kfUserService.findAll();
|
||||
},
|
||||
// 异步设置标签列表
|
||||
asyncCountLables: async (data: ContactGroupByLabel[]) => {
|
||||
set({ countLables: data });
|
||||
// 清除getNewContractList缓存
|
||||
const state = useCkChatStore.getState();
|
||||
if (
|
||||
state.getNewContractList &&
|
||||
typeof state.getNewContractList === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
await state.getNewContractList();
|
||||
}
|
||||
},
|
||||
// 设置搜索关键词
|
||||
setSearchKeyword: (keyword: string) => {
|
||||
set({ searchKeyword: keyword });
|
||||
},
|
||||
// 清除搜索关键词
|
||||
clearSearchKeyword: () => {
|
||||
set({ searchKeyword: "" });
|
||||
},
|
||||
asyncKfSelected: async (data: number) => {
|
||||
set({ kfSelected: data });
|
||||
// 清除getChatSessions、getContractList和getNewContractList缓存
|
||||
const state = useCkChatStore.getState();
|
||||
if (
|
||||
state.getChatSessions &&
|
||||
typeof state.getChatSessions === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
state.getChatSessions();
|
||||
}
|
||||
if (
|
||||
state.getContractList &&
|
||||
typeof state.getContractList === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
state.getContractList();
|
||||
}
|
||||
if (
|
||||
state.getNewContractList &&
|
||||
typeof state.getNewContractList === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
await state.getNewContractList();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取联系人分组列表 - 使用缓存避免无限循环
|
||||
getNewContractList: (() => {
|
||||
let cachedResult: any = null;
|
||||
let lastKfSelected: number | null = null;
|
||||
let lastCountLablesLength: number = 0;
|
||||
let lastSearchKeyword: string = "";
|
||||
|
||||
return async () => {
|
||||
const state = useCkChatStore.getState();
|
||||
|
||||
// 检查是否需要重新计算缓存
|
||||
const shouldRecalculate =
|
||||
cachedResult === null ||
|
||||
lastKfSelected !== state.kfSelected ||
|
||||
lastCountLablesLength !== (state.countLables?.length || 0) ||
|
||||
lastSearchKeyword !== state.searchKeyword;
|
||||
|
||||
if (shouldRecalculate) {
|
||||
// 使用createContractList构建联系人分组数据
|
||||
let contractList = await createContractList(
|
||||
state.kfSelected,
|
||||
state.countLables,
|
||||
);
|
||||
|
||||
// 根据搜索关键词筛选联系人分组
|
||||
if (state.searchKeyword.trim()) {
|
||||
const keyword = state.searchKeyword.toLowerCase();
|
||||
contractList = contractList
|
||||
.map(group => ({
|
||||
...group,
|
||||
contracts:
|
||||
group.contracts?.filter(item => {
|
||||
const nickname = (item.nickname || "").toLowerCase();
|
||||
const conRemark = (item.conRemark || "").toLowerCase();
|
||||
return (
|
||||
nickname.includes(keyword) || conRemark.includes(keyword)
|
||||
);
|
||||
}) || [],
|
||||
}))
|
||||
.filter(group => group.contracts.length > 0);
|
||||
}
|
||||
|
||||
cachedResult = contractList;
|
||||
lastKfSelected = state.kfSelected;
|
||||
lastCountLablesLength = state.countLables?.length || 0;
|
||||
lastSearchKeyword = state.searchKeyword;
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
};
|
||||
})(),
|
||||
// 搜索好友和群组的新方法 - 从本地数据库查询并返回扁平化的搜索结果
|
||||
searchContactsAndGroups: (() => {
|
||||
let cachedResult: (ContractData | weChatGroup)[] = [];
|
||||
let lastKfSelected: number | null = null;
|
||||
let lastSearchKeyword: string = "";
|
||||
|
||||
return async () => {
|
||||
const state = useCkChatStore.getState();
|
||||
|
||||
// 检查是否需要重新计算缓存
|
||||
const shouldRecalculate =
|
||||
lastKfSelected !== state.kfSelected ||
|
||||
lastSearchKeyword !== state.searchKeyword;
|
||||
|
||||
if (shouldRecalculate) {
|
||||
if (state.searchKeyword.trim()) {
|
||||
const keyword = state.searchKeyword.toLowerCase();
|
||||
|
||||
// 从本地数据库查询联系人数据
|
||||
let allContacts: any[] = await contractService.findAll();
|
||||
|
||||
// 从本地数据库查询群组数据
|
||||
let allGroups: any[] = await weChatGroupService.findAll();
|
||||
|
||||
// 根据选中的客服筛选联系人
|
||||
if (state.kfSelected !== 0) {
|
||||
allContacts = allContacts.filter(
|
||||
item => item.wechatAccountId === state.kfSelected,
|
||||
);
|
||||
}
|
||||
|
||||
// 根据选中的客服筛选群组
|
||||
if (state.kfSelected !== 0) {
|
||||
allGroups = allGroups.filter(
|
||||
item => item.wechatAccountId === state.kfSelected,
|
||||
);
|
||||
}
|
||||
|
||||
// 搜索匹配的联系人
|
||||
const matchedContacts = allContacts.filter(item => {
|
||||
const nickname = (item.nickname || "").toLowerCase();
|
||||
const conRemark = (item.conRemark || "").toLowerCase();
|
||||
return nickname.includes(keyword) || conRemark.includes(keyword);
|
||||
});
|
||||
|
||||
// 搜索匹配的群组
|
||||
const matchedGroups = allGroups.filter(item => {
|
||||
const nickname = (item.nickname || "").toLowerCase();
|
||||
const conRemark = (item.conRemark || "").toLowerCase();
|
||||
return nickname.includes(keyword) || conRemark.includes(keyword);
|
||||
});
|
||||
|
||||
// 合并搜索结果
|
||||
cachedResult = [...matchedContacts, ...matchedGroups];
|
||||
} else {
|
||||
cachedResult = [];
|
||||
}
|
||||
|
||||
lastKfSelected = state.kfSelected;
|
||||
lastSearchKeyword = state.searchKeyword;
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
};
|
||||
})(),
|
||||
// 异步设置联系人分组列表
|
||||
asyncNewContractList: async (data: any[]) => {
|
||||
set({ newContractList: data });
|
||||
// 清除getNewContractList缓存
|
||||
const state = useCkChatStore.getState();
|
||||
if (
|
||||
state.getNewContractList &&
|
||||
typeof state.getNewContractList === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
await state.getNewContractList();
|
||||
}
|
||||
},
|
||||
// 异步设置会话列表
|
||||
asyncChatSessions: data => {
|
||||
set({ chatSessions: data });
|
||||
// 清除getChatSessions缓存
|
||||
const state = useCkChatStore.getState();
|
||||
if (
|
||||
state.getChatSessions &&
|
||||
typeof state.getChatSessions === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
state.getChatSessions();
|
||||
}
|
||||
},
|
||||
// 异步设置联系人列表
|
||||
asyncContractList: async (data: ContractData[]) => {
|
||||
set({ contractList: data });
|
||||
await contractService.createManyWithServerId(data);
|
||||
// 清除getContractList缓存
|
||||
const state = useCkChatStore.getState();
|
||||
if (
|
||||
state.getContractList &&
|
||||
typeof state.getContractList === "function"
|
||||
) {
|
||||
// 触发缓存重新计算
|
||||
state.getContractList();
|
||||
}
|
||||
},
|
||||
//获取特定联系人
|
||||
getSomeContractList: (kfSelected: number) => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.contractList.filter(
|
||||
item => item.wechatAccountId === kfSelected,
|
||||
);
|
||||
},
|
||||
// 获取联系人列表 - 使用缓存避免无限循环
|
||||
getContractList: (() => {
|
||||
let cachedResult: any = null;
|
||||
let lastKfSelected: number | null = null;
|
||||
let lastContractListLength: number = 0;
|
||||
let lastSearchKeyword: string = "";
|
||||
|
||||
return () => {
|
||||
const state = useCkChatStore.getState();
|
||||
|
||||
// 检查是否需要重新计算缓存
|
||||
const shouldRecalculate =
|
||||
cachedResult === null ||
|
||||
lastKfSelected !== state.kfSelected ||
|
||||
lastContractListLength !== state.contractList.length ||
|
||||
lastSearchKeyword !== state.searchKeyword;
|
||||
|
||||
if (shouldRecalculate) {
|
||||
let filteredContracts = state.contractList;
|
||||
|
||||
// 根据客服筛选
|
||||
if (state.kfSelected !== 0) {
|
||||
filteredContracts = filteredContracts.filter(
|
||||
item => item.wechatAccountId === state.kfSelected,
|
||||
);
|
||||
}
|
||||
|
||||
// 根据搜索关键词筛选
|
||||
if (state.searchKeyword.trim()) {
|
||||
const keyword = state.searchKeyword.toLowerCase();
|
||||
filteredContracts = filteredContracts.filter(item => {
|
||||
const nickname = (item.nickname || "").toLowerCase();
|
||||
const conRemark = (item.conRemark || "").toLowerCase();
|
||||
return nickname.includes(keyword) || conRemark.includes(keyword);
|
||||
});
|
||||
}
|
||||
|
||||
cachedResult = filteredContracts;
|
||||
lastKfSelected = state.kfSelected;
|
||||
lastContractListLength = state.contractList.length;
|
||||
lastSearchKeyword = state.searchKeyword;
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
};
|
||||
})(),
|
||||
//异步设置联系人分组
|
||||
asyncWeChatGroup: async (data: weChatGroup[]) => {
|
||||
await weChatGroupService.createManyWithServerId(data);
|
||||
},
|
||||
//获取选中的客服信息
|
||||
getKfSelectedUser: () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.kfUserList.find(item => item.id === state.kfSelected);
|
||||
},
|
||||
getKfUserInfo: (wechatAccountId: number) => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.kfUserList.find(item => item.id === wechatAccountId);
|
||||
},
|
||||
|
||||
// 删除控制终端用户
|
||||
deleteCtrlUser: (userId: number) => {
|
||||
set(state => ({
|
||||
kfUserList: state.kfUserList.filter(item => item.id !== userId),
|
||||
}));
|
||||
},
|
||||
// 更新控制终端用户
|
||||
updateCtrlUser: (user: KfUserListData) => {
|
||||
set(state => ({
|
||||
kfUserList: state.kfUserList.map(item =>
|
||||
item.id === user.id ? user : item,
|
||||
),
|
||||
}));
|
||||
},
|
||||
// 清空控制终端用户列表
|
||||
clearkfUserList: () => {
|
||||
set({ kfUserList: [] });
|
||||
},
|
||||
// 获取聊天会话 - 使用缓存避免无限循环
|
||||
getChatSessions: (() => {
|
||||
let cachedResult: any = null;
|
||||
let lastKfSelected: number | null = null;
|
||||
let lastChatSessionsLength: number = 0;
|
||||
let lastSearchKeyword: string = "";
|
||||
|
||||
return () => {
|
||||
const state = useCkChatStore.getState();
|
||||
|
||||
// 检查是否需要重新计算缓存
|
||||
const shouldRecalculate =
|
||||
cachedResult === null ||
|
||||
lastKfSelected !== state.kfSelected ||
|
||||
lastChatSessionsLength !== state.chatSessions.length ||
|
||||
lastSearchKeyword !== state.searchKeyword;
|
||||
|
||||
if (shouldRecalculate) {
|
||||
let filteredSessions = state.chatSessions;
|
||||
|
||||
// 根据客服筛选
|
||||
if (state.kfSelected !== 0) {
|
||||
filteredSessions = filteredSessions.filter(
|
||||
item => item.wechatAccountId === state.kfSelected,
|
||||
);
|
||||
}
|
||||
|
||||
// 根据搜索关键词筛选
|
||||
if (state.searchKeyword.trim()) {
|
||||
const keyword = state.searchKeyword.toLowerCase();
|
||||
filteredSessions = filteredSessions.filter(item => {
|
||||
const nickname = (item.nickname || "").toLowerCase();
|
||||
const conRemark = (item.conRemark || "").toLowerCase();
|
||||
return nickname.includes(keyword) || conRemark.includes(keyword);
|
||||
});
|
||||
}
|
||||
|
||||
cachedResult = filteredSessions;
|
||||
lastKfSelected = state.kfSelected;
|
||||
lastChatSessionsLength = state.chatSessions.length;
|
||||
lastSearchKeyword = state.searchKeyword;
|
||||
}
|
||||
|
||||
return cachedResult;
|
||||
};
|
||||
})(),
|
||||
// 添加聊天会话
|
||||
addChatSession: (session: ContractData | weChatGroup) => {
|
||||
set(state => {
|
||||
// 检查是否已存在相同id的会话
|
||||
const exists = state.chatSessions.some(item => item.id === session.id);
|
||||
// 如果已存在则不添加,否则添加到列表中
|
||||
return {
|
||||
chatSessions: exists
|
||||
? state.chatSessions
|
||||
: [...state.chatSessions, session as ContractData | weChatGroup],
|
||||
};
|
||||
});
|
||||
},
|
||||
// 更新聊天会话
|
||||
updateChatSession: (session: ContractData | weChatGroup) => {
|
||||
set(state => ({
|
||||
chatSessions: state.chatSessions.map(item =>
|
||||
item.id === session.id ? { ...item, ...session } : item,
|
||||
),
|
||||
}));
|
||||
},
|
||||
// 删除聊天会话
|
||||
deleteChatSession: (sessionId: number) => {
|
||||
set(state => ({
|
||||
chatSessions: state.chatSessions.filter(item => item.id !== sessionId),
|
||||
}));
|
||||
//当前选中的客户清空
|
||||
getClearCurrentContact();
|
||||
},
|
||||
// 设置用户信息
|
||||
setUserInfo: (userInfo: CkUserInfo) => {
|
||||
set({ userInfo, isLoggedIn: true });
|
||||
},
|
||||
|
||||
// 清除用户信息
|
||||
clearUserInfo: () => {
|
||||
set({ userInfo: null, isLoggedIn: false });
|
||||
},
|
||||
|
||||
// 更新账户信息
|
||||
updateAccount: (account: Partial<CkAccount>) => {
|
||||
set(state => ({
|
||||
userInfo: state.userInfo
|
||||
? {
|
||||
...state.userInfo,
|
||||
account: { ...state.userInfo.account, ...account },
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
},
|
||||
|
||||
// 更新租户信息
|
||||
updateTenant: (tenant: Partial<CkTenant>) => {
|
||||
set(state => ({
|
||||
userInfo: state.userInfo
|
||||
? {
|
||||
...state.userInfo,
|
||||
tenant: { ...state.userInfo.tenant, ...tenant },
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
},
|
||||
|
||||
// 获取账户ID
|
||||
getAccountId: () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return Number(state.userInfo?.account?.id) || null;
|
||||
},
|
||||
|
||||
// 获取租户ID
|
||||
getTenantId: () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.userInfo?.tenant?.id || null;
|
||||
},
|
||||
|
||||
// 获取账户名称
|
||||
getAccountName: () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return (
|
||||
state.userInfo?.account?.realName ||
|
||||
state.userInfo?.account?.userName ||
|
||||
null
|
||||
);
|
||||
},
|
||||
|
||||
// 获取租户名称
|
||||
getTenantName: () => {
|
||||
const state = useCkChatStore.getState();
|
||||
return state.userInfo?.tenant?.name || null;
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "ckchat-store",
|
||||
partialize: state => ({
|
||||
userInfo: state.userInfo,
|
||||
isLoggedIn: state.isLoggedIn,
|
||||
kfUserList: state.kfUserList,
|
||||
}),
|
||||
onRehydrateStorage: () => state => {
|
||||
// console.log("CkChat store hydrated:", state);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// 导出便捷的获取方法
|
||||
export const getCkAccountId = () => useCkChatStore.getState().getAccountId();
|
||||
export const getCkTenantId = () => useCkChatStore.getState().getTenantId();
|
||||
export const getCkAccountName = () =>
|
||||
useCkChatStore.getState().getAccountName();
|
||||
export const getCkTenantName = () => useCkChatStore.getState().getTenantName();
|
||||
export const getChatSessions = () =>
|
||||
useCkChatStore.getState().getChatSessions();
|
||||
export const addChatSession = (session: ContractData | weChatGroup) =>
|
||||
useCkChatStore.getState().addChatSession(session);
|
||||
export const updateChatSession = (session: ContractData | weChatGroup) =>
|
||||
useCkChatStore.getState().updateChatSession(session);
|
||||
export const deleteChatSession = (sessionId: string) =>
|
||||
useCkChatStore.getState().deleteChatSession(sessionId);
|
||||
export const getkfUserList = () => useCkChatStore.getState().kfUserList;
|
||||
export const addCtrlUser = (user: KfUserListData) =>
|
||||
useCkChatStore.getState().addCtrlUser(user);
|
||||
export const deleteCtrlUser = (userId: number) =>
|
||||
useCkChatStore.getState().deleteCtrlUser(userId);
|
||||
export const updateCtrlUser = (user: KfUserListData) =>
|
||||
useCkChatStore.getState().updateCtrlUser(user);
|
||||
export const asyncKfUserList = (data: KfUserListData[]) =>
|
||||
useCkChatStore.getState().asyncKfUserList(data);
|
||||
export const asyncContractList = (data: ContractData[]) =>
|
||||
useCkChatStore.getState().asyncContractList(data);
|
||||
export const asyncChatSessions = (data: ContractData[]) =>
|
||||
useCkChatStore.getState().asyncChatSessions(data);
|
||||
export const asyncKfSelected = (data: number) =>
|
||||
useCkChatStore.getState().asyncKfSelected(data);
|
||||
export const asyncWeChatGroup = (data: weChatGroup[]) =>
|
||||
useCkChatStore.getState().asyncWeChatGroup(data);
|
||||
export const getKfSelectedUser = () =>
|
||||
useCkChatStore.getState().getKfSelectedUser();
|
||||
export const getKfUserInfo = (wechatAccountId: number) =>
|
||||
useCkChatStore.getState().getKfUserInfo(wechatAccountId);
|
||||
export const getContractList = () =>
|
||||
useCkChatStore.getState().getContractList();
|
||||
export const getNewContractList = () =>
|
||||
useCkChatStore.getState().getNewContractList();
|
||||
export const asyncCountLables = (data: ContactGroupByLabel[]) =>
|
||||
useCkChatStore.getState().asyncCountLables(data);
|
||||
export const asyncNewContractList = (data: any[]) =>
|
||||
useCkChatStore.getState().asyncNewContractList(data);
|
||||
export const getCountLables = () => useCkChatStore.getState().countLables;
|
||||
export const setSearchKeyword = (keyword: string) =>
|
||||
useCkChatStore.getState().setSearchKeyword(keyword);
|
||||
export const clearSearchKeyword = () =>
|
||||
useCkChatStore.getState().clearSearchKeyword();
|
||||
export const searchContactsAndGroups = () =>
|
||||
useCkChatStore.getState().searchContactsAndGroups();
|
||||
useCkChatStore.getState().getKfSelectedUser();
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
// 微信聊天相关的类型定义
|
||||
export interface WeChatState {
|
||||
// 当前选中的联系人/群组
|
||||
currentContract: ContractData | weChatGroup | null;
|
||||
|
||||
// 当前聊天用户的消息列表(只存储当前聊天用户的消息)
|
||||
currentMessages: ChatRecord[];
|
||||
// 清空当前联系人
|
||||
clearCurrentContact: () => void;
|
||||
// 消息加载状态
|
||||
messagesLoading: boolean;
|
||||
isLoadingData: boolean;
|
||||
currentGroupMembers: any[];
|
||||
|
||||
// Actions
|
||||
setCurrentContact: (
|
||||
contract: ContractData | weChatGroup,
|
||||
isExist?: boolean,
|
||||
) => void;
|
||||
loadChatMessages: (Init: boolean, To?: number) => Promise<void>;
|
||||
SearchMessage: (params: {
|
||||
From: number;
|
||||
To: number;
|
||||
keyword: string;
|
||||
Count?: number;
|
||||
}) => Promise<void>;
|
||||
// 视频消息处理方法
|
||||
setVideoLoading: (messageId: number, isLoading: boolean) => void;
|
||||
setVideoUrl: (messageId: number, videoUrl: string) => void;
|
||||
addMessage: (message: ChatRecord) => void;
|
||||
receivedMsg: (message: ChatRecord) => void;
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import {
|
||||
getChatMessages,
|
||||
getChatroomMessages,
|
||||
getGroupMembers,
|
||||
} from "@/pages/pc/ckbox/api";
|
||||
import { WeChatState } from "./weChat.data";
|
||||
import { clearUnreadCount, updateConfig } from "@/pages/pc/ckbox/api";
|
||||
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||
import { weChatGroupService, contractService } from "@/utils/db";
|
||||
import {
|
||||
addChatSession,
|
||||
updateChatSession,
|
||||
useCkChatStore,
|
||||
} from "@/store/module/ckchat/ckchat";
|
||||
|
||||
export const useWeChatStore = create<WeChatState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
// 初始状态
|
||||
currentContract: null,
|
||||
currentMessages: [],
|
||||
messagesLoading: false,
|
||||
isLoadingData: false,
|
||||
currentGroupMembers: [],
|
||||
//清空当前联系人
|
||||
clearCurrentContact: () => {
|
||||
set({ currentContract: null, currentMessages: [] });
|
||||
},
|
||||
// Actions
|
||||
setCurrentContact: (
|
||||
contract: ContractData | weChatGroup,
|
||||
isExist?: boolean,
|
||||
) => {
|
||||
const state = useWeChatStore.getState();
|
||||
// 切换联系人时清空当前消息,等待重新加载
|
||||
set({ currentMessages: [] });
|
||||
clearUnreadCount([contract.id]).then(() => {
|
||||
if (isExist) {
|
||||
updateChatSession({ ...contract, unreadCount: 0 });
|
||||
} else {
|
||||
addChatSession(contract);
|
||||
}
|
||||
set({ currentContract: contract });
|
||||
updateConfig({
|
||||
id: contract.id,
|
||||
config: { chat: true },
|
||||
});
|
||||
state.loadChatMessages(true, 4704624000000);
|
||||
});
|
||||
},
|
||||
loadChatMessages: async (Init: boolean, To?: number) => {
|
||||
const state = useWeChatStore.getState();
|
||||
const contact = state.currentContract;
|
||||
set({ messagesLoading: true });
|
||||
set({ isLoadingData: Init });
|
||||
try {
|
||||
const params: any = {
|
||||
wechatAccountId: contact.wechatAccountId,
|
||||
From: 1,
|
||||
To: To || +new Date(),
|
||||
Count: 5,
|
||||
olderData: true,
|
||||
};
|
||||
|
||||
if ("chatroomId" in contact && contact.chatroomId) {
|
||||
params.wechatChatroomId = contact.id;
|
||||
const messages = await getChatroomMessages(params);
|
||||
const currentGroupMembers = 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 });
|
||||
} catch (error) {
|
||||
console.error("获取聊天消息失败:", error);
|
||||
} finally {
|
||||
set({ messagesLoading: false });
|
||||
}
|
||||
},
|
||||
SearchMessage: async ({
|
||||
From = 1,
|
||||
To = 4704624000000,
|
||||
keyword = "",
|
||||
Count = 20,
|
||||
}: {
|
||||
From: number;
|
||||
To: number;
|
||||
keyword: string;
|
||||
Count?: number;
|
||||
}) => {
|
||||
const state = useWeChatStore.getState();
|
||||
const contact = state.currentContract;
|
||||
set({ messagesLoading: true });
|
||||
|
||||
try {
|
||||
const params: any = {
|
||||
wechatAccountId: contact.wechatAccountId,
|
||||
From,
|
||||
To,
|
||||
keyword,
|
||||
Count,
|
||||
olderData: true,
|
||||
};
|
||||
|
||||
if ("chatroomId" in contact && contact.chatroomId) {
|
||||
params.wechatChatroomId = contact.id;
|
||||
const messages = await getChatroomMessages(params);
|
||||
const currentGroupMembers = await getGroupMembers({
|
||||
id: contact.id,
|
||||
});
|
||||
set({ currentMessages: messages || [], currentGroupMembers });
|
||||
} else {
|
||||
params.wechatFriendId = contact.id;
|
||||
const messages = await getChatMessages(params);
|
||||
set({ currentMessages: messages || [] });
|
||||
}
|
||||
set({ messagesLoading: false });
|
||||
} catch (error) {
|
||||
console.error("获取聊天消息失败:", error);
|
||||
} finally {
|
||||
set({ messagesLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setMessageLoading: loading => {
|
||||
set({ messagesLoading: Boolean(loading) });
|
||||
},
|
||||
|
||||
addMessage: message => {
|
||||
set(state => ({
|
||||
currentMessages: [...state.currentMessages, message],
|
||||
}));
|
||||
},
|
||||
|
||||
receivedMsg: async message => {
|
||||
const currentContract = useWeChatStore.getState().currentContract;
|
||||
//判断群还是好友
|
||||
const getMessageId =
|
||||
message?.wechatChatroomId || message.wechatFriendId;
|
||||
const isWechatGroup = message?.wechatChatroomId;
|
||||
//当前选中聊天的群或好友
|
||||
if (currentContract && currentContract.id == getMessageId) {
|
||||
set(state => ({
|
||||
currentMessages: [...state.currentMessages, message],
|
||||
}));
|
||||
} else {
|
||||
//更新消息列表unread数值,根据接收的++1 这样
|
||||
const chatSessions = useCkChatStore.getState().chatSessions;
|
||||
const session = chatSessions.find(item => item.id == getMessageId);
|
||||
if (session) {
|
||||
session.unreadCount = Number(session.unreadCount) + 1;
|
||||
updateChatSession(session);
|
||||
} else {
|
||||
if (isWechatGroup) {
|
||||
const [group] = await weChatGroupService.findByIds(getMessageId);
|
||||
if (group) {
|
||||
addChatSession({
|
||||
...group,
|
||||
unreadCount: 1,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const [user] = await contractService.findByIds(getMessageId);
|
||||
addChatSession({
|
||||
...user,
|
||||
unreadCount: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateMessage: (messageId, updates) => {
|
||||
set(state => ({
|
||||
currentMessages: state.currentMessages.map(msg =>
|
||||
msg.id === messageId ? { ...msg, ...updates } : msg,
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
// 便捷选择器
|
||||
getCurrentContact: () => get().currentContract,
|
||||
getCurrentMessages: () => get().currentMessages,
|
||||
getMessagesLoading: () => get().messagesLoading,
|
||||
|
||||
// 视频消息处理方法
|
||||
setVideoLoading: (messageId: number, isLoading: boolean) => {
|
||||
set(state => ({
|
||||
currentMessages: state.currentMessages.map(msg => {
|
||||
if (msg.id === messageId) {
|
||||
try {
|
||||
const content = JSON.parse(msg.content);
|
||||
// 更新加载状态
|
||||
const updatedContent = { ...content, isLoading };
|
||||
return {
|
||||
...msg,
|
||||
content: JSON.stringify(updatedContent),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("更新视频加载状态失败:", e);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}),
|
||||
}));
|
||||
},
|
||||
|
||||
setVideoUrl: (messageId: number, videoUrl: string) => {
|
||||
set(state => ({
|
||||
currentMessages: state.currentMessages.map(msg => {
|
||||
if (msg.id === messageId) {
|
||||
try {
|
||||
const content = JSON.parse(msg.content);
|
||||
// 检查视频是否已经下载完毕,避免重复更新
|
||||
if (content.videoUrl && content.videoUrl === videoUrl) {
|
||||
console.log("视频已下载,跳过重复更新:", messageId);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// 设置视频URL并清除加载状态
|
||||
const updatedContent = {
|
||||
...content,
|
||||
videoUrl,
|
||||
isLoading: false,
|
||||
};
|
||||
return {
|
||||
...msg,
|
||||
content: JSON.stringify(updatedContent),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("更新视频URL失败:", e);
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}),
|
||||
}));
|
||||
},
|
||||
clearAllData: () => {
|
||||
set({
|
||||
currentContract: null,
|
||||
currentMessages: [],
|
||||
messagesLoading: false,
|
||||
});
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "wechat-storage",
|
||||
partialize: state => ({
|
||||
// currentContract 不做持久化,登录和页面刷新时直接清空
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// 导出便捷的选择器函数
|
||||
export const useCurrentContact = () =>
|
||||
useWeChatStore(state => state.currentContract);
|
||||
export const useCurrentMessages = () =>
|
||||
useWeChatStore(state => state.currentMessages);
|
||||
export const useMessagesLoading = () =>
|
||||
useWeChatStore(state => state.messagesLoading);
|
||||
@@ -1,7 +0,0 @@
|
||||
import { ChatRecord } from "@/pages/pc/ckbox/data";
|
||||
export interface Messages {
|
||||
friendMessage?: ChatRecord | null;
|
||||
chatroomMessage?: ChatRecord | null;
|
||||
seq: number;
|
||||
cmdType: string;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
//消息管理器
|
||||
import { deepCopy } from "@/utils/common";
|
||||
import { WebSocketMessage } from "./websocket";
|
||||
import { getkfUserList, asyncKfUserList } from "@/store/module/ckchat/ckchat";
|
||||
import { Messages } from "./msg.data";
|
||||
|
||||
import { useWeChatStore } from "@/store/module/weChat/weChat";
|
||||
// 消息处理器类型定义
|
||||
type MessageHandler = (message: WebSocketMessage) => void;
|
||||
const setVideoUrl = useWeChatStore.getState().setVideoUrl;
|
||||
const addMessage = useWeChatStore.getState().addMessage;
|
||||
const receivedMsg = useWeChatStore.getState().receivedMsg;
|
||||
|
||||
// 消息处理器映射
|
||||
const messageHandlers: Record<string, MessageHandler> = {
|
||||
// 微信账号存活状态响应
|
||||
CmdRequestWechatAccountsAliveStatusResp: message => {
|
||||
// console.log("微信账号存活状态响应", message);
|
||||
// 获取客服列表
|
||||
const kfUserList = deepCopy(getkfUserList());
|
||||
const wechatAccountsAliveStatus = message.wechatAccountsAliveStatus || {};
|
||||
// 遍历客服列表,更新存活状态
|
||||
kfUserList.forEach(kfUser => {
|
||||
kfUser.isOnline = wechatAccountsAliveStatus[kfUser.id];
|
||||
});
|
||||
asyncKfUserList(kfUserList);
|
||||
},
|
||||
// 发送消息响应
|
||||
CmdSendMessageResp: message => {
|
||||
console.log("发送消息响应", message);
|
||||
addMessage(message.friendMessage || message.chatroomMessage);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
CmdSendMessageResult: message => {
|
||||
console.log("发送消息结果", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
// 接收消息响应
|
||||
CmdReceiveMessageResp: message => {
|
||||
console.log("接收消息响应", message);
|
||||
addMessage(message.friendMessage || message.chatroomMessage);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
//收到消息
|
||||
CmdNewMessage: (message: Messages) => {
|
||||
// 在这里添加具体的处理逻辑
|
||||
receivedMsg(message.friendMessage || message.chatroomMessage);
|
||||
},
|
||||
CmdFriendInfoChanged: message => {
|
||||
// console.log("好友信息变更", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
|
||||
// 登录响应
|
||||
CmdSignInResp: message => {
|
||||
console.log("登录响应", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
},
|
||||
|
||||
// 通知消息
|
||||
CmdNotify: message => {
|
||||
console.log("通知消息", message);
|
||||
// 在这里添加具体的处理逻辑
|
||||
if (message.notify == "Kicked out") {
|
||||
// 被踢出时直接跳转到登录页面
|
||||
window.location.href = "/login";
|
||||
}
|
||||
},
|
||||
|
||||
CmdDownloadVideoResult: message => {
|
||||
// 在这里添加具体的处理逻辑
|
||||
setVideoUrl(message.friendMessageId, message.url);
|
||||
},
|
||||
|
||||
// 可以继续添加更多处理器...
|
||||
};
|
||||
|
||||
// 默认处理器
|
||||
const defaultHandler: MessageHandler = message => {
|
||||
console.log("未知消息类型", message.cmdType, message);
|
||||
};
|
||||
|
||||
// 注册新的消息处理器
|
||||
export const registerMessageHandler = (
|
||||
cmdType: string,
|
||||
handler: MessageHandler,
|
||||
) => {
|
||||
messageHandlers[cmdType] = handler;
|
||||
};
|
||||
|
||||
// 移除消息处理器
|
||||
export const unregisterMessageHandler = (cmdType: string) => {
|
||||
delete messageHandlers[cmdType];
|
||||
};
|
||||
|
||||
// 获取所有已注册的消息类型
|
||||
export const getRegisteredMessageTypes = (): string[] => {
|
||||
return Object.keys(messageHandlers);
|
||||
};
|
||||
|
||||
// 消息管理核心函数
|
||||
export const msgManageCore = (message: WebSocketMessage) => {
|
||||
const cmdType = message.cmdType;
|
||||
if (!cmdType) {
|
||||
console.warn("消息缺少cmdType字段", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取对应的处理器,如果没有则使用默认处理器
|
||||
const handler = messageHandlers[cmdType] || defaultHandler;
|
||||
|
||||
try {
|
||||
handler(message);
|
||||
} catch (error) {
|
||||
console.error(`处理消息类型 ${cmdType} 时发生错误:`, error);
|
||||
}
|
||||
};
|
||||
@@ -1,586 +0,0 @@
|
||||
import { createPersistStore } from "@/store/createPersistStore";
|
||||
import { Toast } from "antd-mobile";
|
||||
import { useUserStore } from "../user";
|
||||
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
||||
const { getAccountId } = useCkChatStore.getState();
|
||||
import { msgManageCore } from "./msgManage";
|
||||
// WebSocket消息类型
|
||||
export interface WebSocketMessage {
|
||||
cmdType?: string;
|
||||
seq?: number;
|
||||
wechatAccountIds?: string[];
|
||||
content?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// WebSocket连接状态
|
||||
export enum WebSocketStatus {
|
||||
DISCONNECTED = "disconnected",
|
||||
CONNECTING = "connecting",
|
||||
CONNECTED = "connected",
|
||||
RECONNECTING = "reconnecting",
|
||||
ERROR = "error",
|
||||
}
|
||||
|
||||
// WebSocket配置
|
||||
interface WebSocketConfig {
|
||||
url: string;
|
||||
client: string;
|
||||
accountId: number;
|
||||
accessToken: string;
|
||||
autoReconnect: boolean;
|
||||
cmdType: string;
|
||||
seq: number;
|
||||
reconnectInterval: number;
|
||||
maxReconnectAttempts: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface WebSocketState {
|
||||
// 连接状态
|
||||
status: WebSocketStatus;
|
||||
ws: WebSocket | null;
|
||||
|
||||
// 配置信息
|
||||
config: WebSocketConfig | null;
|
||||
|
||||
// 消息相关
|
||||
messages: WebSocketMessage[];
|
||||
unreadCount: number;
|
||||
|
||||
// 重连相关
|
||||
reconnectAttempts: number;
|
||||
reconnectTimer: NodeJS.Timeout | null;
|
||||
aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器
|
||||
|
||||
// 方法
|
||||
connect: (config: Partial<WebSocketConfig>) => void;
|
||||
disconnect: () => void;
|
||||
sendMessage: (message: Omit<WebSocketMessage, "id" | "timestamp">) => void;
|
||||
sendCommand: (cmdType: string, data?: any) => void;
|
||||
clearMessages: () => void;
|
||||
markAsRead: () => void;
|
||||
reconnect: () => void;
|
||||
clearConnectionState: () => void; // 清空连接状态
|
||||
|
||||
// 内部方法
|
||||
_handleOpen: () => void;
|
||||
_handleMessage: (event: MessageEvent) => void;
|
||||
_handleClose: (event: CloseEvent) => void;
|
||||
_handleError: (event: Event) => void;
|
||||
_startReconnectTimer: () => void;
|
||||
_stopReconnectTimer: () => void;
|
||||
_startAliveStatusTimer: () => void; // 启动客服状态查询定时器
|
||||
_stopAliveStatusTimer: () => void; // 停止客服状态查询定时器
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG: WebSocketConfig = {
|
||||
url: "wss://kf.quwanzhi.com:9993",
|
||||
client: "kefu-client",
|
||||
accountId: 0,
|
||||
accessToken: "",
|
||||
autoReconnect: true,
|
||||
cmdType: "", // 添加默认的命令类型
|
||||
seq: +new Date(), // 添加默认的序列号
|
||||
reconnectInterval: 3000,
|
||||
maxReconnectAttempts: 5,
|
||||
};
|
||||
|
||||
export const useWebSocketStore = createPersistStore<WebSocketState>(
|
||||
(set, get) => ({
|
||||
status: WebSocketStatus.DISCONNECTED,
|
||||
ws: null,
|
||||
config: null,
|
||||
messages: [],
|
||||
unreadCount: 0,
|
||||
reconnectAttempts: 0,
|
||||
reconnectTimer: null,
|
||||
aliveStatusTimer: null,
|
||||
|
||||
// 连接WebSocket
|
||||
connect: (config: Partial<WebSocketConfig>) => {
|
||||
const currentState = get();
|
||||
|
||||
// 检查当前连接状态,避免重复连接
|
||||
if (
|
||||
currentState.status === WebSocketStatus.CONNECTED ||
|
||||
currentState.status === WebSocketStatus.CONNECTING
|
||||
) {
|
||||
// console.log("WebSocket已连接或正在连接,跳过重复连接", {
|
||||
// currentStatus: currentState.status,
|
||||
// hasWebSocket: !!currentState.ws,
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经有WebSocket实例,先断开
|
||||
if (currentState.ws) {
|
||||
// console.log("断开现有WebSocket连接");
|
||||
currentState.disconnect();
|
||||
}
|
||||
|
||||
// 合并配置
|
||||
const fullConfig: WebSocketConfig = {
|
||||
...DEFAULT_CONFIG,
|
||||
...config,
|
||||
};
|
||||
|
||||
// 获取用户信息
|
||||
const { token2 } = useUserStore.getState();
|
||||
|
||||
if (!token2) {
|
||||
Toast.show({ content: "未找到有效的访问令牌", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建WebSocket URL
|
||||
const params = new URLSearchParams({
|
||||
client: fullConfig.client.toString(),
|
||||
accountId: getAccountId().toString(),
|
||||
accessToken: token2,
|
||||
t: Date.now().toString(),
|
||||
});
|
||||
|
||||
const wsUrl = fullConfig.url + "?" + params;
|
||||
|
||||
// 检查URL是否为localhost,如果是则不连接
|
||||
if (wsUrl.includes("localhost") || wsUrl.includes("127.0.0.1")) {
|
||||
// console.error("WebSocket连接被拦截:不允许连接到本地地址", wsUrl);
|
||||
Toast.show({
|
||||
content: "WebSocket连接被拦截:不允许连接到本地地址",
|
||||
position: "top",
|
||||
});
|
||||
set({ status: WebSocketStatus.ERROR });
|
||||
return;
|
||||
}
|
||||
|
||||
set({
|
||||
status: WebSocketStatus.CONNECTING,
|
||||
config: fullConfig,
|
||||
});
|
||||
|
||||
try {
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
// 绑定事件处理器
|
||||
ws.onopen = () => get()._handleOpen();
|
||||
ws.onmessage = event => get()._handleMessage(event);
|
||||
ws.onclose = event => get()._handleClose(event);
|
||||
ws.onerror = event => get()._handleError(event);
|
||||
|
||||
set({ ws });
|
||||
|
||||
// console.log("WebSocket连接创建成功", wsUrl);
|
||||
} catch (error) {
|
||||
// console.error("WebSocket连接失败:", error);
|
||||
set({ status: WebSocketStatus.ERROR });
|
||||
Toast.show({ content: "WebSocket连接失败", position: "top" });
|
||||
}
|
||||
},
|
||||
|
||||
// 断开连接
|
||||
disconnect: () => {
|
||||
const currentState = get();
|
||||
|
||||
if (currentState.ws) {
|
||||
currentState.ws.close();
|
||||
}
|
||||
|
||||
currentState._stopReconnectTimer();
|
||||
currentState._stopAliveStatusTimer();
|
||||
|
||||
set({
|
||||
status: WebSocketStatus.DISCONNECTED,
|
||||
ws: null,
|
||||
reconnectAttempts: 0,
|
||||
});
|
||||
|
||||
// console.log("WebSocket连接已断开");
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage: message => {
|
||||
const currentState = get();
|
||||
|
||||
if (
|
||||
currentState.status !== WebSocketStatus.CONNECTED ||
|
||||
!currentState.ws
|
||||
) {
|
||||
Toast.show({ content: "WebSocket未连接", position: "top" });
|
||||
return;
|
||||
}
|
||||
|
||||
const fullMessage: WebSocketMessage = {
|
||||
...message,
|
||||
};
|
||||
|
||||
try {
|
||||
currentState.ws.send(JSON.stringify(fullMessage));
|
||||
// console.log("消息发送成功:", fullMessage);
|
||||
} catch (error) {
|
||||
// console.error("消息发送失败:", error);
|
||||
Toast.show({ content: "消息发送失败", position: "top" });
|
||||
}
|
||||
},
|
||||
|
||||
// 发送命令
|
||||
sendCommand: (cmdType: string, data?: any) => {
|
||||
const currentState = get();
|
||||
|
||||
if (
|
||||
currentState.status !== WebSocketStatus.CONNECTED ||
|
||||
!currentState.ws
|
||||
) {
|
||||
Toast.show({
|
||||
content: "WebSocket未连接,正在重新连接...",
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 重置连接状态并发起重新连接
|
||||
set({ status: WebSocketStatus.DISCONNECTED });
|
||||
if (currentState.config) {
|
||||
currentState.connect(currentState.config);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const command = {
|
||||
cmdType,
|
||||
...data,
|
||||
seq: +new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
currentState.ws.send(JSON.stringify(command));
|
||||
// console.log("命令发送成功:", command);
|
||||
} catch (error) {
|
||||
// console.error("命令发送失败:", error);
|
||||
Toast.show({ content: "命令发送失败", position: "top" });
|
||||
|
||||
// 发送失败时也尝试重新连接
|
||||
set({ status: WebSocketStatus.DISCONNECTED });
|
||||
if (currentState.config) {
|
||||
currentState.connect(currentState.config);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 清除消息
|
||||
clearMessages: () => {
|
||||
set({ messages: [], unreadCount: 0 });
|
||||
},
|
||||
|
||||
// 标记为已读
|
||||
markAsRead: () => {
|
||||
set({ unreadCount: 0 });
|
||||
},
|
||||
|
||||
// 重连
|
||||
reconnect: () => {
|
||||
const currentState = get();
|
||||
|
||||
if (currentState.config) {
|
||||
// 检查是否允许重连
|
||||
if (!currentState.config.autoReconnect) {
|
||||
// console.log("自动重连已禁用,不再尝试重连");
|
||||
return;
|
||||
}
|
||||
currentState.connect(currentState.config);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空连接状态(用于退出登录时)
|
||||
clearConnectionState: () => {
|
||||
const currentState = get();
|
||||
|
||||
// 断开现有连接
|
||||
if (currentState.ws) {
|
||||
currentState.ws.close();
|
||||
}
|
||||
|
||||
// 停止所有定时器
|
||||
currentState._stopReconnectTimer();
|
||||
currentState._stopAliveStatusTimer();
|
||||
|
||||
// 重置所有状态
|
||||
set({
|
||||
status: WebSocketStatus.DISCONNECTED,
|
||||
ws: null,
|
||||
config: null,
|
||||
messages: [],
|
||||
unreadCount: 0,
|
||||
reconnectAttempts: 0,
|
||||
reconnectTimer: null,
|
||||
aliveStatusTimer: null,
|
||||
});
|
||||
|
||||
// console.log("WebSocket连接状态已清空");
|
||||
},
|
||||
|
||||
// 内部方法:处理连接打开
|
||||
_handleOpen: () => {
|
||||
const currentState = get();
|
||||
|
||||
set({
|
||||
status: WebSocketStatus.CONNECTED,
|
||||
reconnectAttempts: 0,
|
||||
});
|
||||
|
||||
// console.log("WebSocket连接成功");
|
||||
const { token2 } = useUserStore.getState();
|
||||
// 发送登录命令
|
||||
if (currentState.config) {
|
||||
currentState.sendCommand("CmdSignIn", {
|
||||
accessToken: token2,
|
||||
accountId: Number(getAccountId()),
|
||||
client: currentState.config?.client || "kefu-client",
|
||||
seq: +new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
Toast.show({ content: "WebSocket连接成功", position: "top" });
|
||||
|
||||
// 启动客服状态查询定时器
|
||||
currentState._startAliveStatusTimer();
|
||||
},
|
||||
|
||||
// 内部方法:处理消息接收
|
||||
_handleMessage: (event: MessageEvent) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
// console.log("收到WebSocket消息:", data);
|
||||
|
||||
// 处理特定的通知消息
|
||||
if (data.cmdType === "CmdNotify") {
|
||||
// 处理Auth failed通知
|
||||
if (data.notify === "Auth failed" || data.notify === "Kicked out") {
|
||||
// console.error(`WebSocket ${data.notify},断开连接`);
|
||||
Toast.show({
|
||||
content: `WebSocket ${data.notify},断开连接`,
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 禁用自动重连
|
||||
if (get().config) {
|
||||
set({
|
||||
config: {
|
||||
...get().config!,
|
||||
autoReconnect: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 停止客服状态查询定时器
|
||||
get()._stopAliveStatusTimer();
|
||||
|
||||
// 断开连接
|
||||
get().disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const currentState = get();
|
||||
const newMessage: WebSocketMessage = {
|
||||
id: Date.now().toString(),
|
||||
type: data.type || "message",
|
||||
content: data,
|
||||
timestamp: Date.now(),
|
||||
sender: data.sender,
|
||||
receiver: data.receiver,
|
||||
};
|
||||
|
||||
set({
|
||||
messages: [...currentState.messages, newMessage],
|
||||
unreadCount: currentState.unreadCount + 1,
|
||||
});
|
||||
//消息处理器
|
||||
msgManageCore(data);
|
||||
|
||||
// 可以在这里添加消息处理逻辑
|
||||
// 比如播放提示音、显示通知等
|
||||
} catch (error) {
|
||||
// console.error("解析WebSocket消息失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 内部方法:处理连接关闭
|
||||
_handleClose: (event: CloseEvent) => {
|
||||
const currentState = get();
|
||||
|
||||
// console.log("WebSocket连接关闭:", event.code, event.reason);
|
||||
|
||||
set({
|
||||
status: WebSocketStatus.DISCONNECTED,
|
||||
ws: null,
|
||||
});
|
||||
|
||||
// 自动重连逻辑
|
||||
if (
|
||||
currentState.config?.autoReconnect &&
|
||||
currentState.reconnectAttempts <
|
||||
(currentState.config?.maxReconnectAttempts || 5)
|
||||
) {
|
||||
// console.log("尝试自动重连...");
|
||||
currentState._startReconnectTimer();
|
||||
} else if (!currentState.config?.autoReconnect) {
|
||||
// console.log("自动重连已禁用,不再尝试重连");
|
||||
// 重置重连计数
|
||||
set({ reconnectAttempts: 0 });
|
||||
}
|
||||
},
|
||||
|
||||
// 内部方法:处理连接错误
|
||||
_handleError: (event: Event) => {
|
||||
// console.error("WebSocket连接错误:", event);
|
||||
|
||||
set({ status: WebSocketStatus.ERROR });
|
||||
|
||||
Toast.show({ content: "WebSocket连接错误", position: "top" });
|
||||
},
|
||||
|
||||
// 内部方法:启动重连定时器
|
||||
_startReconnectTimer: () => {
|
||||
const currentState = get();
|
||||
|
||||
currentState._stopReconnectTimer();
|
||||
|
||||
set({
|
||||
status: WebSocketStatus.RECONNECTING,
|
||||
reconnectAttempts: currentState.reconnectAttempts + 1,
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
// console.log(
|
||||
// `尝试重连 (${currentState.reconnectAttempts + 1}/${currentState.config?.maxReconnectAttempts})`,
|
||||
// );
|
||||
currentState.reconnect();
|
||||
}, currentState.config?.reconnectInterval || 3000);
|
||||
|
||||
set({ reconnectTimer: timer });
|
||||
},
|
||||
|
||||
// 内部方法:停止重连定时器
|
||||
_stopReconnectTimer: () => {
|
||||
const currentState = get();
|
||||
|
||||
if (currentState.reconnectTimer) {
|
||||
clearTimeout(currentState.reconnectTimer);
|
||||
set({ reconnectTimer: null });
|
||||
}
|
||||
},
|
||||
|
||||
// 内部方法:启动客服状态查询定时器
|
||||
_startAliveStatusTimer: () => {
|
||||
const currentState = get();
|
||||
|
||||
// 先停止现有定时器
|
||||
currentState._stopAliveStatusTimer();
|
||||
|
||||
// 获取客服用户列表
|
||||
const { kfUserList } = useCkChatStore.getState();
|
||||
|
||||
// 如果没有客服用户,不启动定时器
|
||||
if (!kfUserList || kfUserList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动定时器,每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),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 如果连接断开,停止定时器
|
||||
state._stopAliveStatusTimer();
|
||||
}
|
||||
}, 5 * 1000);
|
||||
|
||||
set({ aliveStatusTimer: timer });
|
||||
},
|
||||
|
||||
// 内部方法:停止客服状态查询定时器
|
||||
_stopAliveStatusTimer: () => {
|
||||
const currentState = get();
|
||||
|
||||
if (currentState.aliveStatusTimer) {
|
||||
clearInterval(currentState.aliveStatusTimer);
|
||||
set({ aliveStatusTimer: null });
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "websocket-store",
|
||||
partialize: state => ({
|
||||
// 只持久化必要的状态,不持久化WebSocket实例
|
||||
status: state.status,
|
||||
config: state.config,
|
||||
messages: state.messages.slice(-100), // 只保留最近100条消息
|
||||
unreadCount: state.unreadCount,
|
||||
reconnectAttempts: state.reconnectAttempts,
|
||||
// 注意:定时器不需要持久化,重新连接时会重新创建
|
||||
}),
|
||||
onRehydrateStorage: () => state => {
|
||||
// 页面刷新后,如果之前是连接状态,尝试重新连接
|
||||
if (state && state.status === WebSocketStatus.CONNECTED && state.config) {
|
||||
// console.log("页面刷新后恢复WebSocket连接", {
|
||||
// persistedConfig: state.config,
|
||||
// currentDefaultConfig: DEFAULT_CONFIG,
|
||||
// });
|
||||
|
||||
// 使用最新的默认配置,而不是持久化的配置
|
||||
const freshConfig = {
|
||||
...DEFAULT_CONFIG,
|
||||
client: state.config.client,
|
||||
accountId: state.config.accountId,
|
||||
accessToken: state.config.accessToken,
|
||||
autoReconnect: state.config.autoReconnect,
|
||||
};
|
||||
|
||||
// console.log("使用刷新后的配置重连:", freshConfig);
|
||||
|
||||
// 延迟一下再重连,确保页面完全加载
|
||||
// 同时检查当前状态,避免重复连接
|
||||
setTimeout(() => {
|
||||
// 重新获取最新的状态,而不是使用闭包中的state
|
||||
const currentState = useWebSocketStore.getState();
|
||||
// console.log("页面刷新后检查状态", {
|
||||
// status: currentState.status,
|
||||
// hasWs: !!currentState.ws,
|
||||
// });
|
||||
|
||||
// 强制重置状态为disconnected,因为页面刷新后WebSocket实例已失效
|
||||
if (
|
||||
currentState.status === WebSocketStatus.CONNECTED &&
|
||||
!currentState.ws
|
||||
) {
|
||||
// console.log("检测到状态不一致,重置为disconnected");
|
||||
useWebSocketStore.setState({
|
||||
status: WebSocketStatus.DISCONNECTED,
|
||||
});
|
||||
}
|
||||
|
||||
// 重新获取状态进行连接
|
||||
const latestState = useWebSocketStore.getState();
|
||||
if (
|
||||
latestState.status === WebSocketStatus.DISCONNECTED ||
|
||||
latestState.status === WebSocketStatus.ERROR
|
||||
) {
|
||||
// console.log("页面刷新后开始重连");
|
||||
latestState.connect(freshConfig);
|
||||
} else {
|
||||
// console.log("WebSocket已连接或正在连接,跳过页面刷新重连", {
|
||||
// status: latestState.status,
|
||||
// });
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user