新增同步状态提示栏,优化消息列表组件的同步逻辑,支持逐页同步并即时更新UI,提升用户体验和代码可读性。
This commit is contained in:
@@ -242,3 +242,41 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步状态提示栏样式
|
||||||
|
.syncStatusBar {
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 0 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.syncStatusContent {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.syncStatusText {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.syncButton {
|
||||||
|
color: green;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
&:hover {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import {
|
import { List, Avatar, Badge, Modal, Input, message } from "antd";
|
||||||
List,
|
|
||||||
Avatar,
|
|
||||||
Badge,
|
|
||||||
Modal,
|
|
||||||
Input,
|
|
||||||
message,
|
|
||||||
Skeleton,
|
|
||||||
Spin,
|
|
||||||
} from "antd";
|
|
||||||
import {
|
import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
PushpinOutlined,
|
PushpinOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import styles from "./com.module.scss";
|
import styles from "./com.module.scss";
|
||||||
import {
|
import {
|
||||||
@@ -47,14 +40,13 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
|
|
||||||
// Store状态
|
// Store状态
|
||||||
const {
|
const {
|
||||||
loading,
|
|
||||||
hasLoadedOnce,
|
hasLoadedOnce,
|
||||||
setLoading,
|
|
||||||
setHasLoadedOnce,
|
setHasLoadedOnce,
|
||||||
sessions,
|
sessions,
|
||||||
setSessions: setSessionState,
|
setSessions: setSessionState,
|
||||||
} = useMessageStore();
|
} = useMessageStore();
|
||||||
const [filteredSessions, setFilteredSessions] = useState<ChatSession[]>([]);
|
const [filteredSessions, setFilteredSessions] = useState<ChatSession[]>([]);
|
||||||
|
const [syncing, setSyncing] = useState(false); // 同步状态
|
||||||
|
|
||||||
// 右键菜单相关状态
|
// 右键菜单相关状态
|
||||||
const [contextMenu, setContextMenu] = useState<{
|
const [contextMenu, setContextMenu] = useState<{
|
||||||
@@ -306,70 +298,104 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
|
|
||||||
// ==================== 数据加载 ====================
|
// ==================== 数据加载 ====================
|
||||||
|
|
||||||
// 与服务器同步数据
|
// 与服务器同步数据(优化版:逐页同步,立即更新UI)
|
||||||
const syncWithServer = async () => {
|
const syncWithServer = async () => {
|
||||||
if (!currentUserId) return;
|
if (!currentUserId) return;
|
||||||
|
|
||||||
|
setSyncing(true); // 开始同步,显示同步状态栏
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取会话列表数据(分页获取所有数据)
|
|
||||||
let allMessages: any[] = [];
|
|
||||||
let page = 1;
|
let page = 1;
|
||||||
const limit = 500;
|
const limit = 500;
|
||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
|
let totalProcessed = 0;
|
||||||
|
let successCount = 0;
|
||||||
|
let failCount = 0;
|
||||||
|
|
||||||
// 分页获取会话列表
|
// 分页获取会话列表,每页成功后立即同步
|
||||||
while (hasMore) {
|
while (hasMore) {
|
||||||
const result: any = await getMessageList({
|
try {
|
||||||
page,
|
const result: any = await getMessageList({
|
||||||
limit,
|
page,
|
||||||
});
|
limit,
|
||||||
|
});
|
||||||
|
|
||||||
if (!result || !Array.isArray(result) || result.length === 0) {
|
if (!result || !Array.isArray(result) || result.length === 0) {
|
||||||
hasMore = false;
|
hasMore = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
allMessages = [...allMessages, ...result];
|
// 立即处理这一页的数据
|
||||||
|
const friends = result.filter(
|
||||||
|
(msg: any) => msg.dataType === "friend" || !msg.chatroomId,
|
||||||
|
);
|
||||||
|
const groups = result
|
||||||
|
.filter((msg: any) => msg.dataType === "group" || msg.chatroomId)
|
||||||
|
.map((msg: any) => ({
|
||||||
|
...msg,
|
||||||
|
chatroomAvatar: msg.chatroomAvatar || msg.avatar || "",
|
||||||
|
}));
|
||||||
|
|
||||||
if (result.length < limit) {
|
// 立即同步这一页到数据库(会触发UI更新)
|
||||||
hasMore = false;
|
// 分页同步时跳过删除检查,避免误删其他页的会话
|
||||||
} else {
|
await MessageManager.syncSessions(
|
||||||
|
currentUserId,
|
||||||
|
{
|
||||||
|
friends,
|
||||||
|
groups,
|
||||||
|
},
|
||||||
|
{ skipDelete: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
totalProcessed += result.length;
|
||||||
|
successCount++;
|
||||||
|
|
||||||
|
// 判断是否还有下一页
|
||||||
|
if (result.length < limit) {
|
||||||
|
hasMore = false;
|
||||||
|
} else {
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略单页失败,继续处理下一页
|
||||||
|
console.error(`第${page}页同步失败:`, error);
|
||||||
|
failCount++;
|
||||||
|
|
||||||
|
// 如果连续失败太多,停止同步
|
||||||
|
if (failCount >= 3) {
|
||||||
|
console.warn("连续失败次数过多,停止同步");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续下一页
|
||||||
page++;
|
page++;
|
||||||
|
if (page > 100) {
|
||||||
|
// 防止无限循环
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分离好友和群聊数据
|
|
||||||
const friends = allMessages.filter(
|
|
||||||
(msg: any) => msg.dataType === "friend" || !msg.chatroomId,
|
|
||||||
);
|
|
||||||
const groups = allMessages
|
|
||||||
.filter((msg: any) => msg.dataType === "group" || msg.chatroomId)
|
|
||||||
.map((msg: any) => {
|
|
||||||
// 确保群聊数据包含正确的头像字段
|
|
||||||
// 如果接口返回的是 avatar 字段,需要映射到 chatroomAvatar
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
chatroomAvatar: msg.chatroomAvatar || msg.avatar || "",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 执行增量同步
|
|
||||||
const syncResult = await MessageManager.syncSessions(currentUserId, {
|
|
||||||
friends,
|
|
||||||
groups,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 同步后验证数据
|
|
||||||
const verifySession = await MessageManager.getUserSessions(currentUserId);
|
|
||||||
console.log("同步后的会话数据示例:", verifySession[0]);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`会话同步完成: 新增${syncResult.added}, 更新${syncResult.updated}, 删除${syncResult.deleted}`,
|
`会话同步完成: 成功${successCount}页, 失败${failCount}页, 共处理${totalProcessed}条数据`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 会话管理器会在有变更时触发订阅回调
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("同步服务器数据失败:", error);
|
console.error("同步服务器数据失败:", error);
|
||||||
|
} finally {
|
||||||
|
setSyncing(false); // 同步完成,更新状态栏
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手动触发同步的函数
|
||||||
|
const handleManualSync = async () => {
|
||||||
|
if (syncing) return; // 如果正在同步,不重复触发
|
||||||
|
setSyncing(true);
|
||||||
|
try {
|
||||||
|
await syncWithServer();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("手动同步失败:", error);
|
||||||
|
} finally {
|
||||||
|
setSyncing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -394,8 +420,6 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
const requestId = ++loadRequestRef.current;
|
const requestId = ++loadRequestRef.current;
|
||||||
|
|
||||||
const initializeSessions = async () => {
|
const initializeSessions = async () => {
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cachedSessions =
|
const cachedSessions =
|
||||||
await MessageManager.getUserSessions(currentUserId);
|
await MessageManager.getUserSessions(currentUserId);
|
||||||
@@ -404,6 +428,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 有缓存数据立即显示
|
||||||
if (cachedSessions.length > 0) {
|
if (cachedSessions.length > 0) {
|
||||||
setSessionState(cachedSessions);
|
setSessionState(cachedSessions);
|
||||||
}
|
}
|
||||||
@@ -411,12 +436,18 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
const needsFullSync = cachedSessions.length === 0 || !hasLoadedOnce;
|
const needsFullSync = cachedSessions.length === 0 || !hasLoadedOnce;
|
||||||
|
|
||||||
if (needsFullSync) {
|
if (needsFullSync) {
|
||||||
await syncWithServer();
|
// 不等待同步完成,让它在后台进行,第一页数据同步后会立即更新UI
|
||||||
if (isCancelled || loadRequestRef.current !== requestId) {
|
syncWithServer()
|
||||||
return;
|
.then(() => {
|
||||||
}
|
if (!isCancelled && loadRequestRef.current === requestId) {
|
||||||
setHasLoadedOnce(true);
|
setHasLoadedOnce(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("同步失败:", error);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// 后台同步
|
||||||
syncWithServer().catch(error => {
|
syncWithServer().catch(error => {
|
||||||
console.error("后台同步失败:", error);
|
console.error("后台同步失败:", error);
|
||||||
});
|
});
|
||||||
@@ -425,10 +456,6 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
console.error("初始化会话列表失败:", error);
|
console.error("初始化会话列表失败:", error);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (!isCancelled && loadRequestRef.current === requestId) {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -526,13 +553,11 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
// 渲染完毕后自动点击第一个聊天记录
|
// 渲染完毕后自动点击第一个聊天记录
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只在以下条件满足时自动点击:
|
// 只在以下条件满足时自动点击:
|
||||||
// 1. 不在加载状态
|
// 1. 有过滤后的会话列表
|
||||||
// 2. 有过滤后的会话列表
|
// 2. 当前没有选中的联系人
|
||||||
// 3. 当前没有选中的联系人
|
// 3. 还没有自动点击过
|
||||||
// 4. 还没有自动点击过
|
// 4. 不在搜索状态(避免搜索时自动切换)
|
||||||
// 5. 不在搜索状态(避免搜索时自动切换)
|
|
||||||
if (
|
if (
|
||||||
!loading &&
|
|
||||||
filteredSessions.length > 0 &&
|
filteredSessions.length > 0 &&
|
||||||
!currentContract &&
|
!currentContract &&
|
||||||
!autoClickRef.current &&
|
!autoClickRef.current &&
|
||||||
@@ -550,7 +575,7 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [loading, filteredSessions, currentContract, searchKeyword]);
|
}, [filteredSessions, currentContract, searchKeyword]);
|
||||||
|
|
||||||
// ==================== WebSocket消息处理 ====================
|
// ==================== WebSocket消息处理 ====================
|
||||||
|
|
||||||
@@ -796,156 +821,146 @@ const MessageList: React.FC<MessageListProps> = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染骨架屏
|
// 渲染同步状态提示栏
|
||||||
const renderSkeleton = () => (
|
const renderSyncStatusBar = () => (
|
||||||
<div className={styles.skeletonContainer}>
|
<div className={styles.syncStatusBar}>
|
||||||
{Array(8)
|
{syncing ? (
|
||||||
.fill(null)
|
<div className={styles.syncStatusContent}>
|
||||||
.map((_, index) => (
|
<span className={styles.syncStatusText}>
|
||||||
<div key={`skeleton-${index}`} className={styles.skeletonItem}>
|
<LoadingOutlined style={{ marginRight: "10px" }} /> 同步中...
|
||||||
<Skeleton.Avatar active size={48} shape="circle" />
|
</span>
|
||||||
<div className={styles.skeletonContent}>
|
</div>
|
||||||
<div className={styles.skeletonHeader}>
|
) : (
|
||||||
<Skeleton.Input active size="small" style={{ width: "40%" }} />
|
<div className={styles.syncStatusContent}>
|
||||||
<Skeleton.Input active size="small" style={{ width: "20%" }} />
|
<span className={styles.syncStatusText}>
|
||||||
</div>
|
<CheckCircleOutlined
|
||||||
<Skeleton.Input
|
style={{ color: "green", marginRight: "10px" }}
|
||||||
active
|
/>
|
||||||
size="small"
|
同步完成
|
||||||
style={{ width: "70%", marginTop: "8px" }}
|
</span>
|
||||||
/>
|
<span className={styles.syncButton} onClick={handleManualSync}>
|
||||||
</div>
|
同步
|
||||||
</div>
|
</span>
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
);
|
|
||||||
|
|
||||||
// 渲染加载中状态(带旋转动画)
|
|
||||||
const renderLoading = () => (
|
|
||||||
<div className={styles.loadingContainer}>
|
|
||||||
<Spin size="large" tip="加载中...">
|
|
||||||
<div className={styles.loadingContent}>{renderSkeleton()}</div>
|
|
||||||
</Spin>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.messageList}>
|
<div className={styles.messageList}>
|
||||||
{loading ? (
|
{/* 同步状态提示栏 */}
|
||||||
// 加载状态:显示加载动画和骨架屏
|
{renderSyncStatusBar()}
|
||||||
renderLoading()
|
|
||||||
) : (
|
<List
|
||||||
<>
|
dataSource={filteredSessions as any[]}
|
||||||
<List
|
renderItem={session => (
|
||||||
dataSource={filteredSessions as any[]}
|
<List.Item
|
||||||
renderItem={session => (
|
key={session.id}
|
||||||
<List.Item
|
className={`${styles.messageItem} ${
|
||||||
key={session.id}
|
currentContract?.id === session.id ? styles.active : ""
|
||||||
className={`${styles.messageItem} ${
|
} ${(session.config as any)?.top ? styles.pinned : ""}`}
|
||||||
currentContract?.id === session.id ? styles.active : ""
|
onClick={() => onContactClick(session)}
|
||||||
} ${(session.config as any)?.top ? styles.pinned : ""}`}
|
onContextMenu={e => handleContextMenu(e, session)}
|
||||||
onClick={() => onContactClick(session)}
|
>
|
||||||
onContextMenu={e => handleContextMenu(e, session)}
|
<div className={styles.messageInfo}>
|
||||||
>
|
<Badge count={session.config.unreadCount || 0} size="small">
|
||||||
<div className={styles.messageInfo}>
|
<Avatar
|
||||||
<Badge count={session.config.unreadCount || 0} size="small">
|
size={48}
|
||||||
<Avatar
|
src={session.avatar}
|
||||||
size={48}
|
icon={
|
||||||
src={session.avatar}
|
session?.type === "group" ? (
|
||||||
icon={
|
<TeamOutlined />
|
||||||
session?.type === "group" ? (
|
) : (
|
||||||
<TeamOutlined />
|
<UserOutlined />
|
||||||
) : (
|
)
|
||||||
<UserOutlined />
|
}
|
||||||
)
|
/>
|
||||||
}
|
</Badge>
|
||||||
/>
|
<div className={styles.messageDetails}>
|
||||||
</Badge>
|
<div className={styles.messageHeader}>
|
||||||
<div className={styles.messageDetails}>
|
<div className={styles.messageName}>
|
||||||
<div className={styles.messageHeader}>
|
{session.conRemark || session.nickname || session.wechatId}
|
||||||
<div className={styles.messageName}>
|
</div>
|
||||||
{session.conRemark ||
|
<div className={styles.messageTime}>
|
||||||
session.nickname ||
|
{formatWechatTime(session?.lastUpdateTime)}
|
||||||
session.wechatId}
|
|
||||||
</div>
|
|
||||||
<div className={styles.messageTime}>
|
|
||||||
{formatWechatTime(session?.lastUpdateTime)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.messageContent}>
|
|
||||||
{messageFilter(session.content)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
<div className={styles.messageContent}>
|
||||||
)}
|
{messageFilter(session.content)}
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
{/* 右键菜单 */}
|
|
||||||
{contextMenu.visible && contextMenu.session && (
|
|
||||||
<div
|
|
||||||
ref={contextMenuRef}
|
|
||||||
className={styles.contextMenu}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
left: contextMenu.x,
|
|
||||||
top: contextMenu.y,
|
|
||||||
zIndex: 1000,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={styles.menuItem}
|
|
||||||
onClick={() => handleTogglePin(contextMenu.session!)}
|
|
||||||
>
|
|
||||||
<PushpinOutlined />
|
|
||||||
{(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={styles.menuItem}
|
|
||||||
onClick={() => handleEditRemark(contextMenu.session!)}
|
|
||||||
>
|
|
||||||
<EditOutlined />
|
|
||||||
修改备注
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={styles.menuItem}
|
|
||||||
onClick={() => handleDelete(contextMenu.session!)}
|
|
||||||
>
|
|
||||||
<DeleteOutlined />
|
|
||||||
删除
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</List.Item>
|
||||||
|
)}
|
||||||
|
locale={{
|
||||||
|
emptyText:
|
||||||
|
filteredSessions.length === 0 && !syncing ? "暂无会话" : null,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 修改备注Modal */}
|
{/* 右键菜单 */}
|
||||||
<Modal
|
{contextMenu.visible && contextMenu.session && (
|
||||||
title="修改备注"
|
<div
|
||||||
open={editRemarkModal.visible}
|
ref={contextMenuRef}
|
||||||
onOk={handleSaveRemark}
|
className={styles.contextMenu}
|
||||||
onCancel={() =>
|
style={{
|
||||||
setEditRemarkModal({
|
position: "fixed",
|
||||||
visible: false,
|
left: contextMenu.x,
|
||||||
session: null,
|
top: contextMenu.y,
|
||||||
remark: "",
|
zIndex: 1000,
|
||||||
})
|
}}
|
||||||
}
|
>
|
||||||
okText="保存"
|
<div
|
||||||
cancelText="取消"
|
className={styles.menuItem}
|
||||||
|
onClick={() => handleTogglePin(contextMenu.session!)}
|
||||||
>
|
>
|
||||||
<Input
|
<PushpinOutlined />
|
||||||
value={editRemarkModal.remark}
|
{(contextMenu.session.config as any)?.top ? "取消置顶" : "置顶"}
|
||||||
onChange={e =>
|
</div>
|
||||||
setEditRemarkModal(prev => ({
|
<div
|
||||||
...prev,
|
className={styles.menuItem}
|
||||||
remark: e.target.value,
|
onClick={() => handleEditRemark(contextMenu.session!)}
|
||||||
}))
|
>
|
||||||
}
|
<EditOutlined />
|
||||||
placeholder="请输入备注"
|
修改备注
|
||||||
maxLength={20}
|
</div>
|
||||||
/>
|
<div
|
||||||
</Modal>
|
className={styles.menuItem}
|
||||||
</>
|
onClick={() => handleDelete(contextMenu.session!)}
|
||||||
|
>
|
||||||
|
<DeleteOutlined />
|
||||||
|
删除
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 修改备注Modal */}
|
||||||
|
<Modal
|
||||||
|
title="修改备注"
|
||||||
|
open={editRemarkModal.visible}
|
||||||
|
onOk={handleSaveRemark}
|
||||||
|
onCancel={() =>
|
||||||
|
setEditRemarkModal({
|
||||||
|
visible: false,
|
||||||
|
session: null,
|
||||||
|
remark: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
okText="保存"
|
||||||
|
cancelText="取消"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={editRemarkModal.remark}
|
||||||
|
onChange={e =>
|
||||||
|
setEditRemarkModal(prev => ({
|
||||||
|
...prev,
|
||||||
|
remark: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder="请输入备注"
|
||||||
|
maxLength={20}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -259,6 +259,8 @@ export class MessageManager {
|
|||||||
* 增量同步会话数据
|
* 增量同步会话数据
|
||||||
* @param userId 用户ID
|
* @param userId 用户ID
|
||||||
* @param serverData 服务器数据
|
* @param serverData 服务器数据
|
||||||
|
* @param options 同步选项
|
||||||
|
* @param options.skipDelete 是否跳过删除检查(用于分页增量同步)
|
||||||
* @returns 同步结果统计
|
* @returns 同步结果统计
|
||||||
*/
|
*/
|
||||||
static async syncSessions(
|
static async syncSessions(
|
||||||
@@ -267,6 +269,9 @@ export class MessageManager {
|
|||||||
friends?: ContractData[];
|
friends?: ContractData[];
|
||||||
groups?: weChatGroup[];
|
groups?: weChatGroup[];
|
||||||
},
|
},
|
||||||
|
options?: {
|
||||||
|
skipDelete?: boolean; // 是否跳过删除检查(用于分页增量同步)
|
||||||
|
},
|
||||||
): Promise<{
|
): Promise<{
|
||||||
added: number;
|
added: number;
|
||||||
updated: number;
|
updated: number;
|
||||||
@@ -323,10 +328,12 @@ export class MessageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查删除
|
// 检查删除(仅在非增量同步模式下执行)
|
||||||
for (const localSession of localSessions) {
|
if (!options?.skipDelete) {
|
||||||
if (!serverSessionMap.has(localSession.serverId)) {
|
for (const localSession of localSessions) {
|
||||||
toDelete.push(localSession.serverId);
|
if (!serverSessionMap.has(localSession.serverId)) {
|
||||||
|
toDelete.push(localSession.serverId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user