优化消息列表组件,新增消息内容展示,调整消息时间格式处理逻辑,更新获取消息列表的API以支持分页,提升用户体验和代码可读性。
This commit is contained in:
@@ -217,10 +217,7 @@ const NavCommon: React.FC<NavCommonProps> = ({ title = "触客宝" }) => {
|
|||||||
icon={<BarChartOutlined style={{ fontSize: 18 }} />}
|
icon={<BarChartOutlined style={{ fontSize: 18 }} />}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
>
|
></Button>
|
||||||
切换到{isWeChat() ? "功能中心" : "Ai智能客服"}
|
|
||||||
<RetweetOutlined style={{ fontSize: 18 }} />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
icon={<CalendarOutlined />}
|
icon={<CalendarOutlined />}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ export function getLabelsListByGroup(params) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
//群、好友聊天记录列表
|
//群、好友聊天记录列表
|
||||||
export function getMessageList() {
|
export function getMessageList(params: { page: number; limit: number }) {
|
||||||
return request("/v1/kefu/message/list", {}, "GET");
|
return request("/v1/kefu/message/list", params, "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取客服列表
|
//获取客服列表
|
||||||
|
|||||||
@@ -76,51 +76,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.messageContent {
|
.messageContent {
|
||||||
display: flex;
|
font-size: 12px;
|
||||||
justify-content: space-between;
|
color: #888;
|
||||||
align-items: center;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
.lastMessage {
|
white-space: nowrap;
|
||||||
font-size: 12px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
padding-right: 5px;
|
|
||||||
height: 18px; // 添加固定高度
|
|
||||||
line-height: 18px; // 设置行高与高度一致
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: attr(data-count);
|
|
||||||
position: absolute;
|
|
||||||
right: -5px;
|
|
||||||
top: 0;
|
|
||||||
background-color: #ff4d4f;
|
|
||||||
color: white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 0 6px;
|
|
||||||
font-size: 10px;
|
|
||||||
line-height: 16px;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
text-align: center;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-count]:not([data-count=""]):not([data-count="0"]) {
|
|
||||||
&::before {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.onlineIndicator {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #52c41a;
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
{formatWechatTime(session?.lastUpdateTime)}
|
{formatWechatTime(session?.lastUpdateTime)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.messageContent}>{session.content}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { useUserStore } from "@/store/module/user";
|
|||||||
import { weChatGroupService, contractService } from "@/utils/db";
|
import { weChatGroupService, contractService } from "@/utils/db";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loginWithToken,
|
|
||||||
getControlTerminalList,
|
getControlTerminalList,
|
||||||
getContactList,
|
getContactList,
|
||||||
getGroupList,
|
getGroupList,
|
||||||
@@ -28,7 +27,6 @@ import {
|
|||||||
weChatGroup,
|
weChatGroup,
|
||||||
} from "@/pages/pc/ckbox/data";
|
} from "@/pages/pc/ckbox/data";
|
||||||
|
|
||||||
const { login2 } = useUserStore.getState();
|
|
||||||
//获取触客宝基础信息
|
//获取触客宝基础信息
|
||||||
export const chatInitAPIdata = async () => {
|
export const chatInitAPIdata = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -64,60 +62,51 @@ export const chatInitAPIdata = async () => {
|
|||||||
const countLables = await getCountLables();
|
const countLables = await getCountLables();
|
||||||
await asyncCountLables(countLables);
|
await asyncCountLables(countLables);
|
||||||
|
|
||||||
//获取消息会话列表并按lastUpdateTime排序
|
|
||||||
const filterUserSessions = contractList?.filter(
|
|
||||||
v => v?.config && v.config?.chat,
|
|
||||||
);
|
|
||||||
const filterGroupSessions = groupList?.filter(
|
|
||||||
v => v?.config && v.config?.chat,
|
|
||||||
);
|
|
||||||
// 获取显示名称的辅助函数(优先 conRemark,其次 nickname)
|
// 获取显示名称的辅助函数(优先 conRemark,其次 nickname)
|
||||||
const getDisplayName = (session: any) => {
|
const getDisplayName = (session: any) => {
|
||||||
return session.conRemark || session.nickname || "";
|
return session.conRemark || session.nickname || "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageList = await getMessageList();
|
const messageList = await getAllMessageList();
|
||||||
console.log(messageList);
|
|
||||||
//排序功能
|
//排序功能
|
||||||
const sortedSessions = [...filterUserSessions, ...filterGroupSessions].sort(
|
const sortedSessions = messageList.sort((a, b) => {
|
||||||
(a, b) => {
|
// 获取置顶状态
|
||||||
// 获取置顶状态
|
const aTop = a.config?.top || false;
|
||||||
const aTop = a.config?.top || false;
|
const bTop = b.config?.top || false;
|
||||||
const bTop = b.config?.top || false;
|
|
||||||
|
|
||||||
// 首先按置顶状态排序(置顶的排在前面)
|
// 首先按置顶状态排序(置顶的排在前面)
|
||||||
if (aTop !== bTop) {
|
if (aTop !== bTop) {
|
||||||
return aTop ? -1 : 1;
|
return aTop ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果都是置顶或都不是置顶,则按未读消息数量降序排列(未读消息多的排在前面)
|
// 如果都是置顶或都不是置顶,则按未读消息数量降序排列(未读消息多的排在前面)
|
||||||
const aUnread = a.config?.unreadCount || 0;
|
const aUnread = a.config?.unreadCount || 0;
|
||||||
const bUnread = b.config?.unreadCount || 0;
|
const bUnread = b.config?.unreadCount || 0;
|
||||||
|
|
||||||
if (aUnread !== bUnread) {
|
if (aUnread !== bUnread) {
|
||||||
return bUnread - aUnread;
|
return bUnread - aUnread;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未读消息数量相同,则按显示名称排序(字母排序)
|
// 如果未读消息数量相同,则按显示名称排序(字母排序)
|
||||||
const aName = getDisplayName(a).toLowerCase();
|
const aName = getDisplayName(a).toLowerCase();
|
||||||
const bName = getDisplayName(b).toLowerCase();
|
const bName = getDisplayName(b).toLowerCase();
|
||||||
|
|
||||||
if (aName !== bName) {
|
if (aName !== bName) {
|
||||||
return aName.localeCompare(bName, "zh-CN");
|
return aName.localeCompare(bName, "zh-CN");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果名称也相同,则按时间降序排列(最新的在前面)
|
// 如果名称也相同,则按时间降序排列(最新的在前面)
|
||||||
// 如果lastUpdateTime不存在,则将其排在最后
|
// 如果lastUpdateTime不存在,则将其排在最后
|
||||||
if (!a.lastUpdateTime) return 1;
|
if (!a.lastUpdateTime) return 1;
|
||||||
if (!b.lastUpdateTime) return -1;
|
if (!b.lastUpdateTime) return -1;
|
||||||
|
|
||||||
const timeCompare =
|
const timeCompare =
|
||||||
new Date(b.lastUpdateTime).getTime() -
|
new Date(b.lastUpdateTime).getTime() -
|
||||||
new Date(a.lastUpdateTime).getTime();
|
new Date(a.lastUpdateTime).getTime();
|
||||||
|
|
||||||
return timeCompare;
|
return timeCompare;
|
||||||
},
|
});
|
||||||
);
|
|
||||||
//会话数据同步
|
//会话数据同步
|
||||||
asyncChatSessions(sortedSessions);
|
asyncChatSessions(sortedSessions);
|
||||||
|
|
||||||
@@ -131,6 +120,46 @@ export const chatInitAPIdata = async () => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 递归获取所有联系人列表
|
||||||
|
export const getAllMessageList = async () => {
|
||||||
|
try {
|
||||||
|
let allMessages = [];
|
||||||
|
let page = 1;
|
||||||
|
const limit = 500;
|
||||||
|
let hasMore = true;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
const Result = await getMessageList({
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
const messageList = Result;
|
||||||
|
if (
|
||||||
|
!messageList ||
|
||||||
|
!Array.isArray(messageList) ||
|
||||||
|
messageList.length === 0
|
||||||
|
) {
|
||||||
|
hasMore = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
allMessages = [...allMessages, ...messageList];
|
||||||
|
|
||||||
|
// 如果返回的数据少于请求的数量,说明已经没有更多数据了
|
||||||
|
if (messageList.length == 0) {
|
||||||
|
hasMore = false;
|
||||||
|
} else {
|
||||||
|
page = page + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allMessages;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取所有联系人列表失败:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//发起soket连接
|
//发起soket连接
|
||||||
export const initSocket = () => {
|
export const initSocket = () => {
|
||||||
// 从store获取token和accountId
|
// 从store获取token和accountId
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const createContractList = async (
|
|||||||
const dataByLabels = [];
|
const dataByLabels = [];
|
||||||
for (const label of countLables) {
|
for (const label of countLables) {
|
||||||
let data;
|
let data;
|
||||||
if (label.groupType === 1) {
|
if (Number(label.groupType) === 1) {
|
||||||
if (label.id == 0) {
|
if (label.id == 0) {
|
||||||
data = await contractService.findWhereMultiple([
|
data = await contractService.findWhereMultiple([
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ export const createContractList = async (
|
|||||||
data = data.filter(contact => contact.wechatAccountId === kfSelected);
|
data = data.filter(contact => contact.wechatAccountId === kfSelected);
|
||||||
}
|
}
|
||||||
// console.log(`标签 ${label.groupName} 对应的联系人数据:`, data);
|
// console.log(`标签 ${label.groupName} 对应的联系人数据:`, data);
|
||||||
} else if (label.groupType === 2) {
|
} else if (Number(label.groupType) === 2) {
|
||||||
// groupType: 2, 查询 weChatGroupService
|
// groupType: 2, 查询 weChatGroupService
|
||||||
if (label.id == 0) {
|
if (label.id == 0) {
|
||||||
data = await weChatGroupService.findWhereMultiple([
|
data = await weChatGroupService.findWhereMultiple([
|
||||||
|
|||||||
@@ -1,55 +1,76 @@
|
|||||||
import { Modal } from "antd-mobile";
|
import { Modal } from "antd-mobile";
|
||||||
import { getSetting } from "@/store/module/settings";
|
import { getSetting } from "@/store/module/settings";
|
||||||
export function formatWechatTime(timestamp) {
|
import dayjs from "dayjs";
|
||||||
if (!timestamp) {
|
export function formatWechatTime(lastUpdateTime: string) {
|
||||||
|
if (!lastUpdateTime) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
// 处理时间戳(兼容秒级/毫秒级)
|
|
||||||
const date = new Date(
|
|
||||||
timestamp.toString().length === 10 ? timestamp * 1000 : timestamp,
|
|
||||||
);
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
// 获取消息时间的年月日时分
|
// 使用dayjs解析时间字符串,处理"2025-10-22 13:21:13"格式
|
||||||
const messageYear = date.getFullYear();
|
let messageTime;
|
||||||
const messageMonth = date.getMonth();
|
try {
|
||||||
const messageDate = date.getDate();
|
// 直接解析时间字符串,dayjs可以自动识别这种格式
|
||||||
const messageHour = date.getHours().toString().padStart(2, "0");
|
messageTime = dayjs(lastUpdateTime);
|
||||||
const messageMinute = date.getMinutes().toString().padStart(2, "0");
|
|
||||||
|
|
||||||
// 获取当前时间的年月日
|
// 检查日期是否有效
|
||||||
const nowYear = now.getFullYear();
|
if (!messageTime.isValid()) {
|
||||||
const nowMonth = now.getMonth();
|
console.warn("formatWechatTime: Invalid date string", lastUpdateTime);
|
||||||
const nowDate = now.getDate();
|
return "";
|
||||||
|
}
|
||||||
// 创建当天0点的时间对象,用于比较是否同一天
|
} catch (error) {
|
||||||
const today = new Date(nowYear, nowMonth, nowDate, 0, 0, 0);
|
console.error(
|
||||||
const yesterday = new Date(today);
|
"formatWechatTime: Error processing date string",
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
lastUpdateTime,
|
||||||
const weekAgo = new Date(today);
|
error,
|
||||||
weekAgo.setDate(weekAgo.getDate() - 6); // 7天前(包括今天)
|
);
|
||||||
|
return "";
|
||||||
// 消息日期(不含时间)
|
|
||||||
const messageDay = new Date(messageYear, messageMonth, messageDate, 0, 0, 0);
|
|
||||||
|
|
||||||
// 当天消息:只显示时分
|
|
||||||
if (messageDay.getTime() === today.getTime()) {
|
|
||||||
return `${messageHour}:${messageMinute}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 昨天消息:显示"昨天 时分"
|
const now = dayjs();
|
||||||
if (messageDay.getTime() === yesterday.getTime()) {
|
|
||||||
return `昨天 ${messageHour}:${messageMinute}`;
|
// 获取消息时间和当前时间的年份
|
||||||
|
const messageYear = messageTime.year();
|
||||||
|
const nowYear = now.year();
|
||||||
|
|
||||||
|
// 如果是去年或更早的时间,直接返回空字符串
|
||||||
|
if (messageYear < nowYear) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 一周内消息:显示"星期X 时分"
|
// 创建时间比较对象
|
||||||
if (messageDay.getTime() >= weekAgo.getTime()) {
|
const today = now.startOf("day");
|
||||||
|
const yesterday = today.subtract(1, "day");
|
||||||
|
const messageDay = messageTime.startOf("day");
|
||||||
|
|
||||||
|
// 1. 时间是否今天,如果是就展示时和分,示例为: "13:00"
|
||||||
|
if (messageDay.isSame(today, "day")) {
|
||||||
|
return messageTime.format("HH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 时间是否为昨天,如果是就展示"昨天"和时与分,示例为: "昨天 13:00"
|
||||||
|
if (messageDay.isSame(yesterday, "day")) {
|
||||||
|
return `昨天 ${messageTime.format("HH:mm")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 时间是否在本周,如果是就展示具体周几,示例为: "周一"
|
||||||
|
const startOfWeek = now.startOf("week");
|
||||||
|
const endOfWeek = now.endOf("week");
|
||||||
|
if (messageTime.isAfter(startOfWeek) && messageTime.isBefore(endOfWeek)) {
|
||||||
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
||||||
return `${weekdays[date.getDay()]} ${messageHour}:${messageMinute}`;
|
return weekdays[messageTime.day()];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 超过一周:显示"年月日 时分"
|
// 4. 时间是否在本月,如果是就展示月和日,示例为: "12/07"
|
||||||
return `${messageYear}年${messageMonth + 1}月${messageDate}日 ${messageHour}:${messageMinute}`;
|
if (messageTime.isSame(now, "month")) {
|
||||||
|
return messageTime.format("MM/DD");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 时间是否在本年,如果在本年就展示月和日,示例为: "12/07"
|
||||||
|
if (messageTime.isSame(now, "year")) {
|
||||||
|
return messageTime.format("MM/DD");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 通用js调用弹窗,Promise风格
|
* 通用js调用弹窗,Promise风格
|
||||||
|
|||||||
Reference in New Issue
Block a user