diff --git a/Cunkebao/dist/.vite/manifest.json b/Cunkebao/dist/.vite/manifest.json
index aa2b3d0b..90e573c1 100644
--- a/Cunkebao/dist/.vite/manifest.json
+++ b/Cunkebao/dist/.vite/manifest.json
@@ -1,18 +1,14 @@
{
- "_charts-DKSCc2_C.js": {
- "file": "assets/charts-DKSCc2_C.js",
+ "_charts-BET_YNJb.js": {
+ "file": "assets/charts-BET_YNJb.js",
"name": "charts",
"imports": [
- "_ui-DhAz00L0.js",
+ "_ui-BSfOMVFg.js",
"_vendor-2vc8h_ct.js"
]
},
- "_ui-D0C0OGrH.css": {
- "file": "assets/ui-D0C0OGrH.css",
- "src": "_ui-D0C0OGrH.css"
- },
- "_ui-DhAz00L0.js": {
- "file": "assets/ui-DhAz00L0.js",
+ "_ui-BSfOMVFg.js": {
+ "file": "assets/ui-BSfOMVFg.js",
"name": "ui",
"imports": [
"_vendor-2vc8h_ct.js"
@@ -21,6 +17,10 @@
"assets/ui-D0C0OGrH.css"
]
},
+ "_ui-D0C0OGrH.css": {
+ "file": "assets/ui-D0C0OGrH.css",
+ "src": "_ui-D0C0OGrH.css"
+ },
"_utils-6WF66_dS.js": {
"file": "assets/utils-6WF66_dS.js",
"name": "utils",
@@ -33,18 +33,18 @@
"name": "vendor"
},
"index.html": {
- "file": "assets/index-BdCPAYQ7.js",
+ "file": "assets/index-DX2o9_TA.js",
"name": "index",
"src": "index.html",
"isEntry": true,
"imports": [
"_vendor-2vc8h_ct.js",
"_utils-6WF66_dS.js",
- "_ui-DhAz00L0.js",
- "_charts-DKSCc2_C.js"
+ "_ui-BSfOMVFg.js",
+ "_charts-BET_YNJb.js"
],
"css": [
- "assets/index-ChiFk16x.css"
+ "assets/index-DwDrBOQB.css"
]
}
}
\ No newline at end of file
diff --git a/Cunkebao/dist/index.html b/Cunkebao/dist/index.html
index 0b6673bb..d9e4faec 100644
--- a/Cunkebao/dist/index.html
+++ b/Cunkebao/dist/index.html
@@ -11,13 +11,13 @@
-
+
-
-
+
+
-
+
diff --git a/Cunkebao/src/pages/login/Login.tsx b/Cunkebao/src/pages/login/Login.tsx
index d375422a..972710b2 100644
--- a/Cunkebao/src/pages/login/Login.tsx
+++ b/Cunkebao/src/pages/login/Login.tsx
@@ -7,6 +7,7 @@ import {
UserOutline,
} from "antd-mobile-icons";
import { useUserStore } from "@/store/module/user";
+import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { loginWithPassword, loginWithCode, sendVerificationCode } from "./api";
import style from "./login.module.scss";
@@ -75,6 +76,8 @@ const Login: React.FC = () => {
response.then(res => {
const { member, kefuData, deviceTotal } = res;
+ // 清空WebSocket连接状态
+ useWebSocketStore.getState().clearConnectionState();
login(res.token, member, deviceTotal);
const { self, token } = kefuData;
login2(token.access_token);
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts b/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts
index 615cb7ff..ddc48d91 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/list/api.ts
@@ -5,7 +5,7 @@ import {
UpdateLikeTaskData,
LikeRecord,
PaginatedResponse,
-} from "@/pages/workspace/auto-like/record/data";
+} from "@/pages/mobile/workspace/auto-like/record/data";
// 获取自动点赞任务列表
export function fetchAutoLikeTasks(
@@ -36,7 +36,7 @@ export function deleteAutoLikeTask(id: string): Promise {
// 切换任务状态
export function toggleAutoLikeTask(data): Promise {
- return request("/v1/workbench/update-status", { ...data, type: 1 }, "POST");
+ return request("/v1/workbench/update-status", { ...data }, "POST");
}
// 复制自动点赞任务
diff --git a/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx
index 073603ee..a967f725 100644
--- a/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx
+++ b/Cunkebao/src/pages/mobile/workspace/auto-like/list/index.tsx
@@ -201,8 +201,7 @@ const AutoLike: React.FC = () => {
// 切换任务状态
const toggleTaskStatus = async (id: string, status: number) => {
try {
- const newStatus = status === 1 ? "2" : "1";
- await toggleAutoLikeTask(id, newStatus);
+ await toggleAutoLikeTask({ id });
Toast.show({
content: status === 1 ? "已暂停" : "已启动",
position: "top",
diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts
index 1d27fdb0..daa8a79a 100644
--- a/Cunkebao/src/pages/pc/ckbox/api.ts
+++ b/Cunkebao/src/pages/pc/ckbox/api.ts
@@ -22,8 +22,13 @@ export function WechatGroup(params) {
export function clearUnreadCount(params) {
return request("/api/WechatFriend/clearUnreadCount", params, "PUT");
}
+
+//更新配置
+export function updateConfig(params) {
+ return request("/api/WechatFriend/updateConfig", params, "PUT");
+}
//获取聊天记录-2 获取列表
-export function getMessages(params: {
+export function getChatMessages(params: {
wechatAccountId: number;
wechatFriendId?: number;
wechatChatroomId?: number;
@@ -73,19 +78,6 @@ export const getControlTerminalList = params => {
return request("/api/wechataccount", params, "GET");
};
-// 搜索联系人
-export const getChatMessage = (params: {
- wechatAccountId: number;
- wechatFriendId: number;
- From: number;
- To: number;
- Count: number;
- olderData: boolean;
- keyword: string;
-}) => {
- return request("/api/FriendMessage/SearchMessage", params, "GET");
-};
-
// 获取聊天历史
export const getChatHistory = (
chatId: string,
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
index c2515179..917988e3 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/ChatWindow.module.scss
@@ -128,128 +128,6 @@
}
}
-.chatFooter {
- background: #fff;
- border-top: 1px solid #f0f0f0;
- padding: 0;
- height: auto;
- min-height: auto;
- flex-shrink: 0;
-
- .inputContainer {
- .inputToolbar {
- display: flex;
- border-bottom: 1px solid #f0f0f0;
- background: #fafafa;
- justify-content: space-between;
-
- .leftTool {
- display: flex;
- gap: 4px;
- }
-
- .rightTool {
- display: flex;
- gap: 8px;
- padding: 8px;
- }
-
- .toolbarButton {
- color: #666;
- border: none;
- padding: 8px;
- border-radius: 4px;
- font-size: 18px;
- width: 36px;
- height: 36px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: transparent;
- transition: all 0.2s;
-
- &:hover {
- color: #1890ff;
- background: #e6f7ff;
- }
-
- &:active {
- background: #bae7ff;
- }
- }
- }
-
- .inputArea {
- display: flex;
- padding: 12px 16px;
- gap: 8px;
- align-items: flex-end;
- background: #fff;
-
- .messageInput {
- flex: 1;
- border: 1px solid #d9d9d9;
- border-radius: 4px;
- resize: none;
- padding: 8px 12px;
- font-size: 14px;
- line-height: 1.5;
- min-height: 36px;
- max-height: 120px;
- background: #fff;
- transition: all 0.2s;
-
- &:focus {
- border-color: #1890ff;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- outline: none;
- }
-
- &::placeholder {
- color: #bfbfbf;
- }
- }
-
- .sendButton {
- border-radius: 4px;
- height: 36px;
- padding: 0 16px;
- font-size: 14px;
- font-weight: 500;
- background: #1890ff;
- border: 1px solid #1890ff;
- color: #fff;
- transition: all 0.2s;
-
- &:hover {
- background: #40a9ff;
- border-color: #40a9ff;
- }
-
- &:active {
- background: #096dd9;
- border-color: #096dd9;
- }
-
- &:disabled {
- background: #f5f5f5;
- border-color: #d9d9d9;
- color: #bfbfbf;
- cursor: not-allowed;
- }
- }
- }
-
- .inputHint {
- padding: 4px 16px 8px;
- font-size: 12px;
- color: #8c8c8c;
- background: #fff;
- border-top: 1px solid #f0f0f0;
- }
- }
-}
-
// 右侧个人资料卡片
.profileSider {
background: #fff;
@@ -748,19 +626,6 @@
}
}
- .chatFooter {
- padding: 12px;
-
- .inputContainer {
- .inputArea {
- .sendButton {
- height: 28px;
- padding: 0 12px;
- }
- }
- }
- }
-
.messageItem {
.messageContent {
max-width: 85%;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss
new file mode 100644
index 00000000..36977bd1
--- /dev/null
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/MessageEnter.module.scss
@@ -0,0 +1,181 @@
+// MessageEnter 组件样式 - 微信风格
+.chatFooter {
+ background: #f7f7f7;
+ border-top: 1px solid #e1e1e1;
+ padding: 0;
+ height: auto;
+ min-height: 100px;
+}
+
+.inputContainer {
+ padding: 8px 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.inputToolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 4px 0;
+ border-bottom: none;
+}
+
+.leftTool {
+ display: flex;
+ gap: 2px;
+ align-items: center;
+}
+
+.toolbarButton {
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ color: #666;
+ font-size: 16px;
+ transition: all 0.15s;
+ border: none;
+ background: transparent;
+
+ &:hover {
+ background: #e6e6e6;
+ color: #333;
+ }
+
+ &:active {
+ background: #d9d9d9;
+ }
+}
+
+.rightTool {
+ display: flex;
+ gap: 12px;
+ align-items: center;
+}
+
+.rightToolItem {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ color: #666;
+ font-size: 11px;
+ cursor: pointer;
+ padding: 3px 6px;
+ border-radius: 3px;
+ transition: all 0.15s;
+
+ &:hover {
+ background: #e6e6e6;
+ color: #333;
+ }
+}
+
+.inputArea {
+ display: flex;
+ flex-direction: column;
+ padding: 4px 0;
+}
+
+.inputWrapper {
+ border: 1px solid #d1d1d1;
+ border-radius: 4px;
+ background: #fff;
+ overflow: hidden;
+
+ &:focus-within {
+ border-color: #07c160;
+ }
+}
+
+.messageInput {
+ width: 100%;
+ border: none;
+ resize: none;
+ font-size: 13px;
+ line-height: 1.4;
+ padding: 8px 10px;
+ background: transparent;
+
+ &:focus {
+ box-shadow: none;
+ outline: none;
+ }
+
+ &::placeholder {
+ color: #b3b3b3;
+ }
+}
+
+.sendButtonArea {
+ padding: 8px 10px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.sendButton {
+ height: 32px;
+ border-radius: 4px;
+ font-weight: normal;
+ min-width: 60px;
+ font-size: 13px;
+ background: #07c160;
+ border-color: #07c160;
+
+ &:hover {
+ background: #06ad56;
+ border-color: #06ad56;
+ }
+
+ &:active {
+ background: #059748;
+ border-color: #059748;
+ }
+
+ &:disabled {
+ background: #b3b3b3;
+ border-color: #b3b3b3;
+ opacity: 1;
+ }
+}
+
+.inputHint {
+ font-size: 11px;
+ color: #999;
+ text-align: right;
+ margin-top: 2px;
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+ .inputContainer {
+ padding: 8px 12px;
+ }
+
+ .inputToolbar {
+ flex-wrap: wrap;
+ gap: 8px;
+ }
+
+ .rightTool {
+ gap: 8px;
+ }
+
+ .rightToolItem {
+ font-size: 11px;
+ padding: 2px 6px;
+ }
+
+ .inputArea {
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .sendButton {
+ align-self: flex-end;
+ min-width: 60px;
+ }
+}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx
index e69de29b..e226b02a 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/MessageEnter/index.tsx
@@ -0,0 +1,301 @@
+import React, { useState } from "react";
+import { Layout, Input, Button, Dropdown, Menu, Tooltip, Modal } from "antd";
+import {
+ ShareAltOutlined,
+ SendOutlined,
+ SmileOutlined,
+ FolderOutlined,
+ AudioOutlined,
+ AudioOutlined as AudioHoldOutlined,
+ CodeSandboxOutlined,
+ MessageOutlined,
+ EnvironmentOutlined,
+ StarOutlined,
+} from "@ant-design/icons";
+import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
+import { useWebSocketStore } from "@/store/module/websocket/websocket";
+import styles from "./MessageEnter.module.scss";
+
+const { Footer } = Layout;
+const { TextArea } = Input;
+
+interface MessageEnterProps {
+ contract: ContractData | weChatGroup;
+}
+
+const { sendCommand } = useWebSocketStore.getState();
+
+const MessageEnter: React.FC = ({ contract }) => {
+ const [inputValue, setInputValue] = useState("");
+ const [showMaterialModal, setShowMaterialModal] = useState(false);
+
+ const handleSend = async () => {
+ if (!inputValue.trim()) return;
+ console.log("发送消息", contract);
+ const params = {
+ wechatAccountId: contract.wechatAccountId,
+ wechatChatroomId: contract?.chatroomId ? contract.id : 0,
+ wechatFriendId: contract?.chatroomId ? 0 : contract.id,
+ msgSubType: 0,
+ msgType: 1,
+ content: inputValue,
+ };
+ sendCommand("CmdSendMessage", params);
+ setInputValue("");
+ };
+
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ // Ctrl+Enter 换行由 TextArea 自动处理,不需要阻止默认行为
+ };
+
+ // 素材菜单项
+ const materialMenuItems = [
+ {
+ key: "text",
+ label: "文字素材",
+ icon: 📝,
+ },
+ {
+ key: "audio",
+ label: "语音素材",
+ icon: 🎵,
+ },
+ {
+ key: "image",
+ label: "图片素材",
+ icon: 🖼️,
+ },
+ {
+ key: "video",
+ label: "视频素材",
+ icon: 🎬,
+ },
+ {
+ key: "link",
+ label: "链接素材",
+ icon: 🔗,
+ },
+ {
+ key: "card",
+ label: "名片素材",
+ icon: 📇,
+ },
+ ];
+
+ const handleMaterialSelect = (key: string) => {
+ console.log("选择素材类型:", key);
+ setShowMaterialModal(true);
+ // 这里可以根据不同的素材类型显示不同的模态框
+ };
+
+ return (
+ <>
+ {/* 聊天输入 */}
+
+
+ {/* 素材选择模态框 */}
+ setShowMaterialModal(false)}
+ footer={[
+ ,
+ ,
+ ]}
+ width={800}
+ >
+
+ {/* 左侧素材分类 */}
+
+
+
公共素材
+
+
+
+ 暗黑4
+
+
+ 针对老客户的...
+
+
+ D2辅助
+
+
+ ROS反馈演示...
+
+
+ 一键宏产品素...
+
+
+
+
部门素材
+
+
+
+ {/* 右侧内容区域 */}
+
+
+
+ >
+ );
+};
+
+export default MessageEnter;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/Person.module.scss
rename to Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/Person.module.scss
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx
similarity index 100%
rename from Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx
rename to Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/ProfileCard/index.tsx
diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
index 9bdfdb93..5470c535 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/index.tsx
@@ -1,312 +1,112 @@
-import React, { useState, useEffect, useRef } from "react";
+import React, { useEffect, useRef } from "react";
+import { Layout, Button, Avatar, Space, Dropdown, Menu, Tooltip } from "antd";
import {
- Layout,
- Input,
- Button,
- Avatar,
- Space,
- Dropdown,
- Menu,
- message,
- Tooltip,
- Modal,
-} from "antd";
-import {
- ShareAltOutlined,
- SendOutlined,
- SmileOutlined,
- FolderOutlined,
PhoneOutlined,
VideoCameraOutlined,
MoreOutlined,
UserOutlined,
- AudioOutlined,
- AudioOutlined as AudioHoldOutlined,
DownloadOutlined,
- CodeSandboxOutlined,
- MessageOutlined,
FileOutlined,
FilePdfOutlined,
FileWordOutlined,
FileExcelOutlined,
FilePptOutlined,
PlayCircleFilled,
- EnvironmentOutlined,
TeamOutlined,
- StarOutlined,
+ FolderOutlined,
+ EnvironmentOutlined,
} from "@ant-design/icons";
import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
-import { getMessages } from "@/pages/pc/ckbox/api";
import styles from "./ChatWindow.module.scss";
-import {
- useWebSocketStore,
- WebSocketMessage,
-} from "@/store/module/websocket/websocket";
+import { useWebSocketStore } from "@/store/module/websocket/websocket";
import { formatWechatTime } from "@/utils/common";
-import Person from "./components/Person";
-const { Header, Content, Footer } = Layout;
-const { TextArea } = Input;
+import ProfileCard from "./components/ProfileCard";
+import MessageEnter from "./components/MessageEnter";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
+const { Header, Content } = Layout;
interface ChatWindowProps {
contract: ContractData | weChatGroup;
- onSendMessage: (message: string) => void;
showProfile?: boolean;
onToggleProfile?: () => void;
}
const ChatWindow: React.FC = ({
contract,
- onSendMessage,
showProfile = true,
onToggleProfile,
}) => {
- const [messageApi, contextHolder] = message.useMessage();
- const [messages, setMessages] = useState([]);
- const [inputValue, setInputValue] = useState("");
- const [loading, setLoading] = useState(false);
- const [showMaterialModal, setShowMaterialModal] = useState(false);
- const [pendingVideoRequests, setPendingVideoRequests] = useState<
- Record
- >({});
const messagesEndRef = useRef(null);
+ const currentMessages = useWeChatStore(state => state.currentMessages);
+ const prevMessagesRef = useRef(currentMessages);
useEffect(() => {
- setLoading(true);
- const params: any = {
- wechatAccountId: contract.wechatAccountId,
- From: 1,
- To: +new Date() + 1000,
- Count: 100,
- olderData: true,
- };
- if (contract.groupId == 1) {
- params.wechatFriendId = contract.id;
- } else {
- params.wechatChatroomId = contract.id;
- }
- getMessages(params)
- .then(msg => {
- setMessages(msg);
- })
- .finally(() => {
- setLoading(false);
- });
- }, [contract.id]);
+ const prevMessages = prevMessagesRef.current;
+
+ // 检查是否有视频状态变化(从加载中变为已完成或开始加载)
+ console.log("currentMessages", currentMessages);
+
+ const hasVideoStateChange = currentMessages.some((msg, index) => {
+ const prevMsg = prevMessages[index];
+ if (!prevMsg || prevMsg.id !== msg.id) return false;
- useEffect(() => {
- // 只有在非视频加载操作时才自动滚动到底部
- // 检查是否有视频正在加载中
- const hasLoadingVideo = messages.some(msg => {
try {
- const content =
+ const currentContent =
typeof msg.content === "string"
? JSON.parse(msg.content)
: msg.content;
- return content.isLoading === true;
+ const prevContent =
+ typeof prevMsg.content === "string"
+ ? JSON.parse(prevMsg.content)
+ : prevMsg.content;
+
+ // 检查视频状态是否发生变化(开始加载、完成加载、获得URL)
+ const currentHasVideo =
+ currentContent.previewImage && currentContent.tencentUrl;
+ const prevHasVideo = prevContent.previewImage && prevContent.tencentUrl;
+
+ if (currentHasVideo && prevHasVideo) {
+ // 检查加载状态变化或视频URL变化
+ return (
+ currentContent.isLoading !== prevContent.isLoading ||
+ currentContent.videoUrl !== prevContent.videoUrl
+ );
+ }
+
+ return false;
} catch (e) {
return false;
}
});
- if (!hasLoadingVideo) {
+ // 只有在没有视频状态变化时才自动滚动到底部
+ if (!hasVideoStateChange) {
scrollToBottom();
}
- }, [messages]);
- // 添加 WebSocket 消息订阅 - 监听视频下载响应消息
- useEffect(() => {
- // 只有当有待处理的视频请求时才订阅WebSocket消息
- if (Object.keys(pendingVideoRequests).length === 0) {
- return;
- }
-
- console.log("开始监听视频下载响应,当前待处理请求:", pendingVideoRequests);
-
- // 订阅 WebSocket 消息变化
- const unsubscribe = useWebSocketStore.subscribe(state => {
- // 只处理新增的消息
- const messages = state.messages as WebSocketMessage[];
-
- // 筛选出视频下载响应消息
- messages.forEach(message => {
- if (message?.content?.cmdType === "CmdDownloadVideoResult") {
- console.log("收到视频下载响应:", message.content);
-
- // 检查是否是我们正在等待的视频响应
- const messageId = Object.keys(pendingVideoRequests).find(
- id => pendingVideoRequests[id] === message.content.friendMessageId,
- );
-
- if (messageId) {
- console.log("找到对应的消息ID:", messageId);
-
- // 从待处理队列中移除
- setPendingVideoRequests(prev => {
- const newRequests = { ...prev };
- delete newRequests[messageId];
- return newRequests;
- });
-
- // 更新消息内容,将视频URL添加到对应的消息中
- setMessages(prevMessages => {
- return prevMessages.map(msg => {
- if (msg.id === Number(messageId)) {
- try {
- const msgContent =
- typeof msg.content === "string"
- ? JSON.parse(msg.content)
- : msg.content;
-
- // 更新消息内容,添加视频URL并移除加载状态
- return {
- ...msg,
- content: JSON.stringify({
- ...msgContent,
- videoUrl: message.content.url,
- isLoading: false,
- }),
- };
- } catch (e) {
- console.error("解析消息内容失败:", e);
- }
- }
- return msg;
- });
- });
- }
- }
- });
- });
-
- // 组件卸载时取消订阅
- return () => {
- unsubscribe();
- };
- }, [pendingVideoRequests]); // 依赖于pendingVideoRequests,当队列变化时重新设置订阅
+ // 更新上一次的消息状态
+ prevMessagesRef.current = currentMessages;
+ }, [currentMessages]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
- const handleSend = async () => {
- if (!inputValue.trim()) return;
-
- try {
- const newMessage: ChatRecord = {
- id: contract.id,
- wechatAccountId: contract.wechatAccountId,
- wechatFriendId: contract.id,
- tenantId: 0,
- accountId: 0,
- synergyAccountId: 0,
- content: inputValue,
- msgType: 0,
- msgSubType: 0,
- msgSvrId: "",
- isSend: false,
- createTime: "",
- isDeleted: false,
- deleteTime: "",
- sendStatus: 0,
- wechatTime: 0,
- origin: 0,
- msgId: 0,
- recalled: false,
- };
-
- setMessages(prev => [...prev, newMessage]);
- onSendMessage(inputValue);
- setInputValue("");
- } catch (error) {
- messageApi.error("发送失败");
- }
- };
-
- const handleKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- handleSend();
- }
- };
-
- // 素材菜单项
- const materialMenuItems = [
- {
- key: "text",
- label: "文字素材",
- icon: 📝,
- },
- {
- key: "audio",
- label: "语音素材",
- icon: 🎵,
- },
- {
- key: "image",
- label: "图片素材",
- icon: 🖼️,
- },
- {
- key: "video",
- label: "视频素材",
- icon: 🎬,
- },
- {
- key: "link",
- label: "链接素材",
- icon: 🔗,
- },
- {
- key: "card",
- label: "名片素材",
- icon: 📇,
- },
- ];
-
- const handleMaterialSelect = (key: string) => {
- console.log("选择素材类型:", key);
- setShowMaterialModal(true);
- // 这里可以根据不同的素材类型显示不同的模态框
- };
-
// 处理视频播放请求,发送socket请求获取真实视频地址
const handleVideoPlayRequest = (tencentUrl: string, messageId: number) => {
- // 生成请求ID (使用当前时间戳作为唯一标识)
- const requestSeq = `${+new Date()}`;
- console.log("发送视频下载请求:", { messageId, requestSeq });
+ console.log("发送视频下载请求:", { messageId, tencentUrl });
+
+ // 先设置加载状态
+ useWeChatStore.getState().setVideoLoading(messageId, true);
// 构建socket请求数据
useWebSocketStore.getState().sendCommand("CmdDownloadVideo", {
chatroomMessageId: contract.chatroomId ? messageId : 0,
friendMessageId: contract.chatroomId ? 0 : messageId,
- seq: requestSeq, // 使用唯一的请求ID
+ seq: `${+new Date()}`, // 使用时间戳作为请求序列号
tencentUrl: tencentUrl,
wechatAccountId: contract.wechatAccountId,
});
-
- // 将消息ID和请求序列号添加到待处理队列
- setPendingVideoRequests(prev => ({
- ...prev,
- [messageId]: messageId,
- }));
-
- // 更新消息状态为加载中
- setMessages(prevMessages => {
- return prevMessages.map(msg => {
- if (msg.id === messageId) {
- // 保存原始内容,添加loading状态
- const originalContent = msg.content;
- return {
- ...msg,
- content: JSON.stringify({
- ...JSON.parse(originalContent),
- isLoading: true,
- }),
- };
- }
- return msg;
- });
- });
};
// 解析消息内容,判断消息类型并返回对应的渲染内容
@@ -337,21 +137,22 @@ const ChatWindow: React.FC = ({
content.trim().endsWith("}")
) {
const videoData = JSON.parse(content);
- // 处理用户提供的JSON格式 {"previewImage":"https://...", "tencentUrl":"..."}
+ // 处理视频消息格式 {"previewImage":"https://...", "tencentUrl":"...", "videoUrl":"...", "isLoading":true}
if (videoData.previewImage && videoData.tencentUrl) {
// 提取预览图URL,去掉可能的引号
const previewImageUrl = videoData.previewImage.replace(/[`"']/g, "");
- // 创建点击处理函数,调用handleVideoPlayRequest发送socket请求获取真实视频地址
+ // 创建点击处理函数
const handlePlayClick = (e: React.MouseEvent) => {
e.stopPropagation();
- // 调用处理函数,传入tencentUrl和消息ID
- handleVideoPlayRequest(videoData.tencentUrl, msg.id);
+ // 如果没有视频URL且不在加载中,则发起下载请求
+ if (!videoData.videoUrl && !videoData.isLoading) {
+ handleVideoPlayRequest(videoData.tencentUrl, msg.id);
+ }
};
- // 检查是否已下载视频URL
+ // 如果已有视频URL,显示视频播放器
if (videoData.videoUrl) {
- // 已获取到视频URL,显示视频播放器
return (
@@ -374,30 +175,7 @@ const ChatWindow: React.FC
= ({
);
}
- // 检查是否处于加载状态
- if (videoData.isLoading) {
- return (
-
-
-

-
-
-
- );
- }
-
- // 默认显示预览图和播放按钮
+ // 显示预览图,根据加载状态显示不同的图标
return (
@@ -405,12 +183,20 @@ const ChatWindow: React.FC
= ({
src={previewImageUrl}
alt="视频预览"
className={styles.videoThumbnail}
- style={{ maxWidth: "100%", borderRadius: "8px" }}
+ style={{
+ maxWidth: "100%",
+ borderRadius: "8px",
+ opacity: videoData.isLoading ? "0.7" : "1",
+ }}
/>
-
+ {videoData.isLoading ? (
+
+ ) : (
+
+ )}
@@ -740,14 +526,10 @@ const ChatWindow: React.FC = ({
// 用于分组消息并添加时间戳的辅助函数
const groupMessagesByTime = (messages: ChatRecord[]) => {
- const groups: { time: string; messages: ChatRecord[] }[] = [];
- messages.forEach(msg => {
- // 使用 formatWechatTime 函数格式化时间戳
- const formattedTime = formatWechatTime(msg.wechatTime);
- groups.push({ time: formattedTime, messages: [msg] });
- });
-
- return groups;
+ return messages.map(msg => ({
+ time: formatWechatTime(msg?.wechatTime),
+ messages: [msg],
+ }));
};
const renderMessage = (msg: ChatRecord) => {
@@ -802,7 +584,6 @@ const ChatWindow: React.FC = ({
return (
- {contextHolder}
{/* 聊天主体区域 */}
{/* 聊天头部 */}
@@ -849,228 +630,26 @@ const ChatWindow: React.FC = ({
{/* 聊天内容 */}
- {loading ? (
-
- ) : (
- <>
- {groupMessagesByTime(messages).map((group, groupIndex) => (
-
- {group.time}
- {group.messages.map(renderMessage)}
-
- ))}
-
- >
- )}
+ {groupMessagesByTime(currentMessages).map((group, groupIndex) => (
+
+ {group.time}
+ {group.messages.map(renderMessage)}
+
+ ))}
+
- {/* 聊天输入 */}
-
+ {/* 消息输入组件 */}
+
{/* 右侧个人资料卡片 */}
-
-
- {/* 素材选择模态框 */}
- setShowMaterialModal(false)}
- footer={[
- ,
- ,
- ]}
- width={800}
- >
-
- {/* 左侧素材分类 */}
-
-
-
公共素材
-
-
-
- 暗黑4
-
-
- 针对老客户的...
-
-
- D2辅助
-
-
- ROS反馈演示...
-
-
- 一键宏产品素...
-
-
-
-
部门素材
-
-
-
- {/* 右侧内容区域 */}
-
-
-
);
};
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss
index 7f78afc9..e70717c2 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/MessageList.module.scss
@@ -63,7 +63,6 @@
.lastMessage {
font-size: 12px;
color: #8c8c8c;
- overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
@@ -71,7 +70,7 @@
padding-right: 5px;
height: 18px; // 添加固定高度
line-height: 18px; // 设置行高与高度一致
-
+
&::before {
content: attr(data-count);
position: absolute;
@@ -88,7 +87,7 @@
text-align: center;
display: none;
}
-
+
&[data-count]:not([data-count=""]):not([data-count="0"]) {
&::before {
display: inline-block;
@@ -106,7 +105,7 @@
}
}
}
-
+
.lastDayMessage {
position: absolute;
bottom: 0;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
index 877b9c9e..dfaab7e4 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx
@@ -2,28 +2,28 @@ import React from "react";
import { List, Avatar, Badge } from "antd";
import { UserOutlined, TeamOutlined } from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
+import { useCkChatStore } from "@/store/module/ckchat/ckchat";
+
import styles from "./MessageList.module.scss";
import { formatWechatTime } from "@/utils/common";
-interface MessageListProps {
- chatSessions: ContractData[] | weChatGroup[];
- currentChat: ContractData | weChatGroup;
- onContactClick: (chat: ContractData | weChatGroup) => void;
-}
+interface MessageListProps {}
-const MessageList: React.FC = ({
- chatSessions,
- currentChat,
- onContactClick,
-}) => {
+const MessageList: React.FC = () => {
+ const { setCurrentContact, currentContract } = useWeChatStore();
+ const chatSessions = useCkChatStore(state => state.chatSessions);
+ const onContactClick = (session: ContractData | weChatGroup) => {
+ setCurrentContact(session, true);
+ };
return (
(
onContactClick(session)}
>
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss
index 8b371fa2..00633207 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/WechatFriends.module.scss
@@ -60,6 +60,13 @@
padding: 10px 0;
}
+.noResults {
+ text-align: center;
+ color: #999;
+ padding: 20px;
+ font-size: 14px;
+}
+
.list {
flex: 1;
overflow-y: auto;
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx
index 33573b04..76f5f981 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends/index.tsx
@@ -2,21 +2,24 @@ import React, { useState, useCallback, useEffect } from "react";
import { List, Avatar, Collapse, Button } from "antd";
import type { CollapseProps } from "antd";
import styles from "./WechatFriends.module.scss";
-import { useCkChatStore } from "@/store/module/ckchat/ckchat";
+import {
+ useCkChatStore,
+ searchContactsAndGroups,
+} from "@/store/module/ckchat/ckchat";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
+import { addChatSession } from "@/store/module/ckchat/ckchat";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
interface WechatFriendsProps {
- contracts: ContractData[] | weChatGroup[];
- onContactClick: (contract: ContractData | weChatGroup) => void;
selectedContactId?: ContractData | weChatGroup;
}
-
const ContactListSimple: React.FC = ({
- contracts,
- onContactClick,
selectedContactId,
}) => {
const [newContractList, setNewContractList] = useState([]);
+ const [searchResults, setSearchResults] = useState<
+ (ContractData | weChatGroup)[]
+ >([]);
const getNewContractListFn = useCkChatStore(
state => state.getNewContractList,
);
@@ -26,17 +29,27 @@ const ContactListSimple: React.FC = ({
// 使用useEffect来处理异步的getNewContractList调用
useEffect(() => {
- const fetchNewContractList = async () => {
+ const fetchData = async () => {
try {
- const result = await getNewContractListFn();
- setNewContractList(result || []);
+ if (searchKeyword.trim()) {
+ // 有搜索关键词时,获取搜索结果
+ const searchResult = await searchContactsAndGroups();
+ setSearchResults(searchResult || []);
+ setNewContractList([]);
+ } else {
+ // 无搜索关键词时,获取分组列表
+ const result = await getNewContractListFn();
+ setNewContractList(result || []);
+ setSearchResults([]);
+ }
} catch (error) {
- console.error("获取联系人分组列表失败:", error);
+ console.error("获取联系人数据失败:", error);
setNewContractList([]);
+ setSearchResults([]);
}
};
- fetchNewContractList();
+ fetchData();
}, [getNewContractListFn, kfSelected, countLables, searchKeyword]);
const [activeKey, setActiveKey] = useState([]); // 默认展开第一个分组
@@ -48,32 +61,39 @@ const ContactListSimple: React.FC = ({
const [loading, setLoading] = useState<{ [key: string]: boolean }>({});
const [hasMore, setHasMore] = useState<{ [key: string]: boolean }>({});
const [page, setPage] = useState<{ [key: string]: number }>({});
+ const { setCurrentContact } = useWeChatStore();
+ const onContactClick = (contact: ContractData | weChatGroup) => {
+ addChatSession(contact);
+ setCurrentContact(contact);
+ };
// 渲染联系人项
- const renderContactItem = (contact: ContractData) => (
- onContactClick(contact)}
- className={`${styles.contractItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`}
- >
-
-
{contact.nickname.charAt(0)}
- )
- }
- className={styles.avatar}
- />
-
-
-
- {contact.conRemark || contact.nickname}
+ const renderContactItem = (contact: ContractData | weChatGroup) => {
+ // 判断是否为群组
+ const isGroup = "chatroomId" in contact;
+ const avatar = contact.avatar || contact.chatroomAvatar;
+ const name = contact.conRemark || contact.nickname;
+
+ return (
+
onContactClick(contact)}
+ className={`${styles.contractItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`}
+ >
+
+
{contact.nickname.charAt(0)}}
+ className={styles.avatar}
+ />
-
-
- );
+
+
{name}
+ {isGroup &&
群聊
}
+
+
+ );
+ };
// 初始化分页数据
useEffect(() => {
@@ -188,22 +208,27 @@ const ContactListSimple: React.FC
= ({
return (
- {newContractList && newContractList.length > 0 ? (
+ {searchKeyword.trim() ? (
+ // 搜索模式:直接显示搜索结果列表
+ <>
+
搜索结果
+
+ {searchResults.length === 0 && (
+
未找到匹配的联系人
+ )}
+ >
+ ) : (
+ // 正常模式:显示分组
setActiveKey(keys as string[])}
items={getCollapseItems()}
/>
- ) : (
- <>
- 全部好友
-
- >
)}
);
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
index b143d406..dd072845 100644
--- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx
@@ -6,26 +6,15 @@ import {
ChromeOutlined,
MessageOutlined,
} from "@ant-design/icons";
-import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import WechatFriends from "./WechatFriends";
import MessageList from "./MessageList/index";
import styles from "./SidebarMenu.module.scss";
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
-
interface SidebarMenuProps {
- contracts: ContractData[] | weChatGroup[];
- currentChat: ContractData | weChatGroup;
- onContactClick: (contract: ContractData | weChatGroup) => void;
loading?: boolean;
}
-const SidebarMenu: React.FC = ({
- contracts,
- currentChat,
- onContactClick,
- loading = false,
-}) => {
- const chatSessions = useCkChatStore(state => state.getChatSessions());
+const SidebarMenu: React.FC = ({ loading = false }) => {
const searchKeyword = useCkChatStore(state => state.searchKeyword);
const setSearchKeyword = useCkChatStore(state => state.setSearchKeyword);
const clearSearchKeyword = useCkChatStore(state => state.clearSearchKeyword);
@@ -93,7 +82,7 @@ const SidebarMenu: React.FC = ({
{/* 搜索栏 */}
}
value={searchKeyword}
onChange={e => handleSearch(e.target.value)}
@@ -133,21 +122,9 @@ const SidebarMenu: React.FC
= ({
const renderContent = () => {
switch (activeTab) {
case "chats":
- return (
-
- );
+ return ;
case "contracts":
- return (
-
- );
+ return ;
case "groups":
return (
diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx
index b0112675..0cbe80f4 100644
--- a/Cunkebao/src/pages/pc/ckbox/index.tsx
+++ b/Cunkebao/src/pages/pc/ckbox/index.tsx
@@ -10,38 +10,15 @@ import styles from "./index.module.scss";
import { addChatSession } from "@/store/module/ckchat/ckchat";
const { Header, Content, Sider } = Layout;
import { chatInitAPIdata, initSocket } from "./main";
-import { clearUnreadCount } from "@/pages/pc/ckbox/api";
-import {
- KfUserListData,
- weChatGroup,
- ContractData,
-} from "@/pages/pc/ckbox/data";
-import { useWebSocketStore } from "@/store/module/websocket/websocket";
-import { useCkChatStore } from "@/store/module/ckchat/ckchat";
+import { useWeChatStore } from "@/store/module/weChat/weChat";
+
+import { KfUserListData } from "@/pages/pc/ckbox/data";
const CkboxPage: React.FC = () => {
- const [messageApi, contextHolder] = message.useMessage();
- const [contracts, setContacts] = useState
([]);
- const [currentChat, setCurrentChat] = useState(
- null,
- );
- const status = useWebSocketStore(state => state.status);
// 不要在组件初始化时获取sendCommand,而是在需要时动态获取
const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true);
- const kfUserList = useCkChatStore(state => state.kfUserList);
- const { sendCommand } = useWebSocketStore.getState();
- useEffect(() => {
- if (status == "connected" && kfUserList.length > 0) {
- //查询客服用户激活状态
- setInterval(() => {
- sendCommand("CmdRequestWechatAccountsAliveStatus", {
- wechatAccountIds: kfUserList.map(v => v.id),
- });
- }, 10 * 1000);
- }
- }, [status]);
-
+ const currentContract = useWeChatStore(state => state.currentContract);
useEffect(() => {
// 方法一:使用 Promise 链式调用处理异步函数
setLoading(true);
@@ -63,8 +40,6 @@ const CkboxPage: React.FC = () => {
addChatSession(v);
});
- setContacts(isChatList);
-
// 数据加载完成后初始化WebSocket连接
initSocket();
})
@@ -76,46 +51,9 @@ const CkboxPage: React.FC = () => {
});
}, []);
- //开始开启聊天
- const handleContactClick = (contract: ContractData | weChatGroup) => {
- clearUnreadCount([contract.id]).then(() => {
- contract.unreadCount = 0;
- addChatSession(contract);
- setCurrentChat(contract);
- });
- };
-
- const handleSendMessage = async (message: string) => {
- if (!currentChat || !message.trim()) return;
-
- try {
- // 更新当前聊天会话
- const updatedSession = {
- ...currentChat,
- lastMessage: message,
- lastTime: dayjs().toISOString(),
- unreadCount: 0,
- };
-
- setCurrentChat(updatedSession);
-
- messageApi.success("消息发送成功");
- } catch (error) {
- messageApi.error("消息发送失败");
- }
- };
-
- // 处理垂直侧边栏用户选择
- const handleVerticalUserSelect = (userId: string) => {
- // setActiveVerticalUserId(userId);
- // 这里可以根据选择的用户类别筛选不同的联系人列表
- // 例如:根据userId加载不同分类的联系人
- };
-
return (
- {contextHolder}
{/* 垂直侧边栏 */}
@@ -126,17 +64,12 @@ const CkboxPage: React.FC = () => {
{/* 左侧联系人边栏 */}
-
+
{/* 主内容区 */}
- {currentChat ? (
+ {currentContract ? (
@@ -153,8 +86,7 @@ const CkboxPage: React.FC = () => {
setShowProfile(!showProfile)}
/>
diff --git a/Cunkebao/src/store/module/ckchat/ckchat.data.ts b/Cunkebao/src/store/module/ckchat/ckchat.data.ts
index c7d28f66..af7f8743 100644
--- a/Cunkebao/src/store/module/ckchat/ckchat.data.ts
+++ b/Cunkebao/src/store/module/ckchat/ckchat.data.ts
@@ -33,6 +33,7 @@ export interface CkUserInfo {
export interface CkChatState {
userInfo: CkUserInfo | null;
isLoggedIn: boolean;
+ searchKeyword: string;
contractList: ContractData[];
chatSessions: any[];
kfUserList: KfUserListData[];
@@ -42,6 +43,8 @@ export interface CkChatState {
newContractList: ContactGroupByLabel[];
getContractList: () => ContractData[];
getNewContractList: () => ContactGroupByLabel[];
+ setSearchKeyword: (keyword: string) => void;
+ clearSearchKeyword: () => void;
asyncKfSelected: (data: number) => void;
asyncWeChatGroup: (data: weChatGroup[]) => void;
asyncCountLables: (data: ContactGroupByLabel[]) => void;
@@ -49,13 +52,13 @@ export interface CkChatState {
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;
- getChatSessions: () => any[];
addChatSession: (session: any) => void;
- updateChatSession: (session: any) => void;
deleteChatSession: (sessionId: string) => void;
setUserInfo: (userInfo: CkUserInfo) => void;
clearUserInfo: () => void;
diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts
index db1bf9e6..d0a16103 100644
--- a/Cunkebao/src/store/module/ckchat/ckchat.ts
+++ b/Cunkebao/src/store/module/ckchat/ckchat.ts
@@ -131,6 +131,71 @@ export const useCkChatStore = createPersistStore(
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 });
@@ -297,6 +362,7 @@ export const useCkChatStore = createPersistStore(
})(),
// 添加聊天会话
addChatSession: (session: ContractData | weChatGroup) => {
+ session.unreadCount = 0;
set(state => {
// 检查是否已存在相同id的会话
const exists = state.chatSessions.some(item => item.id === session.id);
@@ -307,47 +373,20 @@ export const useCkChatStore = createPersistStore(
: [...state.chatSessions, session as ContractData | weChatGroup],
};
});
- // 清除getChatSessions缓存
- const state = useCkChatStore.getState();
- if (
- state.getChatSessions &&
- typeof state.getChatSessions === "function"
- ) {
- // 触发缓存重新计算
- state.getChatSessions();
- }
},
// 更新聊天会话
updateChatSession: (session: ContractData | weChatGroup) => {
set(state => ({
chatSessions: state.chatSessions.map(item =>
- item.id === session.id ? session : item,
+ item.id === session.id ? { ...item, ...session } : item,
),
}));
- // 清除getChatSessions缓存
- const state = useCkChatStore.getState();
- if (
- state.getChatSessions &&
- typeof state.getChatSessions === "function"
- ) {
- // 触发缓存重新计算
- state.getChatSessions();
- }
},
// 删除聊天会话
deleteChatSession: (sessionId: string) => {
set(state => ({
chatSessions: state.chatSessions.filter(item => item.id !== sessionId),
}));
- // 清除getChatSessions缓存
- const state = useCkChatStore.getState();
- if (
- state.getChatSessions &&
- typeof state.getChatSessions === "function"
- ) {
- // 触发缓存重新计算
- state.getChatSessions();
- }
},
// 设置用户信息
setUserInfo: (userInfo: CkUserInfo) => {
@@ -472,4 +511,6 @@ export const setSearchKeyword = (keyword: string) =>
useCkChatStore.getState().setSearchKeyword(keyword);
export const clearSearchKeyword = () =>
useCkChatStore.getState().clearSearchKeyword();
+export const searchContactsAndGroups = () =>
+ useCkChatStore.getState().searchContactsAndGroups();
useCkChatStore.getState().getKfSelectedUser();
diff --git a/Cunkebao/src/store/module/weChat/weChat.data.ts b/Cunkebao/src/store/module/weChat/weChat.data.ts
new file mode 100644
index 00000000..a63ff292
--- /dev/null
+++ b/Cunkebao/src/store/module/weChat/weChat.data.ts
@@ -0,0 +1,25 @@
+import { ChatRecord, ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
+// 微信聊天相关的类型定义
+export interface WeChatState {
+ // 当前选中的联系人/群组
+ currentContract: ContractData | weChatGroup | null;
+
+ // 当前聊天用户的消息列表(只存储当前聊天用户的消息)
+ currentMessages: ChatRecord[];
+
+ // 消息加载状态
+ messagesLoading: boolean;
+
+ // Actions
+ setCurrentContact: (
+ contract: ContractData | weChatGroup,
+ isExist?: boolean,
+ ) => void;
+ loadChatMessages: (contact: ContractData | weChatGroup) => Promise;
+
+ // 视频消息处理方法
+ setVideoLoading: (messageId: number, isLoading: boolean) => void;
+ setVideoUrl: (messageId: number, videoUrl: string) => void;
+ addMessage: (message: ChatRecord) => void;
+ receivedMsg: (message: ChatRecord) => void;
+}
diff --git a/Cunkebao/src/store/module/weChat/weChat.ts b/Cunkebao/src/store/module/weChat/weChat.ts
new file mode 100644
index 00000000..696ba0bd
--- /dev/null
+++ b/Cunkebao/src/store/module/weChat/weChat.ts
@@ -0,0 +1,194 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+import { getChatMessages } 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 {
+ addChatSession,
+ updateChatSession,
+ useCkChatStore,
+} from "@/store/module/ckchat/ckchat";
+
+export const useWeChatStore = create()(
+ persist(
+ (set, get) => ({
+ // 初始状态
+ currentContract: null,
+ currentMessages: [],
+ messagesLoading: false,
+
+ // 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(contract);
+ });
+ },
+
+ loadChatMessages: async contact => {
+ set({ messagesLoading: true });
+
+ try {
+ const params: any = {
+ wechatAccountId: contact.wechatAccountId,
+ From: 1,
+ To: 4704624000000,
+ Count: 10,
+ olderData: true,
+ };
+
+ if ("chatroomId" in contact && contact.chatroomId) {
+ params.wechatChatroomId = contact.chatroomId;
+ } else {
+ params.wechatFriendId = contact.id;
+ }
+
+ const messages = await getChatMessages(params);
+ set({ currentMessages: messages || [] });
+ } catch (error) {
+ console.error("获取聊天消息失败:", error);
+ } finally {
+ set({ messagesLoading: false });
+ }
+ },
+
+ setMessageLoading: loading => {
+ set({ messagesLoading: Boolean(loading) });
+ },
+
+ addMessage: message => {
+ set(state => ({
+ currentMessages: [...state.currentMessages, message],
+ }));
+ },
+
+ receivedMsg: message => {
+ const currentContract = useWeChatStore.getState().currentContract;
+ if (
+ currentContract &&
+ currentContract.wechatAccountId == message.wechatAccountId &&
+ currentContract.id == message.wechatFriendId
+ ) {
+ set(state => ({
+ currentMessages: [...state.currentMessages, message],
+ }));
+ } else {
+ //更新消息列表unread数值,根据接收的++1 这样
+ const chatSessions = useCkChatStore.getState().chatSessions;
+ const session = chatSessions.find(
+ item => item.id == message.wechatFriendId,
+ );
+ if (session) {
+ session.unreadCount = Number(session.unreadCount) + 1;
+ updateChatSession(session);
+ }
+ }
+ },
+
+ 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);
diff --git a/Cunkebao/src/store/module/websocket/msg.data.ts b/Cunkebao/src/store/module/websocket/msg.data.ts
new file mode 100644
index 00000000..ed8510b1
--- /dev/null
+++ b/Cunkebao/src/store/module/websocket/msg.data.ts
@@ -0,0 +1,27 @@
+export interface FriendMessage {
+ id: number;
+ wechatFriendId: number;
+ wechatAccountId: number;
+ tenantId: number;
+ accountId: number;
+ synergyAccountId: number;
+ content: string;
+ msgType: number;
+ msgSubType: number;
+ msgSvrId: string;
+ isSend: boolean;
+ createTime: string;
+ isDeleted: boolean;
+ deleteTime: string;
+ sendStatus: number;
+ wechatTime: number;
+ origin: number;
+ msgId: number;
+ recalled: boolean;
+}
+export interface Messages {
+ friendMessage: FriendMessage | null;
+ chatroomMessage: string;
+ seq: number;
+ cmdType: string;
+}
diff --git a/Cunkebao/src/store/module/websocket/msgManage.ts b/Cunkebao/src/store/module/websocket/msgManage.ts
index 45dc9ff1..3146e213 100644
--- a/Cunkebao/src/store/module/websocket/msgManage.ts
+++ b/Cunkebao/src/store/module/websocket/msgManage.ts
@@ -2,8 +2,14 @@
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 = {
@@ -19,6 +25,31 @@ const messageHandlers: Record = {
});
asyncKfUserList(kfUserList);
},
+ // 发送消息响应
+ CmdSendMessageResp: message => {
+ console.log("发送消息响应", message);
+ addMessage(message.friendMessage);
+ // 在这里添加具体的处理逻辑
+ },
+ CmdSendMessageResult: message => {
+ console.log("发送消息结果", message);
+ // 在这里添加具体的处理逻辑
+ },
+ // 接收消息响应
+ CmdReceiveMessageResp: message => {
+ console.log("接收消息响应", message);
+ addMessage(message.friendMessage);
+ // 在这里添加具体的处理逻辑
+ },
+ //收到消息
+ CmdNewMessage: (message: Messages) => {
+ // 在这里添加具体的处理逻辑
+ receivedMsg(message.friendMessage);
+ },
+ CmdFriendInfoChanged: message => {
+ // console.log("好友信息变更", message);
+ // 在这里添加具体的处理逻辑
+ },
// 登录响应
CmdSignInResp: message => {
@@ -30,6 +61,15 @@ const messageHandlers: Record = {
CmdNotify: message => {
console.log("通知消息", message);
// 在这里添加具体的处理逻辑
+ if (message.notify == "Kicked out") {
+ // 被踢出时直接跳转到登录页面
+ window.location.href = "/login";
+ }
+ },
+
+ CmdDownloadVideoResult: message => {
+ // 在这里添加具体的处理逻辑
+ setVideoUrl(message.friendMessageId, message.url);
},
// 可以继续添加更多处理器...
diff --git a/Cunkebao/src/store/module/websocket/websocket.ts b/Cunkebao/src/store/module/websocket/websocket.ts
index ebfe53a2..46f9ed18 100644
--- a/Cunkebao/src/store/module/websocket/websocket.ts
+++ b/Cunkebao/src/store/module/websocket/websocket.ts
@@ -51,6 +51,7 @@ interface WebSocketState {
// 重连相关
reconnectAttempts: number;
reconnectTimer: NodeJS.Timeout | null;
+ aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器
// 方法
connect: (config: Partial) => void;
@@ -60,6 +61,7 @@ interface WebSocketState {
clearMessages: () => void;
markAsRead: () => void;
reconnect: () => void;
+ clearConnectionState: () => void; // 清空连接状态
// 内部方法
_handleOpen: () => void;
@@ -68,6 +70,8 @@ interface WebSocketState {
_handleError: (event: Event) => void;
_startReconnectTimer: () => void;
_stopReconnectTimer: () => void;
+ _startAliveStatusTimer: () => void; // 启动客服状态查询定时器
+ _stopAliveStatusTimer: () => void; // 停止客服状态查询定时器
}
// 默认配置
@@ -92,6 +96,7 @@ export const useWebSocketStore = createPersistStore(
unreadCount: 0,
reconnectAttempts: 0,
reconnectTimer: null,
+ aliveStatusTimer: null,
// 连接WebSocket
connect: (config: Partial) => {
@@ -183,6 +188,7 @@ export const useWebSocketStore = createPersistStore(
}
currentState._stopReconnectTimer();
+ currentState._stopAliveStatusTimer();
set({
status: WebSocketStatus.DISCONNECTED,
@@ -226,7 +232,16 @@ export const useWebSocketStore = createPersistStore(
currentState.status !== WebSocketStatus.CONNECTED ||
!currentState.ws
) {
- Toast.show({ content: "WebSocket未连接", position: "top" });
+ Toast.show({
+ content: "WebSocket未连接,正在重新连接...",
+ position: "top",
+ });
+
+ // 重置连接状态并发起重新连接
+ set({ status: WebSocketStatus.DISCONNECTED });
+ if (currentState.config) {
+ currentState.connect(currentState.config);
+ }
return;
}
@@ -242,6 +257,12 @@ export const useWebSocketStore = createPersistStore(
} catch (error) {
// console.error("命令发送失败:", error);
Toast.show({ content: "命令发送失败", position: "top" });
+
+ // 发送失败时也尝试重新连接
+ set({ status: WebSocketStatus.DISCONNECTED });
+ if (currentState.config) {
+ currentState.connect(currentState.config);
+ }
}
},
@@ -269,6 +290,34 @@ export const useWebSocketStore = createPersistStore(
}
},
+ // 清空连接状态(用于退出登录时)
+ 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();
@@ -291,6 +340,9 @@ export const useWebSocketStore = createPersistStore(
}
Toast.show({ content: "WebSocket连接成功", position: "top" });
+
+ // 启动客服状态查询定时器
+ currentState._startAliveStatusTimer();
},
// 内部方法:处理消息接收
@@ -319,6 +371,9 @@ export const useWebSocketStore = createPersistStore(
});
}
+ // 停止客服状态查询定时器
+ get()._stopAliveStatusTimer();
+
// 断开连接
get().disconnect();
return;
@@ -414,6 +469,51 @@ export const useWebSocketStore = createPersistStore(
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",
@@ -424,6 +524,7 @@ export const useWebSocketStore = createPersistStore(
messages: state.messages.slice(-100), // 只保留最近100条消息
unreadCount: state.unreadCount,
reconnectAttempts: state.reconnectAttempts,
+ // 注意:定时器不需要持久化,重新连接时会重新创建
}),
onRehydrateStorage: () => state => {
// 页面刷新后,如果之前是连接状态,尝试重新连接