FEAT => 本次更新项目为:
This commit is contained in:
@@ -78,16 +78,16 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
const fetchChatHistory = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
|
||||
// 从chat对象中提取wechatFriendId
|
||||
// 假设chat.id存储的是wechatFriendId
|
||||
const wechatFriendId = parseInt(chat.id);
|
||||
|
||||
|
||||
if (isNaN(wechatFriendId)) {
|
||||
messageApi.error("无效的聊天ID");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 调用API获取聊天历史
|
||||
const response = await getChatMessage({
|
||||
wechatAccountId: 32686452, // 使用实际的wechatAccountId
|
||||
@@ -98,9 +98,9 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
olderData: false,
|
||||
keyword: "",
|
||||
});
|
||||
|
||||
|
||||
console.log("聊天历史响应:", response);
|
||||
|
||||
|
||||
if (response && Array.isArray(response)) {
|
||||
// 将API返回的消息记录转换为MessageData格式
|
||||
const chatMessages: MessageData[] = response.map(item => {
|
||||
@@ -112,24 +112,28 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
|
||||
} catch (e) {
|
||||
msgContent = item.content;
|
||||
}
|
||||
|
||||
|
||||
// 判断消息是发送还是接收
|
||||
const isSend = item.isSend === true;
|
||||
|
||||
|
||||
return {
|
||||
id: item.id.toString(),
|
||||
senderId: isSend ? "me" : "other",
|
||||
senderName: isSend ? "我" : chat.name,
|
||||
content: msgContent,
|
||||
type: MessageType.TEXT, // 默认为文本类型,实际应根据msgType字段判断
|
||||
timestamp: item.createTime || new Date(item.wechatTime).toISOString(),
|
||||
timestamp:
|
||||
item.createTime || new Date(item.wechatTime).toISOString(),
|
||||
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);
|
||||
} else {
|
||||
// 如果没有消息,显示空数组
|
||||
|
||||
@@ -60,4 +60,4 @@
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import { List, Avatar, Badge } from "antd";
|
||||
import { ContactData } from "../../data";
|
||||
import { ContactData } from "./data";
|
||||
import styles from "./ContactListSimple.module.scss";
|
||||
|
||||
interface ContactListSimpleProps {
|
||||
contacts: ContactData[];
|
||||
onContactClick: (contact: ContactData) => void;
|
||||
selectedContactId?: string;
|
||||
selectedContactId?: number;
|
||||
}
|
||||
|
||||
const ContactListSimple: React.FC<ContactListSimpleProps> = ({
|
||||
@@ -31,14 +31,14 @@ const ContactListSimple: React.FC<ContactListSimpleProps> = ({
|
||||
<Avatar
|
||||
src={contact.avatar}
|
||||
icon={
|
||||
!contact.avatar && <span>{contact.name.charAt(0)}</span>
|
||||
!contact.avatar && <span>{contact.nickname.charAt(0)}</span>
|
||||
}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
</Badge>
|
||||
</div>
|
||||
<div className={styles.contactInfo}>
|
||||
<div className={styles.name}>{contact.name}</div>
|
||||
<div className={styles.name}>{contact.nickname}</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
47
Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
Normal file
47
Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/data.ts
Normal 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;
|
||||
}
|
||||
@@ -6,12 +6,13 @@ import {
|
||||
TeamOutlined,
|
||||
MessageOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { ContactData, ChatSession } from "../../data";
|
||||
import ContactListSimple from "./ContactListSimple";
|
||||
import { ContactData, ChatSession } from "./data";
|
||||
import WechatFriendsModule from "./WechatFriendsModule";
|
||||
import MessageList from "../MessageList/index";
|
||||
import styles from "./SidebarMenu.module.scss";
|
||||
|
||||
const { Sider } = Layout;
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
interface SidebarMenuProps {
|
||||
contacts: ContactData[];
|
||||
@@ -41,7 +42,7 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({
|
||||
if (!searchText) return contacts;
|
||||
return contacts.filter(
|
||||
contact =>
|
||||
contact.name.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
contact.nickname.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
contact.phone.includes(searchText),
|
||||
);
|
||||
};
|
||||
@@ -71,56 +72,52 @@ const SidebarMenu: React.FC<SidebarMenuProps> = ({
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
className={styles.tabs}
|
||||
items={[
|
||||
{
|
||||
key: "chats",
|
||||
label: (
|
||||
<span>
|
||||
<MessageOutlined />
|
||||
聊天
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<MessageList
|
||||
sessions={getFilteredSessions()}
|
||||
currentChat={currentChat}
|
||||
onChatSelect={onChatSelect}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "contacts",
|
||||
label: (
|
||||
<span>
|
||||
<UserOutlined />
|
||||
联系人
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<ContactListSimple
|
||||
contacts={getFilteredContacts()}
|
||||
onContactClick={onContactClick}
|
||||
selectedContactId={currentChat?.id.split("_")[1]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "groups",
|
||||
label: (
|
||||
<span>
|
||||
<TeamOutlined />
|
||||
群组
|
||||
</span>
|
||||
),
|
||||
children: (
|
||||
<div className={styles.emptyState}>
|
||||
<TeamOutlined style={{ fontSize: 48, color: "#ccc" }} />
|
||||
<p>暂无群组</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<MessageOutlined />
|
||||
聊天
|
||||
</span>
|
||||
}
|
||||
key="chats"
|
||||
>
|
||||
<MessageList
|
||||
sessions={getFilteredSessions()}
|
||||
currentChat={currentChat}
|
||||
onChatSelect={onChatSelect}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<UserOutlined />
|
||||
联系人
|
||||
</span>
|
||||
}
|
||||
key="contacts"
|
||||
>
|
||||
<WechatFriendsModule
|
||||
contacts={getFilteredContacts()}
|
||||
onContactClick={onContactClick}
|
||||
selectedContactId={currentChat?.id.split("_")[1]}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane
|
||||
tab={
|
||||
<span>
|
||||
<TeamOutlined />
|
||||
群组
|
||||
</span>
|
||||
}
|
||||
key="groups"
|
||||
>
|
||||
<div className={styles.emptyState}>
|
||||
<TeamOutlined style={{ fontSize: 48, color: "#ccc" }} />
|
||||
<p>暂无群组</p>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Sider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,52 @@
|
||||
// 联系人数据接口
|
||||
export interface ContactData {
|
||||
id: string;
|
||||
name: string;
|
||||
phone: string;
|
||||
id?: number;
|
||||
wechatAccountId: number;
|
||||
wechatId: string;
|
||||
alias: string;
|
||||
conRemark: string;
|
||||
nickname: string;
|
||||
quanPin: string;
|
||||
avatar?: string;
|
||||
online: boolean;
|
||||
lastSeen?: string;
|
||||
status?: string;
|
||||
department?: string;
|
||||
position?: 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 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;
|
||||
}
|
||||
|
||||
// 群组信息接口
|
||||
export interface GroupData {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
description?: string;
|
||||
members: ContactData[];
|
||||
adminIds: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// 聊天历史响应接口
|
||||
export interface ChatHistoryResponse {
|
||||
messages: MessageData[];
|
||||
@@ -79,12 +105,6 @@ export interface SendMessageRequest {
|
||||
replyTo?: string;
|
||||
}
|
||||
|
||||
// 联系人列表响应接口
|
||||
export interface ContactListResponse {
|
||||
contacts: ContactData[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
// 搜索联系人请求接口
|
||||
export interface SearchContactRequest {
|
||||
keyword: string;
|
||||
|
||||
@@ -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 { InfoCircleOutlined, MessageOutlined } from "@ant-design/icons";
|
||||
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 SidebarMenu from "./components/SidebarMenu/index";
|
||||
import styles from "./index.module.scss";
|
||||
import { getContactList, getChatMessage } from "./api";
|
||||
const { Content } = Layout;
|
||||
|
||||
import { loginWithToken, getChuKeBaoUserInfo } from "@/pages/login/api";
|
||||
import { useCkChatStore } from "@/store/module/ckchat";
|
||||
const { Content } = Layout;
|
||||
import { loginWithToken } from "@/pages/login/api";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useWebSocketStore } from "@/store/module/websocket";
|
||||
import { chatInitAPIdata, getChatInfo } from "./main";
|
||||
import { chatInitAPIdata } from "./main";
|
||||
|
||||
const CkboxPage: React.FC = () => {
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
@@ -22,12 +20,19 @@ const CkboxPage: React.FC = () => {
|
||||
const [currentChat, setCurrentChat] = useState<ChatSession | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showProfile, setShowProfile] = useState(true);
|
||||
const { setUserInfo, getAccountId } = useCkChatStore();
|
||||
const { login2 } = useUserStore();
|
||||
|
||||
useEffect(() => {
|
||||
const contactList = chatInitAPIdata();
|
||||
console.log(contactList);
|
||||
// 方法一:使用 Promise 链式调用处理异步函数
|
||||
chatInitAPIdata()
|
||||
.then(contactList => {
|
||||
console.log(contactList);
|
||||
// 如果需要可以设置联系人列表
|
||||
setContacts(contactList);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("获取联系人列表失败:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
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) => {
|
||||
// 查找或创建聊天会话
|
||||
let session = chatSessions.find(s => s.id === contact.id);
|
||||
@@ -165,7 +60,7 @@ const CkboxPage: React.FC = () => {
|
||||
session = {
|
||||
id: contact.id,
|
||||
type: "private",
|
||||
name: contact.name,
|
||||
name: contact.nickname,
|
||||
avatar: contact.avatar,
|
||||
lastMessage: "",
|
||||
lastTime: dayjs().toISOString(),
|
||||
@@ -181,16 +76,6 @@ const CkboxPage: React.FC = () => {
|
||||
if (!currentChat || !message.trim()) return;
|
||||
|
||||
try {
|
||||
const newMessage: MessageData = {
|
||||
id: Date.now().toString(),
|
||||
senderId: "me",
|
||||
senderName: "我",
|
||||
content: message,
|
||||
type: "text" as any,
|
||||
timestamp: dayjs().toISOString(),
|
||||
isRead: false,
|
||||
};
|
||||
|
||||
// 更新当前聊天会话
|
||||
const updatedSession = {
|
||||
...currentChat,
|
||||
|
||||
@@ -9,30 +9,35 @@ const { connect } = useWebSocketStore.getState();
|
||||
const { setUserInfo, getAccountId } = useCkChatStore.getState();
|
||||
//获取触客宝基础信息
|
||||
export const chatInitAPIdata = async () => {
|
||||
//获取Token
|
||||
const Token = await getToken();
|
||||
//获取用户信息
|
||||
const userInfo = await getChuKeBaoUserInfo();
|
||||
setUserInfo(userInfo);
|
||||
try {
|
||||
//获取Token
|
||||
const Token = await getToken();
|
||||
//获取用户信息
|
||||
const userInfo = await getChuKeBaoUserInfo();
|
||||
setUserInfo(userInfo);
|
||||
|
||||
//获取用户账号Id
|
||||
const accountId = getAccountId();
|
||||
//获取用户账号Id
|
||||
const accountId = getAccountId();
|
||||
|
||||
//发起链接
|
||||
connect({
|
||||
accessToken: String(Token),
|
||||
accountId: accountId,
|
||||
client: "kefu-client",
|
||||
cmdType: "CmdSignIn",
|
||||
seq: 1,
|
||||
});
|
||||
//获取联系人列表
|
||||
const contactList = await getContactList({
|
||||
prevId: userInfo.tenant.tenantType,
|
||||
count: 100,
|
||||
});
|
||||
//发起链接
|
||||
connect({
|
||||
accessToken: String(Token),
|
||||
accountId: accountId,
|
||||
client: "kefu-client",
|
||||
cmdType: "CmdSignIn",
|
||||
seq: 1,
|
||||
});
|
||||
//获取联系人列表
|
||||
const contactList = await getContactList({
|
||||
prevId: userInfo.tenant.tenantType,
|
||||
count: 100,
|
||||
});
|
||||
|
||||
return contactList;
|
||||
return contactList;
|
||||
} catch (error) {
|
||||
console.error("获取联系人列表失败:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getChatInfo = () => {
|
||||
|
||||
@@ -114,8 +114,6 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("获取connect参数", getAccountId());
|
||||
|
||||
// 构建WebSocket URL
|
||||
const params = new URLSearchParams({
|
||||
client: fullConfig.client.toString(),
|
||||
|
||||
Reference in New Issue
Block a user