FEAT => 本次更新项目为:

This commit is contained in:
超级老白兔
2025-08-22 14:21:35 +08:00
parent d0bd7d4cd7
commit 32ea075e90
9 changed files with 206 additions and 250 deletions

View File

@@ -78,16 +78,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
const fetchChatHistory = async () => { const fetchChatHistory = async () => {
try { try {
setLoading(true); setLoading(true);
// 从chat对象中提取wechatFriendId // 从chat对象中提取wechatFriendId
// 假设chat.id存储的是wechatFriendId // 假设chat.id存储的是wechatFriendId
const wechatFriendId = parseInt(chat.id); const wechatFriendId = parseInt(chat.id);
if (isNaN(wechatFriendId)) { if (isNaN(wechatFriendId)) {
messageApi.error("无效的聊天ID"); messageApi.error("无效的聊天ID");
return; return;
} }
// 调用API获取聊天历史 // 调用API获取聊天历史
const response = await getChatMessage({ const response = await getChatMessage({
wechatAccountId: 32686452, // 使用实际的wechatAccountId wechatAccountId: 32686452, // 使用实际的wechatAccountId
@@ -98,9 +98,9 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
olderData: false, olderData: false,
keyword: "", keyword: "",
}); });
console.log("聊天历史响应:", response); console.log("聊天历史响应:", response);
if (response && Array.isArray(response)) { if (response && Array.isArray(response)) {
// 将API返回的消息记录转换为MessageData格式 // 将API返回的消息记录转换为MessageData格式
const chatMessages: MessageData[] = response.map(item => { const chatMessages: MessageData[] = response.map(item => {
@@ -112,24 +112,28 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
} catch (e) { } catch (e) {
msgContent = item.content; msgContent = item.content;
} }
// 判断消息是发送还是接收 // 判断消息是发送还是接收
const isSend = item.isSend === true; const isSend = item.isSend === true;
return { return {
id: item.id.toString(), id: item.id.toString(),
senderId: isSend ? "me" : "other", senderId: isSend ? "me" : "other",
senderName: isSend ? "我" : chat.name, senderName: isSend ? "我" : chat.name,
content: msgContent, content: msgContent,
type: MessageType.TEXT, // 默认为文本类型实际应根据msgType字段判断 type: MessageType.TEXT, // 默认为文本类型实际应根据msgType字段判断
timestamp: item.createTime || new Date(item.wechatTime).toISOString(), timestamp:
item.createTime || new Date(item.wechatTime).toISOString(),
isRead: true, // 默认已读 isRead: true, // 默认已读
}; };
}); });
// 按时间排序 // 按时间排序
chatMessages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); chatMessages.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
);
setMessages(chatMessages); setMessages(chatMessages);
} else { } else {
// 如果没有消息,显示空数组 // 如果没有消息,显示空数组

View File

@@ -60,4 +60,4 @@
padding: 20px; padding: 20px;
text-align: center; text-align: center;
} }
} }

View File

