diff --git a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx index 3be617b5..a5a3b29d 100644 --- a/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx +++ b/Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx @@ -21,7 +21,11 @@ import { } from "@ant-design/icons"; import Layout from "@/components/Layout/Layout"; import style from "./detail.module.scss"; -import { getWechatAccountDetail, getWechatFriends, transferWechatFriends } from "./api"; +import { + getWechatAccountDetail, + getWechatFriends, + transferWechatFriends, +} from "./api"; import DeviceSelection from "@/components/DeviceSelection"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; @@ -36,7 +40,9 @@ const WechatAccountDetail: React.FC = () => { const [accountInfo, setAccountInfo] = useState(null); const [showRestrictions, setShowRestrictions] = useState(false); const [showTransferConfirm, setShowTransferConfirm] = useState(false); - const [selectedDevices, setSelectedDevices] = useState([]); + const [selectedDevices, setSelectedDevices] = useState( + [], + ); const [inheritInfo, setInheritInfo] = useState(true); const [transferLoading, setTransferLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); @@ -216,7 +222,7 @@ const WechatAccountDetail: React.FC = () => { await transferWechatFriends({ wechatId: id, devices: selectedDevices.map(device => device.id), - inherit: inheritInfo + inherit: inheritInfo, }); Toast.show({ @@ -608,10 +614,7 @@ const WechatAccountDetail: React.FC = () => {
同步原有信息
- + {inheritInfo ? "是" : "否"} diff --git a/Touchkebao/src/api/request.ts b/Touchkebao/src/api/request.ts index 6394d22e..adfa7ea5 100644 --- a/Touchkebao/src/api/request.ts +++ b/Touchkebao/src/api/request.ts @@ -28,10 +28,20 @@ instance.interceptors.request.use((config: any) => { instance.interceptors.response.use( (res: AxiosResponse) => { - const { code, success, msg } = res.data || {}; - if (code === 200 || success) { - return res.data.data ?? res.data; + const payload = res.data || {}; + const { code, success, msg } = payload; + const hasBizCode = typeof code === "number"; + const hasBizSuccess = typeof success === "boolean"; + const bizSuccess = hasBizCode + ? code === 200 + : hasBizSuccess + ? success + : undefined; + + if (bizSuccess === true || (!hasBizCode && !hasBizSuccess)) { + return payload.data ?? payload; } + Toast.show({ content: msg || "接口错误", position: "top" }); if (code === 401) { localStorage.removeItem("token"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx index 1b4d86f7..6249df70 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx @@ -6,11 +6,14 @@ import { WechatFriendAllot, WechatFriendRebackAllot, } from "@/pages/pc/ckbox/weChat/api"; +import { dataProcessing } from "@/api/ai"; import { useCurrentContact } from "@/store/module/weChat/weChat"; import { ContactManager } from "@/utils/dbAction/contact"; import { MessageManager } from "@/utils/dbAction/message"; import { useUserStore } from "@/store/module/user"; import { useWeChatStore } from "@/store/module/weChat/weChat"; +import { useMessageStore } from "@weChatStore/message"; +import { useContactStore } from "@weChatStore/contacts"; const { TextArea } = Input; const { Option } = Select; @@ -37,6 +40,8 @@ const ToContract: React.FC = ({ const clearCurrentContact = useWeChatStore( state => state.clearCurrentContact, ); + const removeSessionById = useMessageStore(state => state.removeSessionById); + const deleteContact = useContactStore(state => state.deleteContact); const [visible, setVisible] = useState(false); const [selectedTarget, setSelectedTarget] = useState(null); const [comment, setComment] = useState(""); @@ -79,6 +84,12 @@ const ToContract: React.FC = ({ notifyReceiver: true, comment: comment.trim(), }); + dataProcessing({ + type: "CmdAllotFriend", + wechatChatroomId: currentContact.id, + toAccountId: selectedTarget as number, + wechatAccountId: currentContact.wechatAccountId, + }); } else { await WechatFriendAllot({ wechatFriendId: currentContact.id, @@ -86,6 +97,12 @@ const ToContract: React.FC = ({ notifyReceiver: true, comment: comment.trim(), }); + dataProcessing({ + type: "CmdAllotFriend", + wechatFriendId: currentContact.id, + toAccountId: selectedTarget as number, + wechatAccountId: currentContact.wechatAccountId, + }); } } @@ -97,7 +114,10 @@ const ToContract: React.FC = ({ const currentUserId = useUserStore.getState().user?.id || 0; const contactType = "chatroomId" in currentContact ? "group" : "friend"; - // 1. 从会话列表数据库删除 + // 1. 立即从Store中删除会话(更新UI) + removeSessionById(currentContact.id, contactType); + + // 2. 从会话列表数据库删除 await MessageManager.deleteSession( currentUserId, currentContact.id, @@ -105,11 +125,19 @@ const ToContract: React.FC = ({ ); console.log("✅ 已从会话列表删除"); - // 2. 从联系人数据库删除 + // 3. 从联系人数据库删除 await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 清空当前选中的联系人(关闭聊天窗口) + // 4. 从联系人Store中删除(更新联系人列表UI) + try { + await deleteContact(currentContact.id); + console.log("✅ 已从联系人列表Store删除"); + } catch (error) { + console.error("从联系人Store删除失败:", error); + } + + // 5. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转接成功,已清理本地数据"); @@ -151,7 +179,10 @@ const ToContract: React.FC = ({ const currentUserId = useUserStore.getState().user?.id || 0; const contactType = "chatroomId" in currentContact ? "group" : "friend"; - // 1. 从会话列表数据库删除 + // 1. 立即从Store中删除会话(更新UI) + removeSessionById(currentContact.id, contactType); + + // 2. 从会话列表数据库删除 await MessageManager.deleteSession( currentUserId, currentContact.id, @@ -159,11 +190,19 @@ const ToContract: React.FC = ({ ); console.log("✅ 已从会话列表删除"); - // 2. 从联系人数据库删除 + // 3. 从联系人数据库删除 await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 清空当前选中的联系人(关闭聊天窗口) + // 4. 从联系人Store中删除(更新联系人列表UI) + try { + await deleteContact(currentContact.id); + console.log("✅ 已从联系人列表Store删除"); + } catch (error) { + console.error("从联系人Store删除失败:", error); + } + + // 5. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转回成功,已清理本地数据"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx index 514f9957..7c812bb8 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/QuickWords/index.tsx @@ -502,13 +502,10 @@ const QuickWords: React.FC = ({ onInsert }) => { key: QuickWordsType.PERSONAL.toString(), label: "个人快捷语", }, - { - key: QuickWordsType.PUBLIC.toString(), - label: "公共快捷语", - }, + { key: QuickWordsType.DEPARTMENT.toString(), - label: "部门快捷语", + label: "公司快捷语", }, ]} /> diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss index 77c9db24..802ee819 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/com.module.scss @@ -188,6 +188,37 @@ text-align: center; } +// 加载容器样式 +.loadingContainer { + height: 100%; + display: flex; + align-items: flex-start; + justify-content: center; + min-height: 400px; + position: relative; + padding-top: 40px; + + :global(.ant-spin-container) { + width: 100%; + } + + :global(.ant-spin-spinning) { + position: relative; + } + + :global(.ant-spin-text) { + color: #1890ff; + font-size: 14px; + margin-top: 12px; + } +} + +.loadingContent { + width: 100%; + height: 100%; + opacity: 0.6; +} + // 骨架屏样式 .skeletonContainer { padding: 10px; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx index 0e39a94b..2d187623 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/SidebarMenu/MessageList/index.tsx @@ -1,5 +1,14 @@ import React, { useEffect, useState, useRef } from "react"; -import { List, Avatar, Badge, Modal, Input, message, Skeleton } from "antd"; +import { + List, + Avatar, + Badge, + Modal, + Input, + message, + Skeleton, + Spin, +} from "antd"; import { UserOutlined, TeamOutlined, @@ -74,6 +83,7 @@ const MessageList: React.FC = () => { const contextMenuRef = useRef(null); const previousUserIdRef = useRef(null); const loadRequestRef = useRef(0); + const autoClickRef = useRef(false); // 右键菜单事件处理 const handleContextMenu = (e: React.MouseEvent, session: ChatSession) => { @@ -370,6 +380,7 @@ const MessageList: React.FC = () => { previousUserIdRef.current = currentUserId; setHasLoadedOnce(false); setSessionState([]); + autoClickRef.current = false; // 重置自动点击标记 }, [currentUserId, setHasLoadedOnce, setSessionState]); // 初始化加载会话列表 @@ -383,7 +394,7 @@ const MessageList: React.FC = () => { const requestId = ++loadRequestRef.current; const initializeSessions = async () => { - // setLoading(true); + setLoading(true); try { const cachedSessions = @@ -416,7 +427,7 @@ const MessageList: React.FC = () => { } } finally { if (!isCancelled && loadRequestRef.current === requestId) { - // setLoading(false); + setLoading(false); } } }; @@ -447,25 +458,99 @@ const MessageList: React.FC = () => { // 根据客服和搜索关键词筛选会话 useEffect(() => { - let filtered = [...sessions]; + const filterSessions = async () => { + let filtered = [...sessions]; - // 根据当前选中的客服筛选 - if (currentCustomer && currentCustomer.id !== 0) { - filtered = filtered.filter(v => v.wechatAccountId === currentCustomer.id); + // 根据当前选中的客服筛选 + if (currentCustomer && currentCustomer.id !== 0) { + filtered = filtered.filter( + v => v.wechatAccountId === currentCustomer.id, + ); + } + + // 根据搜索关键词进行模糊匹配(支持搜索昵称、备注名、微信号) + if (searchKeyword.trim()) { + const keyword = searchKeyword.toLowerCase(); + + // 如果搜索关键词可能是微信号,需要从联系人表补充 wechatId + const sessionsNeedingWechatId = filtered.filter( + v => !v.wechatId && v.type === "friend", + ); + + // 批量从联系人表获取 wechatId + if (sessionsNeedingWechatId.length > 0) { + const contactPromises = sessionsNeedingWechatId.map(session => + ContactManager.getContactByIdAndType( + currentUserId, + session.id, + session.type, + ), + ); + const contacts = await Promise.all(contactPromises); + + // 补充 wechatId 到会话数据 + contacts.forEach((contact, index) => { + if (contact && contact.wechatId) { + const session = sessionsNeedingWechatId[index]; + const sessionIndex = filtered.findIndex( + s => s.id === session.id && s.type === session.type, + ); + if (sessionIndex !== -1) { + filtered[sessionIndex] = { + ...filtered[sessionIndex], + wechatId: contact.wechatId, + }; + } + } + }); + } + + filtered = filtered.filter(v => { + const nickname = (v.nickname || "").toLowerCase(); + const conRemark = (v.conRemark || "").toLowerCase(); + const wechatId = (v.wechatId || "").toLowerCase(); + return ( + nickname.includes(keyword) || + conRemark.includes(keyword) || + wechatId.includes(keyword) + ); + }); + } + + setFilteredSessions(filtered); + }; + + filterSessions(); + }, [sessions, currentCustomer, searchKeyword, currentUserId]); + + // 渲染完毕后自动点击第一个聊天记录 + useEffect(() => { + // 只在以下条件满足时自动点击: + // 1. 不在加载状态 + // 2. 有过滤后的会话列表 + // 3. 当前没有选中的联系人 + // 4. 还没有自动点击过 + // 5. 不在搜索状态(避免搜索时自动切换) + if ( + !loading && + filteredSessions.length > 0 && + !currentContract && + !autoClickRef.current && + !searchKeyword.trim() + ) { + // 延迟一点时间确保DOM已渲染 + const timer = setTimeout(() => { + const firstSession = filteredSessions[0]; + if (firstSession) { + autoClickRef.current = true; + onContactClick(firstSession); + } + }, 100); + + return () => clearTimeout(timer); } - - // 根据搜索关键词进行模糊匹配 - if (searchKeyword.trim()) { - const keyword = searchKeyword.toLowerCase(); - filtered = filtered.filter(v => { - const nickname = (v.nickname || "").toLowerCase(); - const conRemark = (v.conRemark || "").toLowerCase(); - return nickname.includes(keyword) || conRemark.includes(keyword); - }); - } - - setFilteredSessions(filtered); - }, [sessions, currentCustomer, searchKeyword]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loading, filteredSessions, currentContract, searchKeyword]); // ==================== WebSocket消息处理 ==================== @@ -735,11 +820,20 @@ const MessageList: React.FC = () => {
); + // 渲染加载中状态(带旋转动画) + const renderLoading = () => ( +
+ +
{renderSkeleton()}
+
+
+ ); + return (
{loading ? ( - // 加载状态:显示骨架屏 - renderSkeleton() + // 加载状态:显示加载动画和骨架屏 + renderLoading() ) : ( <> {
) : (
-
- -

欢迎使用触客宝

-

选择一个联系人开始聊天

-
+
)} diff --git a/Touchkebao/src/store/module/websocket/msgManage.ts b/Touchkebao/src/store/module/websocket/msgManage.ts index 32666c59..a7b11732 100644 --- a/Touchkebao/src/store/module/websocket/msgManage.ts +++ b/Touchkebao/src/store/module/websocket/msgManage.ts @@ -157,7 +157,7 @@ const messageHandlers: Record = { CmdNotify: async (message: WebSocketMessage) => { console.log("通知消息", message); // 在这里添加具体的处理逻辑 - if (message.notify == "Auth failed") { + if (["Auth failed", "Kicked out"].includes(message.notify)) { // 避免重复弹窗 if ((window as any).__CKB_AUTH_FAILED_SHOWN__) { return; diff --git a/Touchkebao/src/store/module/websocket/websocket.ts b/Touchkebao/src/store/module/websocket/websocket.ts index 59a2fcf3..912daf48 100644 --- a/Touchkebao/src/store/module/websocket/websocket.ts +++ b/Touchkebao/src/store/module/websocket/websocket.ts @@ -351,36 +351,6 @@ export const useWebSocketStore = createPersistStore( _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 = { diff --git a/Touchkebao/src/utils/dbAction/contact.ts b/Touchkebao/src/utils/dbAction/contact.ts index 661d827e..77371a2b 100644 --- a/Touchkebao/src/utils/dbAction/contact.ts +++ b/Touchkebao/src/utils/dbAction/contact.ts @@ -40,6 +40,7 @@ export class ContactManager { /** * 搜索联系人 + * 支持搜索昵称、备注名、微信号 */ static async searchContacts( userId: number, @@ -52,8 +53,11 @@ export class ContactManager { return contacts.filter(contact => { const nickname = (contact.nickname || "").toLowerCase(); const conRemark = (contact.conRemark || "").toLowerCase(); + const wechatId = (contact.wechatId || "").toLowerCase(); return ( - nickname.includes(lowerKeyword) || conRemark.includes(lowerKeyword) + nickname.includes(lowerKeyword) || + conRemark.includes(lowerKeyword) || + wechatId.includes(lowerKeyword) ); }); } catch (error) { diff --git a/Touchkebao/src/utils/dbAction/message.ts b/Touchkebao/src/utils/dbAction/message.ts index e34041b9..6c534670 100644 --- a/Touchkebao/src/utils/dbAction/message.ts +++ b/Touchkebao/src/utils/dbAction/message.ts @@ -231,6 +231,8 @@ export class MessageManager { "phone", "region", "extendFields", + "wechatId", // 添加wechatId比较 + "alias", // 添加alias比较 ]; for (const field of fieldsToCompare) {