diff --git a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts index 8a5fdc68..02188e84 100644 --- a/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts +++ b/Cunkebao/src/pages/mobile/workspace/group-push/form/index.data.ts @@ -23,7 +23,7 @@ export interface FormData { name: string; startTime: string; // 允许推送的开始时间 endTime: string; // 允许推送的结束时间 - maxPerDay: number; + dailyPushCount: number; pushOrder: number; // 1: 按最早, 2: 按最新 isLoop: number; // 0: 否, 1: 是 pushType: number; // 0: 定时推送, 1: 立即推送 diff --git a/Cunkebao/src/pages/pc/ckbox/api.ts b/Cunkebao/src/pages/pc/ckbox/api.ts index c642da36..3ea80e5b 100644 --- a/Cunkebao/src/pages/pc/ckbox/api.ts +++ b/Cunkebao/src/pages/pc/ckbox/api.ts @@ -14,6 +14,10 @@ import { //读取聊天信息 //kf.quwanzhi.com:9991/api/WechatFriend/clearUnreadCount +export function WechatGroup(params) { + return request("/api/WechatGroup/list", params, "GET"); +} + //获取聊天记录-1 清除未读 export function clearUnreadCount(params) { return request("/api/WechatFriend/clearUnreadCount", params, "PUT"); @@ -30,7 +34,7 @@ export function getMessages(params: { return request("/api/FriendMessage/SearchMessage", params, "GET"); } //获取群列表 -export function getChatRoomList(params: { prevId: number; count: number }) { +export function getGroupList(params: { prevId: number; count: number }) { return request( "/api/wechatChatroom/listExcludeMembersByPage?", params, 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 f00994de..3a4058ae 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/MessageList/index.tsx @@ -4,7 +4,7 @@ import { UserOutlined, TeamOutlined } from "@ant-design/icons"; import dayjs from "dayjs"; import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; import styles from "./MessageList.module.scss"; - +import { formatWechatTime } from "@/utils/common"; interface MessageListProps { chatSessions: ContractData[]; currentChat: ContractData; @@ -16,22 +16,6 @@ const MessageList: React.FC = ({ currentChat, onChatSelect, }) => { - const formatTime = (timestamp: string) => { - const now = dayjs(); - const messageTime = dayjs(timestamp); - const diffDays = now.diff(messageTime, "day"); - - if (diffDays === 0) { - return messageTime.format("HH:mm"); - } else if (diffDays === 1) { - return "昨天"; - } else if (diffDays < 7) { - return messageTime.format("ddd"); - } else { - return messageTime.format("MM-DD"); - } - }; - return (
= ({ @@ -61,7 +45,7 @@ const MessageList: React.FC = ({
{session.nickname}
- {formatTime(session?.lastTime || "")} + {formatWechatTime(session?.lastUpdateTime)}
diff --git a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx index 36cbf763..41bd4da7 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/SidebarMenu/index.tsx @@ -31,7 +31,7 @@ const SidebarMenu: React.FC = ({ const chatSessions = getChatSessions(); const [searchText, setSearchText] = useState(""); - const [activeTab, setActiveTab] = useState("contracts"); + const [activeTab, setActiveTab] = useState("chats"); const handleSearch = (value: string) => { setSearchText(value); diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx index bc10cfa1..31112010 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx @@ -1,29 +1,17 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { Avatar, Badge, Tooltip } from "antd"; import styles from "./VerticalUserList.module.scss"; -import { CkChatCtrlUserData } from "@/store/module/ckchat.data"; -interface UserItem { - id: string; - name: string; - avatar: string; - messageCount?: number; - isOnline?: boolean; -} - +import { useCkChatStore } from "@/store/module/ckchat"; interface VerticalUserListProps { + activeKfUserId: number; onUserSelect: (userId: string) => void; } -import { getCtrlUserList, useCkChatStore } from "@/store/module/ckchat"; const VerticalUserList: React.FC = ({ + activeKfUserId, onUserSelect, }) => { - // 直接从store获取ctrlUserList,这样当store中的数据更新时,组件会自动重新渲染 - const ctrlUserList = useCkChatStore(state => state.ctrlUserList); - const [activeUserId, setActiveUserId] = useState(); + const [activeUserId, setActiveUserId] = useState(activeKfUserId); - useEffect(() => { - console.log("控制终端用户列表更新:", ctrlUserList); - }, [ctrlUserList]); // 格式化消息数量显示 const formatMessageCount = (count: number) => { if (count > 99) return "99+"; @@ -34,6 +22,7 @@ const VerticalUserList: React.FC = ({ setActiveUserId(userId); onUserSelect(userId.toString()); }; + const kfUserList = useCkChatStore(state => state.kfUserList); return (
@@ -41,7 +30,7 @@ const VerticalUserList: React.FC = ({
全部好友
- {ctrlUserList.map(user => ( + {kfUserList.map(user => (
{ const [messageApi, contextHolder] = message.useMessage(); const [contracts, setContacts] = useState([]); const [currentChat, setCurrentChat] = useState(null); - const [activeVerticalUserId, setActiveVerticalUserId] = useState("all"); + const [activeVerticalUserId, setActiveVerticalUserId] = useState(0); const [loading, setLoading] = useState(false); const [showProfile, setShowProfile] = useState(true); @@ -75,8 +24,8 @@ const CkboxPage: React.FC = () => { // 方法一:使用 Promise 链式调用处理异步函数 setLoading(true); chatInitAPIdata() - .then((response: { contractList: any[]; chatRoomList: any[] }) => { - const { contractList, chatRoomList } = response; + .then(response => { + const { contractList, chatRoomList, kfUserList } = response; //找出已经在聊天的 const isChatList = contractList.filter( v => (v?.config && v.config?.chat) || false, @@ -133,10 +82,10 @@ const CkboxPage: React.FC = () => {
触客宝
{/* 垂直侧边栏 */} + @@ -148,7 +97,6 @@ const CkboxPage: React.FC = () => { currentChat={currentChat} onContactClick={handleContactClick} onChatSelect={setCurrentChat} - loading={loading} /> diff --git a/Cunkebao/src/pages/pc/ckbox/main.ts b/Cunkebao/src/pages/pc/ckbox/main.ts index 55bb7176..a0e7ab7c 100644 --- a/Cunkebao/src/pages/pc/ckbox/main.ts +++ b/Cunkebao/src/pages/pc/ckbox/main.ts @@ -1,62 +1,182 @@ -import { addCtrlUser, useCkChatStore } from "@/store/module/ckchat"; +import { + useCkChatStore, + asyncKfUserList, + asyncContractList, + asyncChatSessions, +} from "@/store/module/ckchat"; import { useWebSocketStore } from "@/store/module/websocket"; import { loginWithToken, getControlTerminalList, getContactList, - getChatRoomList, + getGroupList, + WechatGroup, } from "./api"; const { sendCommand } = useWebSocketStore.getState(); import { useUserStore } from "@/store/module/user"; -import { CkChatCtrlUserData } from "@/store/module/ckchat.data"; +import { KfUserListData } from "@/store/module/ckchat.data"; const { login2 } = useUserStore.getState(); -const { connect } = useWebSocketStore.getState(); -const { setUserInfo, getAccountId } = useCkChatStore.getState(); //获取触客宝基础信息 export const chatInitAPIdata = async () => { try { - // //发起链接 - // if (Token && accountId) { - // connect({ - // url: "wss://kf.quwanzhi.com:9993", // 显式指定WebSocket URL,确保使用正确的服务器地址 - // accessToken: String(Token), - // accountId: accountId, - // client: "kefu-client", - // cmdType: "CmdSignIn", - // seq: +new Date(), - // }); - // console.log("WebSocket连接已初始化"); - // } else { - // console.error("WebSocket连接初始化失败:缺少Token或accountId"); - // } //获取联系人列表 const contractList = await getAllContactList(); + //构建联系人列表标签 + const newContractList = await createContractList(contractList); + + //获取联系人列表 + asyncContractList(contractList); + // 提取不重复的wechatAccountId组 const uniqueWechatAccountIds: number[] = getUniqueWechatAccountIds(contractList); //获取控制终端列表 - const controlTerminalList: CkChatCtrlUserData[] = + const kfUserList: KfUserListData[] = await getControlTerminalListByWechatAccountIds(uniqueWechatAccountIds); - //添加控制终端用户 - controlTerminalList.forEach(item => { - addCtrlUser(item); - }); + //获取用户列表 + asyncKfUserList(kfUserList); //获取群列表 - const chatRoomList = await getAllChatRoomList(); + const groupList = await getAllGroupList(); + + //获取消息会话列表并按lastUpdateTime排序 + const filterUserSessions = contractList?.filter( + v => v?.config && v.config?.chat, + ); + const filterGroupSessions = groupList?.filter( + v => v?.config && v.config?.chat, + ); + //排序功能 + const sortedSessions = [...filterUserSessions, ...filterGroupSessions].sort( + (a, b) => { + // 如果lastUpdateTime不存在,则将其排在最后 + if (!a.lastUpdateTime) return 1; + if (!b.lastUpdateTime) return -1; + + // 首先按时间降序排列(最新的在前面) + const timeCompare = + new Date(b.lastUpdateTime).getTime() - + new Date(a.lastUpdateTime).getTime(); + + // 如果时间相同,则按未读消息数量降序排列 + if (timeCompare === 0) { + // 如果unreadCount不存在,则将其排在后面 + const aUnread = a.unreadCount || 0; + const bUnread = b.unreadCount || 0; + return bUnread - aUnread; // 未读消息多的排在前面 + } + + return timeCompare; + }, + ); + //会话数据同步 + asyncChatSessions(sortedSessions); + return { contractList, - chatRoomList, + groupList, + kfUserList, + newContractList, }; } catch (error) { console.error("获取联系人列表失败:", error); return []; } }; + +//构建联系人列表标签 +export const createContractList = async (contractList: any[]) => { + const LablesRes = await Promise.all( + [1, 2].map(item => + WechatGroup({ + groupType: item, + }), + ), + ); + const [friend, group] = LablesRes; + + const countLables = [...friend, ...group]; + + // 根据countLables中的groupName整理contractList数据 + // 返回按标签分组的联系人数组,包括未分组标签(在数组最后) + return organizeContactsByLabels(contractList, countLables); +}; + +/** + * 根据标签组织联系人 + * @param contractList 联系人列表 + * @param countLables 标签列表 + * @returns 按标签分组的联系人 + */ +export const organizeContactsByLabels = ( + contractList: any[], + countLables: any[], +) => { + // 创建结果对象,用于存储按标签分组的联系人 + const result: { [key: string]: any[] } = {}; + + // 初始化结果对象,为每个标签创建一个空数组 + countLables.forEach(label => { + if (label && label.groupName) { + result[label.groupName] = []; + } + }); + + // 创建未分组标签,用于存放没有匹配到任何标签的联系人 + const ungroupedLabel = "未分组"; + result[ungroupedLabel] = []; + + // 遍历联系人列表 + contractList.forEach(contact => { + // 确保联系人有labels字段且是数组 + if (contact && Array.isArray(contact.labels)) { + // 标记联系人是否已被分配到某个组 + let isAssigned = false; + + // 遍历标签列表 + countLables.forEach(label => { + if (label && label.groupName) { + // 检查联系人的labels是否包含当前标签的groupName + if (contact.labels.includes(label.groupName)) { + // 将联系人添加到对应标签的数组中 + result[label.groupName].push(contact); + isAssigned = true; + } + } + }); + + // 如果联系人没有被分配到任何组,则添加到未分组 + if (!isAssigned) { + result[ungroupedLabel].push(contact); + } + } else { + // 如果联系人没有labels字段或不是数组,也添加到未分组 + result[ungroupedLabel].push(contact); + } + }); + + // 将结果转换为数组格式,确保未分组在最后 + const resultArray = Object.entries(result).map(([groupName, contacts]) => ({ + groupName, + contacts, + })); + + // 将未分组移到数组末尾 + const ungroupedIndex = resultArray.findIndex( + item => item.groupName === ungroupedLabel, + ); + if (ungroupedIndex !== -1) { + const ungrouped = resultArray.splice(ungroupedIndex, 1)[0]; + resultArray.push(ungrouped); + } + + return resultArray; +}; + //获取控制终端列表 export const getControlTerminalListByWechatAccountIds = ( WechatAccountIds: number[], @@ -126,7 +246,7 @@ export const getUniqueWechatAccountIds = contacts => { return Array.from(uniqueAccountIdsSet); }; // 递归获取所有群列表 -export const getAllChatRoomList = async () => { +export const getAllGroupList = async () => { try { let allContacts = []; let prevId = 0; @@ -134,7 +254,7 @@ export const getAllChatRoomList = async () => { let hasMore = true; while (hasMore) { - const contractList = await getChatRoomList({ + const contractList = await getGroupList({ prevId, count, }); diff --git a/Cunkebao/src/store/module/ckchat.data.ts b/Cunkebao/src/store/module/ckchat.data.ts index 8d026158..28c1c708 100644 --- a/Cunkebao/src/store/module/ckchat.data.ts +++ b/Cunkebao/src/store/module/ckchat.data.ts @@ -1,61 +1,27 @@ +import { ContractData } from "../../pages/pc/ckbox/data"; + //终端用户数据接口 -export interface CkChatCtrlUserData { - /** 用户唯一标识ID */ +export interface KfUserListData { id: number; - /** 租户ID(多租户系统中用于区分租户) */ tenantId: number; - /** 微信ID(用户微信账号唯一标识) */ wechatId: string; - /** 用户昵称 */ nickname: string; - /** 用户别名/备注名(自定义标识) */ alias: string; - /** 头像图片URL(阿里云OSS存储地址) */ avatar: string; - /** - * 性别标识(0:未知/未设置,1:男,2:女,符合微信性别字段定义) - * 原始数据中为0,此处标注可能的枚举值以便后续扩展 - */ - gender: 0 | 1 | 2; - /** 地区信息(原始数据为空字符串,未填写) */ + gender: number; region: string; - /** 个性签名(原始数据为空字符串,未填写) */ signature: string; - /** 绑定的QQ号(0表示未绑定) */ bindQQ: string; - /** 绑定的邮箱(原始数据为空字符串,未绑定) */ bindEmail: string; - /** 绑定的手机号(原始数据为空字符串,未绑定) */ bindMobile: string; - /** - * 用户创建时间(注册时间) - * 格式:ISO 8601标准时间字符串(YYYY-MM-DDTHH:mm:ss.fffffff) - */ createTime: string; - /** 当前登录设备ID(关联用户使用的设备唯一标识) */ currentDeviceId: number; - /** 是否删除(逻辑删除标识,false:未删除,true:已删除) */ isDeleted: boolean; - /** - * 删除时间(逻辑删除时记录,未删除时为默认初始时间"0001-01-01T00:00:00") - * 格式:ISO 8601标准时间字符串 - */ deleteTime: string; - /** 用户所属群组ID(用于群组分类管理) */ groupId: number; - /** 系统内对用户的备注信息(补充标识) */ memo: string; - /** 用户当前使用的微信版本号 */ wechatVersion: string; - /** - * 用户标签列表(用于分类、筛选,涵盖:业务类型、互动记录、身份标识等维度) - * 例如:"团队"(身份)、"未成交"(业务状态)、"抖音"(渠道)等 - */ labels: string[]; - /** - * 最后更新时间(用户信息最近修改时间) - * 格式:ISO 8601标准时间字符串 - */ lastUpdateTime: string; [key: string]: any; } @@ -102,6 +68,20 @@ export interface CkUserInfo { export interface CkChatState { userInfo: CkUserInfo | null; isLoggedIn: boolean; + contractList: ContractData[]; + chatSessions: any[]; + kfUserList: KfUserListData[]; + getkfUserList: () => KfUserListData[]; + asyncKfUserList: (data: KfUserListData[]) => void; + asyncContractList: (data: ContractData[]) => void; + asyncChatSessions: (data: any[]) => 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; updateAccount: (account: Partial) => void; diff --git a/Cunkebao/src/store/module/ckchat.ts b/Cunkebao/src/store/module/ckchat.ts index b7f9a26e..13289ac5 100644 --- a/Cunkebao/src/store/module/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat.ts @@ -5,40 +5,50 @@ import { CkUserInfo, CkAccount, CkTenant, - CkChatCtrlUserData, + KfUserListData, } from "./ckchat.data"; import { ContractData, GroupData } from "@/pages/pc/ckbox/data"; export const useCkChatStore = createPersistStore( set => ({ userInfo: null, isLoggedIn: false, - chatSessions: [], - ctrlUserList: [], - // 控制终端用户列表 - getCtrlUserList: () => { - const state = useCkChatStore.getState(); - return state.ctrlUserList; + contractList: [], //联系人列表 + chatSessions: [], //聊天会话 + kfUserList: [], //客服列表 + // 异步设置会话列表 + asyncChatSessions: data => { + set({ chatSessions: data }); }, - // 添加控制终端用户 - addCtrlUser: (user: CkChatCtrlUserData) => { - set(state => ({ - ctrlUserList: [...state.ctrlUserList, user], - })); + // 异步设置联系人列表 + asyncContractList: data => { + set({ contractList: data }); + }, + // 控制终端用户列表 + getkfUserList: () => { + const state = useCkChatStore.getState(); + return state.kfUserList; + }, + asyncKfUserList: data => { + set({ kfUserList: data }); }, // 删除控制终端用户 deleteCtrlUser: (userId: number) => { set(state => ({ - ctrlUserList: state.ctrlUserList.filter(item => item.id !== userId), + kfUserList: state.kfUserList.filter(item => item.id !== userId), })); }, // 更新控制终端用户 - updateCtrlUser: (user: CkChatCtrlUserData) => { + updateCtrlUser: (user: KfUserListData) => { set(state => ({ - ctrlUserList: state.ctrlUserList.map(item => + kfUserList: state.kfUserList.map(item => item.id === user.id ? user : item, ), })); }, + // 清空控制终端用户列表 + clearkfUserList: () => { + set({ kfUserList: [] }); + }, // 获取聊天会话 getChatSessions: () => { const state = useCkChatStore.getState(); @@ -159,10 +169,16 @@ export const updateChatSession = (session: ContractData | GroupData) => useCkChatStore.getState().updateChatSession(session); export const deleteChatSession = (sessionId: string) => useCkChatStore.getState().deleteChatSession(sessionId); -export const getCtrlUserList = () => useCkChatStore.getState().ctrlUserList; -export const addCtrlUser = (user: CkChatCtrlUserData) => +export const getkfUserList = () => useCkChatStore.getState().kfUserList; +export const addCtrlUser = (user: KfUserListData) => useCkChatStore.getState().addCtrlUser(user); export const deleteCtrlUser = (userId: number) => useCkChatStore.getState().deleteCtrlUser(userId); -export const updateCtrlUser = (user: CkChatCtrlUserData) => +export const updateCtrlUser = (user: KfUserListData) => useCkChatStore.getState().updateCtrlUser(user); +export const asyncKfUserList = (data: KfUserListData[]) => + useCkChatStore.getState().asyncKfUserList(data); +export const asyncContractList = (data: ContractData[]) => + useCkChatStore.getState().asyncContractList(data); +export const asyncChatSessions = (data: ContractData[]) => + useCkChatStore.getState().asyncChatSessions(data); diff --git a/Cunkebao/src/utils/common.ts b/Cunkebao/src/utils/common.ts index 65db3b71..fd740223 100644 --- a/Cunkebao/src/utils/common.ts +++ b/Cunkebao/src/utils/common.ts @@ -1,6 +1,9 @@ import { Modal } from "antd-mobile"; import { getSetting } from "@/store/module/settings"; export function formatWechatTime(timestamp) { + if (!timestamp) { + return ""; + } // 处理时间戳(兼容秒级/毫秒级) const date = new Date( timestamp.toString().length === 10 ? timestamp * 1000 : timestamp,