diff --git a/Cunkebao/src/pages/mobile/mine/setting/About.tsx b/Cunkebao/src/pages/mobile/mine/setting/About.tsx index 7bcfcf40..432d15aa 100644 --- a/Cunkebao/src/pages/mobile/mine/setting/About.tsx +++ b/Cunkebao/src/pages/mobile/mine/setting/About.tsx @@ -46,7 +46,7 @@ const About: React.FC = () => { ]; // 联系信息 - const contactInfo = [ + const contractInfo = [ { id: "email", title: "邮箱支持", @@ -125,7 +125,7 @@ const About: React.FC = () => { {/*
联系我们
- {contactInfo.map(item => ( + {contractInfo.map(item => ( = ({ const [inputValue, setInputValue] = useState(""); const [loading, setLoading] = useState(false); const [showMaterialModal, setShowMaterialModal] = useState(false); + const [pendingVideoRequests, setPendingVideoRequests] = useState< + Record + >({}); const messagesEndRef = useRef(null); useEffect(() => { @@ -92,6 +93,64 @@ const ChatWindow: React.FC = ({ scrollToBottom(); }, [messages]); + // 添加 WebSocket 消息订阅 + useEffect(() => { + // 订阅 WebSocket 消息变化 + const unsubscribe = useWebSocketStore.subscribe( + state => state.messages, + (messages, previousMessages) => { + // 只处理新消息 + if (messages.length > previousMessages.length) { + // 获取最新的消息 + const newMessages = messages.slice(previousMessages.length); + + // 处理新消息 + newMessages.forEach(message => { + const content = message.content; + + // 检查是否是视频下载响应 + if (content && content.cmdType === "CmdDownloadVideoResponse") { + // 获取视频URL + const videoUrl = content.videoUrl; + const requestId = content.requestId || content.seq; + + // 查找对应的消息ID + const messageId = pendingVideoRequests[requestId]; + if (messageId && videoUrl) { + // 更新消息内容,将预览图替换为实际视频 + setMessages(prevMessages => { + return prevMessages.map(msg => { + if (msg.id === messageId) { + return { + ...msg, + content: videoUrl, + }; + } + return msg; + }); + }); + + // 从待处理请求中移除 + setPendingVideoRequests(prev => { + const newRequests = { ...prev }; + delete newRequests[requestId]; + return newRequests; + }); + + messageApi.success("视频加载成功"); + } + } + }); + } + }, + ); + + // 组件卸载时取消订阅 + return () => { + unsubscribe(); + }; + }, [pendingVideoRequests, messageApi]); + const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; @@ -179,15 +238,22 @@ const ChatWindow: React.FC = ({ // 处理视频播放请求,发送socket请求获取真实视频地址 const handleVideoPlayRequest = (tencentUrl: string, messageId: string) => { + // 生成请求ID (可以使用 seq 或其他唯一标识) + // 构建socket请求数据 - sendCommand("CmdDownloadVideo", { + useWebSocketStore.getState().sendCommand("CmdDownloadVideo", { chatroomMessageId: contract.chatroomId ? contract.chatroomId : 0, friendMessageId: contract.chatroomId ? 0 : contract.id, - seq: 9, + seq: 9, // 使用唯一的请求ID tencentUrl: tencentUrl, wechatAccountId: contract.wechatAccountId, }); + // 记录待处理的视频请求 + setPendingVideoRequests(prev => ({ + ...prev, + })); + // 更新消息状态为加载中 setMessages(prevMessages => { return prevMessages.map(msg => { @@ -205,32 +271,6 @@ const ChatWindow: React.FC = ({ return msg; }); }); - - // TODO: 这里应该实现实际的socket请求发送逻辑 - console.log("发送视频请求:", socketData); - - // 模拟获取视频地址后的处理 - // 实际应用中,这里应该是socket响应的回调处理 - setTimeout(() => { - // 模拟获取到视频地址 - const videoUrl = "https://example.com/video.mp4"; - - // 更新消息内容,将预览图替换为实际视频 - setMessages(prevMessages => { - return prevMessages.map(msg => { - if (msg.id === messageId) { - // 创建新的视频消息内容 - return { - ...msg, - content: videoUrl, - }; - } - return msg; - }); - }); - - messageApi.success("视频加载成功"); - }, 1500); }; // 解析消息内容,判断消息类型并返回对应的渲染内容 diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss b/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss index d868d744..28ed1451 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/ContactList/ContactList.module.scss @@ -1,8 +1,8 @@ -.contactList { +.contractList { height: 100%; overflow-y: auto; - .contactItem { + .contractItem { padding: 12px 16px; cursor: pointer; border-bottom: 1px solid #f0f0f0; @@ -16,17 +16,17 @@ border-bottom: none; } - .contactInfo { + .contractInfo { display: flex; align-items: center; gap: 12px; width: 100%; - .contactDetails { + .contractDetails { flex: 1; min-width: 0; - .contactName { + .contractName { font-size: 14px; font-weight: 500; color: #262626; @@ -36,13 +36,13 @@ white-space: nowrap; } - .contactPhone { + .contractPhone { font-size: 12px; color: #8c8c8c; margin-bottom: 2px; } - .contactStatus { + .contractStatus { font-size: 11px; color: #bfbfbf; overflow: hidden; @@ -56,19 +56,19 @@ // 响应式设计 @media (max-width: 768px) { - .contactList { - .contactItem { + .contractList { + .contractItem { padding: 10px 12px; - .contactInfo { + .contractInfo { gap: 10px; - .contactDetails { - .contactName { + .contractDetails { + .contractName { font-size: 13px; } - .contactPhone { + .contractPhone { font-size: 11px; } } diff --git a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx index aaadf2ba..38caf58a 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ContactList/index.tsx @@ -5,36 +5,36 @@ import { ContractData } from "../../data"; import styles from "./ContactList.module.scss"; interface ContactListProps { - contacts: ContractData[]; - onContactClick: (contact: ContractData) => void; + contracts: ContractData[]; + onContactClick: (contract: ContractData) => void; } const ContactList: React.FC = ({ - contacts, + contracts, onContactClick, }) => { return ( -
+
( + dataSource={contracts} + renderItem={contract => ( onContactClick(contact)} + className={styles.contractItem} + onClick={() => onContactClick(contract)} > -
- +
+ } /> -
-
{contact.name}
-
{contact.phone}
- {contact.status && ( -
{contact.status}
+
+
{contract.name}
+
{contract.phone}
+ {contract.status && ( +
{contract.status}
)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx index 6cb74783..f00994de 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/MessageList/index.tsx @@ -2,13 +2,13 @@ import React from "react"; import { List, Avatar, Badge } from "antd"; import { UserOutlined, TeamOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; -import { ContractData } from "./data"; +import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; import styles from "./MessageList.module.scss"; interface MessageListProps { chatSessions: ContractData[]; currentChat: ContractData; - onChatSelect: (chat: ContractData) => void; + onChatSelect: (chat: ContractData | GroupData) => void; } const MessageList: React.FC = ({ diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss index 5de994a5..bb90641b 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriends.module.scss @@ -1,4 +1,4 @@ -.contactListSimple { +.contractListSimple { display: flex; flex-direction: column; height: 100%; @@ -26,7 +26,7 @@ } } - .contactItem { + .contractItem { display: flex; align-items: center; padding: 8px 15px; @@ -44,7 +44,7 @@ background-color: #1890ff; } - .contactInfo { + .contractInfo { flex: 1; overflow: hidden; } diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx index 8d3f04fb..303d9bb6 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/WechatFriendsModule.tsx @@ -1,43 +1,43 @@ import React from "react"; import { List, Avatar } from "antd"; -import { ContractData } from "./data"; +import { ContractData } from "@/pages/pc/ckbox/data"; import styles from "./WechatFriends.module.scss"; interface WechatFriendsProps { - contacts: ContractData[]; - onContactClick: (contact: ContractData) => void; + contracts: ContractData[]; + onContactClick: (contract: ContractData) => void; selectedContactId?: ContractData; } const ContactListSimple: React.FC = ({ - contacts, + contracts, onContactClick, selectedContactId, }) => { return ( -
+
全部好友
( + dataSource={contracts} + renderItem={contract => ( onContactClick(contact)} - className={`${styles.contactItem} ${contact.id === selectedContactId?.id ? styles.selected : ""}`} + key={contract.id} + onClick={() => onContactClick(contract)} + className={`${styles.contractItem} ${contract.id === selectedContactId?.id ? styles.selected : ""}`} >
{contact.nickname.charAt(0)} + !contract.avatar && {contract.nickname.charAt(0)} } className={styles.avatar} />
-
+
- {contact.conRemark || contact.nickname} + {contract.conRemark || contract.nickname}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 2a120454..20adfd63 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -14,15 +14,15 @@ import styles from "./SidebarMenu.module.scss"; import { getChatSessions } from "@/store/module/ckchat"; interface SidebarMenuProps { - contacts: ContractData[]; + contracts: ContractData[]; currentChat: ContractData; - onContactClick: (contact: ContractData) => void; + onContactClick: (contract: ContractData) => void; onChatSelect: (chat: ContractData) => void; loading?: boolean; } const SidebarMenu: React.FC = ({ - contacts, + contracts, currentChat, onContactClick, onChatSelect, @@ -31,18 +31,18 @@ const SidebarMenu: React.FC = ({ const chatSessions = getChatSessions(); const [searchText, setSearchText] = useState(""); - const [activeTab, setActiveTab] = useState("contacts"); + const [activeTab, setActiveTab] = useState("contracts"); const handleSearch = (value: string) => { setSearchText(value); }; const getFilteredContacts = () => { - if (!searchText) return contacts; - return contacts.filter( - contact => - contact.nickname.toLowerCase().includes(searchText.toLowerCase()) || - contact.phone.includes(searchText), + if (!searchText) return contracts; + return contracts.filter( + contract => + contract.nickname.toLowerCase().includes(searchText.toLowerCase()) || + contract.phone.includes(searchText), ); }; @@ -77,8 +77,8 @@ const SidebarMenu: React.FC = ({ 聊天
setActiveTab("contacts")} + className={`${styles.tabItem} ${activeTab === "contracts" ? styles.active : ""}`} + onClick={() => setActiveTab("contracts")} > 联系人 @@ -105,10 +105,10 @@ const SidebarMenu: React.FC = ({ currentChat={currentChat} /> ); - case "contacts": + case "contracts": return ( diff --git a/Cunkebao/src/pages/pc/ckbox/index.tsx b/Cunkebao/src/pages/pc/ckbox/index.tsx index 7a54e1fe..ab19ab07 100644 --- a/Cunkebao/src/pages/pc/ckbox/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/index.tsx @@ -11,7 +11,7 @@ const { Header, Content, Sider } = Layout; import { chatInitAPIdata } from "./main"; const CkboxPage: React.FC = () => { const [messageApi, contextHolder] = message.useMessage(); - const [contacts, setContacts] = useState([]); + const [contracts, setContacts] = useState([]); const [currentChat, setCurrentChat] = useState(null); const [loading, setLoading] = useState(false); @@ -21,10 +21,10 @@ const CkboxPage: React.FC = () => { // 方法一:使用 Promise 链式调用处理异步函数 setLoading(true); chatInitAPIdata() - .then((response: { contactList: any[]; chatRoomList: any[] }) => { - const { contactList, chatRoomList } = response; + .then((response: { contractList: any[]; chatRoomList: any[] }) => { + const { contractList, chatRoomList } = response; //找出已经在聊天的 - const isChatList = contactList.filter( + const isChatList = contractList.filter( v => (v?.config && v.config?.chat) || false, ); isChatList.forEach(v => { @@ -41,9 +41,9 @@ const CkboxPage: React.FC = () => { }); }, []); - const handleContactClick = (contact: ContractData) => { - addChatSession(contact); - setCurrentChat(contact); + const handleContactClick = (contract: ContractData) => { + addChatSession(contract); + setCurrentChat(contract); }; const handleSendMessage = async (message: string) => { @@ -74,7 +74,7 @@ const CkboxPage: React.FC = () => { {/* 左侧边栏 */} {
setShowProfile(!showProfile)} diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index 6fc17e3a..3d195926 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -33,11 +33,11 @@ export const chatInitAPIdata = async () => { seq: 1, }); //获取联系人列表 - const contactList = await getAllContactList(); + const contractList = await getAllContactList(); //获取群列表 const chatRoomList = await getAllChatRoomList(); return { - contactList, + contractList, chatRoomList, }; } catch (error) { @@ -56,28 +56,28 @@ export const getAllContactList = async () => { while (hasMore) { console.log(`获取联系人列表,prevId: ${prevId}, count: ${count}`); - const contactList = await getContactList({ + const contractList = await getContactList({ prevId, count, }); if ( - !contactList || - !Array.isArray(contactList) || - contactList.length === 0 + !contractList || + !Array.isArray(contractList) || + contractList.length === 0 ) { hasMore = false; break; } - allContacts = [...allContacts, ...contactList]; + allContacts = [...allContacts, ...contractList]; // 如果返回的数据少于请求的数量,说明已经没有更多数据了 - if (contactList.length < count) { + if (contractList.length < count) { hasMore = false; } else { // 获取最后一条数据的id作为下一次请求的prevId - const lastContact = contactList[contactList.length - 1]; + const lastContact = contractList[contractList.length - 1]; prevId = lastContact.id; } } @@ -99,28 +99,28 @@ export const getAllChatRoomList = async () => { while (hasMore) { console.log(`获取群列表,prevId: ${prevId}, count: ${count}`); - const contactList = await getChatRoomList({ + const contractList = await getChatRoomList({ prevId, count, }); if ( - !contactList || - !Array.isArray(contactList) || - contactList.length === 0 + !contractList || + !Array.isArray(contractList) || + contractList.length === 0 ) { hasMore = false; break; } - allContacts = [...allContacts, ...contactList]; + allContacts = [...allContacts, ...contractList]; // 如果返回的数据少于请求的数量,说明已经没有更多数据了 - if (contactList.length < count) { + if (contractList.length < count) { hasMore = false; } else { // 获取最后一条数据的id作为下一次请求的prevId - const lastContact = contactList[contactList.length - 1]; + const lastContact = contractList[contractList.length - 1]; prevId = lastContact.id; } } diff --git a/Cunkebao/src/router/config.ts b/Cunkebao/src/router/config.ts index 7ad14ac5..b6a8f394 100644 --- a/Cunkebao/src/router/config.ts +++ b/Cunkebao/src/router/config.ts @@ -95,7 +95,7 @@ export const routeGroups = { "/plans", "/plans/:planId", "/orders", - "/contact-import", + "/contract-import", ], }, }; @@ -126,7 +126,7 @@ export const routePermissions = { "/plans", "/plans/:planId", "/orders", - "/contact-import", + "/contract-import", ], // 访客权限 @@ -150,7 +150,7 @@ export const routeTitles: Record = { "/profile": "个人中心", "/plans": "计划管理", "/orders": "订单管理", - "/contact-import": "联系人导入", + "/contract-import": "联系人导入", }; // 获取路由标题 diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index d0a6f983..d25b5960 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -120,6 +120,11 @@ export const getCkTenantId = () => useCkChatStore.getState().getTenantId(); export const getCkAccountName = () => useCkChatStore.getState().getAccountName(); export const getCkTenantName = () => useCkChatStore.getState().getTenantName(); -export const getChatSessions = () => useCkChatStore.getState().chatSessions; +export const getChatSessions = () => + useCkChatStore.getState().getChatSessions(); export const addChatSession = (session: ContractData | GroupData) => useCkChatStore.getState().addChatSession(session); +export const updateChatSession = (session: ContractData | GroupData) => + useCkChatStore.getState().updateChatSession(session); +export const deleteChatSession = (sessionId: string) => + useCkChatStore.getState().deleteChatSession(sessionId);