@@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import { List, Avatar, Badge } from "antd"; import { List, Avatar, Badge } from "antd";
import { ContactData } from "../../data"; import { ContactData } from "./data";
import styles from "./ContactListSimple.module.scss"; import styles from "./ContactListSimple.module.scss";
interface ContactListSimpleProps { interface ContactListSimpleProps {
contacts: ContactData[]; contacts: ContactData[];
onContactClick: (contact: ContactData) => void; onContactClick: (contact: ContactData) => void;
selectedContactId?: string; selectedContactId?: number;
} }
const ContactListSimple: React.FC<ContactListSimpleProps> = ({ const ContactListSimple: React.FC<ContactListSimpleProps> = ({
@@ -31,14 +31,14 @@ const ContactListSimple: React.FC<ContactListSimpleProps> = ({
<Avatar <Avatar
src={contact.avatar} src={contact.avatar}
icon={ icon={
!contact.avatar && <span>{contact.name.charAt(0)}</span> !contact.avatar && <span>{contact.nickname.charAt(0)}</span>
} }
className={styles.avatar} className={styles.avatar}
/> />
</Badge> </Badge>
</div> </div>
<div className={styles.contactInfo}> <div className={styles.contactInfo}>
<div className={styles.name}>{contact.name}</div> <div className={styles.name}>{contact.nickname}</div>
</div> </div>
</List.Item> </List.Item>
)} )}

View File

@@ -0,0 +1,47 @@
// 联系人数据接口
export interface ContactData {
id?: number;
wechatAccountId: number;
wechatId: string;
alias: string;
conRemark: string;
nickname: string;
quanPin: string;
avatar?: string;
gender: number;
region: string;
addFrom: number;
phone: string;
labels: string[];
signature: string;
accountId: number;
extendFields: null;
city?: string;
lastUpdateTime: string;
isPassed: boolean;
tenantId: number;
groupId: number;
thirdParty: null;
additionalPicture: string;
desc: string;
config: null;
lastMessageTime: number;
unreadCount: number;
duplicate: boolean;
}
//聊天会话类型
export type ChatType = "private" | "group";
// 聊天会话接口
export interface ChatSession {
id: string;
type: ChatType;
name: string;
avatar?: string;
lastMessage: string;
lastTime: string;
unreadCount: number;
online: boolean;
members?: string[];
pinned?: boolean;
muted?: boolean;
}

View File

@@ -6,12 +6,13 @@ import {
TeamOutlined, TeamOutlined,
MessageOutlined, MessageOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { ContactData, ChatSession } from "../../data"; import { ContactData, ChatSession } from "./data";
import ContactListSimple from "./ContactListSimple"; import WechatFriendsModule from "./WechatFriendsModule";
import MessageList from "../MessageList/index"; import MessageList from "../MessageList/index";
import styles from "./SidebarMenu.module.scss"; import styles from "./SidebarMenu.module.scss";
const { Sider } = Layout; const { Sider } = Layout;
const { TabPane } = Tabs;
interface SidebarMenuProps { interface SidebarMenuProps {
contacts: ContactData[]; contacts: ContactData[];
@@ -41,7 +42,7 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({
if (!searchText) return contacts; if (!searchText) return contacts;
return contacts.filter( return contacts.filter(
contact => contact =>
contact.name.toLowerCase().includes(searchText.toLowerCase()) || contact.nickname.toLowerCase().includes(searchText.toLowerCase()) ||
contact.phone.includes(searchText), contact.phone.includes(searchText),
); );
}; };
@@ -71,56 +72,52 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({
activeKey={activeTab} activeKey={activeTab}
onChange={setActiveTab} onChange={setActiveTab}
className={styles.tabs} className={styles.tabs}
items={[ >
{ <TabPane
key: "chats", tab={
label: ( <span>
<span> <MessageOutlined />
<MessageOutlined />
</span>
</span> }
), key="chats"
children: ( >
<MessageList <MessageList
sessions={getFilteredSessions()} sessions={getFilteredSessions()}
currentChat={currentChat} currentChat={currentChat}
onChatSelect={onChatSelect} onChatSelect={onChatSelect}
/> />
), </TabPane>
}, <TabPane
{ tab={
key: "contacts", <span>
label: ( <UserOutlined />
<span>
<UserOutlined /> </span>
}
</span> key="contacts"
), >
children: ( <WechatFriendsModule
<ContactListSimple contacts={getFilteredContacts()}
contacts={getFilteredContacts()} onContactClick={onContactClick}
onContactClick={onContactClick} selectedContactId={currentChat?.id.split("_")[1]}
selectedContactId={currentChat?.id.split("_")[1]} />
/> </TabPane>
), <TabPane
}, tab={
{ <span>
key: "groups", <TeamOutlined />
label: (
<span> </span>
<TeamOutlined /> }
key="groups"
</span> >
), <div className={styles.emptyState}>
children: ( <TeamOutlined style={{ fontSize: 48, color: "#ccc" }} />
<div className={styles.emptyState}> <p></p>
<TeamOutlined style={{ fontSize: 48, color: "#ccc" }} /> </div>
<p></p> </TabPane>
</div> </Tabs>
),
},
]}
/>
</Sider> </Sider>
); );
}; };

View File

@@ -1,14 +1,52 @@
// 联系人数据接口 // 联系人数据接口
export interface ContactData { export interface ContactData {
id: string; id?: number;
name: string; wechatAccountId: number;
phone: string; wechatId: string;
alias: string;
conRemark: string;
nickname: string;
quanPin: string;
avatar?: string; avatar?: string;
online: boolean; gender: number;
lastSeen?: string; region: string;
status?: string; addFrom: number;
department?: string; phone: string;
position?: string; labels: string[];
signature: string;
accountId: number;
extendFields: null;
city?: string;
lastUpdateTime: string;
isPassed: boolean;
tenantId: number;
groupId: number;
thirdParty: null;
additionalPicture: string;
desc: string;
config: null;
lastMessageTime: number;
unreadCount: number;
duplicate: boolean;
}
/**
* 微信好友基本信息接口
* 包含主要字段和兼容性字段
*/
export interface WechatFriend {
// 主要字段
id: number; // 好友ID
wechatAccountId: number; // 微信账号ID
wechatId: string; // 微信ID
nickname: string; // 昵称
conRemark: string; // 备注名
avatar: string; // 头像URL
gender: number; // 性别1-男2-女0-未知
region: string; // 地区
phone: string; // 电话
labels: string[]; // 标签列表
[key: string]: any;
} }
// 消息类型枚举 // 消息类型枚举
@@ -52,18 +90,6 @@ export interface ChatSession {
muted?: boolean; muted?: boolean;
} }
// 群组信息接口
export interface GroupData {
id: string;
name: string;
avatar?: string;
description?: string;
members: ContactData[];
adminIds: string[];
createdAt: string;
updatedAt: string;
}
// 聊天历史响应接口 // 聊天历史响应接口
export interface ChatHistoryResponse { export interface ChatHistoryResponse {
messages: MessageData[]; messages: MessageData[];
@@ -79,12 +105,6 @@ export interface SendMessageRequest {
replyTo?: string; replyTo?: string;
} }
// 联系人列表响应接口
export interface ContactListResponse {
contacts: ContactData[];
total: number;
}
// 搜索联系人请求接口 // 搜索联系人请求接口
export interface SearchContactRequest { export interface SearchContactRequest {
keyword: string; keyword: string;

View File

@@ -1,19 +1,17 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect } from "react";
import { Layout, Button, Space, message, Tooltip } from "antd"; import { Layout, Button, Space, message, Tooltip } from "antd";
import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons"; import { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { ContactData, MessageData, ChatSession } from "./data"; import { ChatSession } from "./data";
import { ContactData } from "./components/SidebarMenu/data";
import ChatWindow from "./components/ChatWindow/index"; import ChatWindow from "./components/ChatWindow/index";
import SidebarMenu from "./components/SidebarMenu/index"; import SidebarMenu from "./components/SidebarMenu/index";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { getContactList, getChatMessage } from "./api";
const { Content } = Layout;
import { loginWithToken, getChuKeBaoUserInfo } from "@/pages/login/api"; const { Content } = Layout;
import { useCkChatStore } from "@/store/module/ckchat"; import { loginWithToken } from "@/pages/login/api";
import { useUserStore } from "@/store/module/user"; import { useUserStore } from "@/store/module/user";
import { useWebSocketStore } from "@/store/module/websocket"; import { chatInitAPIdata } from "./main";
import { chatInitAPIdata, getChatInfo } from "./main";
const CkboxPage: React.FC = () => { const CkboxPage: React.FC = () => {
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
@@ -22,12 +20,19 @@ const CkboxPage: React.FC = () => {
const [currentChat, setCurrentChat] = useState<ChatSession | null>(null); const [currentChat, setCurrentChat] = useState<ChatSession | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showProfile, setShowProfile] = useState(true); const [showProfile, setShowProfile] = useState(true);
const { setUserInfo, getAccountId } = useCkChatStore();
const { login2 } = useUserStore(); const { login2 } = useUserStore();
useEffect(() => { useEffect(() => {
const contactList = chatInitAPIdata(); // 方法一:使用 Promise 链式调用处理异步函数
console.log(contactList); chatInitAPIdata()
.then(contactList => {
console.log(contactList);
// 如果需要可以设置联系人列表
setContacts(contactList);
})
.catch(error => {
console.error("获取联系人列表失败:", error);
});
}, []); }, []);
const getToken2 = () => { const getToken2 = () => {
@@ -48,116 +53,6 @@ const CkboxPage: React.FC = () => {
}); });
}; };
const fetchContacts = async () => {
try {
setLoading(true);
// 使用API获取联系人数据
const response = await getContactList({ prevId: 0, count: 500 });
if (response) {
// 转换API返回的数据结构为组件所需的ContactData结构
const contactList: ContactData[] = response.map((item: any) => ({
id: item.id.toString(),
name: item.nickname || item.conRemark || item.alias || "",
phone: item.phone || "",
avatar: item.avatar || "",
online: true, // 假设所有联系人都在线实际应根据API返回数据调整
status: "在线", // 假设状态实际应根据API返回数据调整
department: "", // API中没有对应字段可以根据需要添加
position: "", // API中没有对应字段可以根据需要添加
}));
setContacts(contactList);
}
} catch (error) {
messageApi.error("获取联系人失败");
console.error("获取联系人失败:", error);
} finally {
setLoading(false);
}
};
const fetchChatSessions = async () => {
try {
// 先确保联系人列表已加载
if (contacts.length === 0) {
await fetchContacts();
}
const response = await getChatMessage({
wechatAccountId: 32686452, // 使用实际的wechatAccountId
wechatFriendId: 0, // 可以设置为0获取所有好友的消息
From: 0,
To: 0,
Count: 50, // 获取最近的50条消息
olderData: false,
keyword: "",
});
console.log("聊天消息响应:", response);
if (response && Array.isArray(response)) {
// 创建一个Map来存储每个好友ID对应的最新消息
const friendMessageMap = new Map<number, any>();
// 遍历所有消息,只保留每个好友的最新消息
response.forEach(item => {
const friendId = item.wechatFriendId;
const existingMessage = friendMessageMap.get(friendId);
// 如果Map中没有这个好友的消息或者当前消息比已存在的更新则更新Map
if (
!existingMessage ||
new Date(item.createTime) > new Date(existingMessage.createTime)
) {
friendMessageMap.set(friendId, item);
}
});
// 将Map转换为数组
const latestMessages = Array.from(friendMessageMap.values());
// 将API返回的消息记录转换为ChatSession格式
const sessions: ChatSession[] = latestMessages.map(item => {
// 解析content字段它是一个JSON字符串
let msgContent = "";
try {
const contentObj = JSON.parse(item.content);
msgContent = contentObj.content || "";
} catch (e) {
msgContent = item.content;
}
// 尝试从联系人列表中找到对应的联系人信息
const contact = contacts.find(
c => c.id === item.wechatFriendId.toString(),
);
return {
id: item.id.toString(),
type: "private", // 假设都是私聊
name: contact ? contact.name : `联系人 ${item.wechatFriendId}`, // 使用联系人名称或默认名称
avatar: contact?.avatar || "", // 使用联系人头像或默认空字符串
lastMessage: msgContent,
lastTime:
item.createTime || new Date(item.wechatTime).toISOString(),
unreadCount: 0, // 未读消息数需要另外获取
online: contact?.online || false, // 使用联系人在线状态或默认为false
};
});
// 按最后消息时间排序
sessions.sort(
(a, b) =>
new Date(b.lastTime).getTime() - new Date(a.lastTime).getTime(),
);
setChatSessions(sessions);
}
} catch (error) {
console.error("获取聊天记录失败:", error);
messageApi.error("获取聊天记录失败");
}
};
const handleContactClick = (contact: ContactData) => { const handleContactClick = (contact: ContactData) => {
// 查找或创建聊天会话 // 查找或创建聊天会话
let session = chatSessions.find(s => s.id === contact.id); let session = chatSessions.find(s => s.id === contact.id);
@@ -165,7 +60,7 @@ const CkboxPage: React.FC = () => {
session = { session = {
id: contact.id, id: contact.id,
type: "private", type: "private",
name: contact.name, name: contact.nickname,
avatar: contact.avatar, avatar: contact.avatar,
lastMessage: "", lastMessage: "",
lastTime: dayjs().toISOString(), lastTime: dayjs().toISOString(),
@@ -181,16 +76,6 @@ const CkboxPage: React.FC = () => {
if (!currentChat || !message.trim()) return; if (!currentChat || !message.trim()) return;
try { try {
const newMessage: MessageData = {
id: Date.now().toString(),
senderId: "me",
senderName: "我",
content: message,
type: "text" as any,
timestamp: dayjs().toISOString(),
isRead: false,
};
// 更新当前聊天会话 // 更新当前聊天会话
const updatedSession = { const updatedSession = {
...currentChat, ...currentChat,

View File

@@ -9,30 +9,35 @@ const { connect } = useWebSocketStore.getState();
const { setUserInfo, getAccountId } = useCkChatStore.getState(); const { setUserInfo, getAccountId } = useCkChatStore.getState();
//获取触客宝基础信息 //获取触客宝基础信息
export const chatInitAPIdata = async () => { export const chatInitAPIdata = async () => {
//获取Token try {
const Token = await getToken(); //获取Token
//获取用户信息 const Token = await getToken();
const userInfo = await getChuKeBaoUserInfo(); //获取用户信息
setUserInfo(userInfo); const userInfo = await getChuKeBaoUserInfo();
setUserInfo(userInfo);
//获取用户账号Id //获取用户账号Id
const accountId = getAccountId(); const accountId = getAccountId();
//发起链接 //发起链接
connect({ connect({
accessToken: String(Token), accessToken: String(Token),
accountId: accountId, accountId: accountId,
client: "kefu-client", client: "kefu-client",
cmdType: "CmdSignIn", cmdType: "CmdSignIn",
seq: 1, seq: 1,
}); });
//获取联系人列表 //获取联系人列表
const contactList = await getContactList({ const contactList = await getContactList({
prevId: userInfo.tenant.tenantType, prevId: userInfo.tenant.tenantType,
count: 100, count: 100,
}); });
return contactList; return contactList;
} catch (error) {
console.error("获取联系人列表失败:", error);
return [];
}
}; };
export const getChatInfo = () => { export const getChatInfo = () => {

View File

@@ -114,8 +114,6 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
return; return;
} }
console.log("获取connect参数", getAccountId());
// 构建WebSocket URL // 构建WebSocket URL
const params = new URLSearchParams({ const params = new URLSearchParams({
client: fullConfig.client.toString(), client: fullConfig.client.toString(),